sass / embedded-host-node

A Node.js library that will communicate with Embedded Dart Sass using the Embedded Sass protocol
MIT License
169 stars 29 forks source link

Allow using a custom sass binary #334

Closed Znert closed 2 months ago

Znert commented 2 months ago

I recently wanted to bootstrap a new Sveltekit app and use sass-embedded so that I can use SCSS in my web app. But because I run NixOS, I can't use the pre-compiled version of sass-dart you are shipping with the sass-embedded-linux-x64 package - dynamic linking works a little differently on NixOS than on normal Linux distributions.

Now, NixOS already provides a working binary via the dart-sass package. However, there is no way for me to instruct sass-embedded to use the existing (globally installed) sass binary instead of the one inside the node_modules folder.

My idea would be to use some env variable, something like SASS_EMBEDDED_BIN_PATH, that I can set in order to point towards the binary I want to use.

I'm not sure if you need/want to add some checks to ensure that the sass-embedded package is compatible with the sass binary, but I think it's fair to tell the user "you are on your own if you do this".

ntkme commented 2 months ago

You are on your own. sass-embedded is only guaranteed to work with version matched dart-sass, and the version shipped by Nix is usually not the right version. For example, the current version in Nix is a few releases behind, and there was a breaking change for color-4 embedded protocol, which will break if you try to force older version of dart-sass from Nix.

Znert commented 2 months ago

Well, anything is better than not being able to run the sass compiler at all, right?

I tried to apply an pnpm patch to test my theory out and so far it seems to work fine. But I've only got a very basic project, so I assume there will be dragons later down the line. Will post my code for anyone running into a similar issue tomorrow, because I've also turned off my PC for the day.

As for version missmatch, I would be fine with running an older version of the library. I'll check tomorrow which binary version you support with which package version.

ntkme commented 2 months ago

anything is better than not being able to run the sass compiler at all, right?

Yes, but because this can easily break, I don’t think we want to officially support it. As you already discovered you can still patch the package locally yourself.

Another solution is to port the logic used by ruby sass-embedded: https://ntk.me/2023/06/04/invoking-ld-linux-so/ Given that we now have an elf parser that read the interpreter, it should be relatively easy to port the ruby implementation.

Znert commented 2 months ago

As promised, here is the patch I applied for anybody in a similar spot as me:

In your project root directory, you should now have a folder called patches with a file called sass-embedded.patch. For the version of sass-embedded I had installed, this was the content of the file:

diff --git a/dist/lib/src/compiler-path.js b/dist/lib/src/compiler-path.js
index c274baf174a035c4b568f14e7167900cb29911481b69dcf5b46641315cc65f8b..0beebe20c20cb68248f80048de57696cb44a7b57d873de3fd4ee3eb002adfee6 100644
--- a/dist/lib/src/compiler-path.js
+++ b/dist/lib/src/compiler-path.js
@@ -24,6 +24,10 @@ function isLinuxMusl(path) {
 }
 /** The full command for the embedded compiler executable. */
 exports.compilerCommand = (() => {
+    const binPath = process.env.SASS_EMBEDDED_BIN_PATH;
+    if (binPath) {
+        return [binPath];
+    }
     const platform = process.platform === 'linux' && isLinuxMusl(process.execPath)
         ? 'linux-musl'
         : process.platform;

In order to set the SASS_EMBEDDED_BIN_PATH env variable, I'm using this flake.nix:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs, ... }:
  let
    lib = nixpkgs.lib;
    system = "x86_64-linux";
    pkgs = import nixpkgs {
      inherit system;
    };
  in
  {

    devShells.${system}.default = pkgs.mkShell {
      buildInputs = with pkgs; [
        nodejs_20
        pnpm
        dart-sass
      ];
      SASS_EMBEDDED_BIN_PATH = "${pkgs.dart-sass}/bin/sass";
    };

  };
}

To make everything work with VSCode, I've installed the direnv extension and setup an .envrc:

if ! has nix_direnv_version || ! nix_direnv_version 3.0.5; then
    source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.5/direnvrc" "sha256-RuwIS+QKFj/T9M2TFXScjBsLR6V3A17YVoEW/Q6AZ1w="
fi

use flake

More details can be found in the nix.direnv repo.


Obviously this setup won't be very stable. Any update to either the dart-sass Nix package or the sass-embedded npm package could easily break the setup. But for now it seems to work...


Edit: I also had to update the vite.config.ts to get rid of the legacy API message:

diff --git a/vite.config.ts b/vite.config.ts
index 3427d0a..c95006f 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -3,6 +3,13 @@ import { defineConfig } from 'vitest/config';

 export default defineConfig({
        plugins: [sveltekit()],
+       css: {
+               preprocessorOptions: {
+                       scss: {
+                               api: 'modern-compiler',
+                       },
+               },
+       },
        test: {
                include: ['src/**/*.{test,spec}.{js,ts}'],
        },
nex3 commented 2 months ago

I agree with @ntkme here that we should actively avoid supporting this officially because it's so easy to get broken. I think the "correct" way to handle it would be to have NixOS build the entire Sass embedded stack and npm link it into your project.

Znert commented 2 months ago

Makes sense to me. Say I would like to do something like that, how would I go about this?

Looking at your prepare-optional-release.ts script, you download the pre-built binaries from sass/dart-sass and package them as npm packages. I think I could write a "post init" script to my flake.nix, that would replace the content of the sass-embedded-linux-x64 (or similar) to use the binaries packaged for/by NixOS.

But I think the result would just be the same: Version mismatch hell :thinking:

I think maybe it would just be simpler to pull a specific version of dart-sass and the appropriate npm package version of sass-embedded.

Of course if you can't help me, that's also fine. As long as I have a somewhat working workspace, I'm happy :smile:

nex3 commented 2 months ago

I'm not very familiar with NixOS, but I can help you navigate the build process of these packages. When you're building the embedded host, you can pass --compiler-path and --language-path flags to npm run init to have it build against a local version of Dart Sass and the Sass language repo, respectively. So my recommendation would be:

siggerajamae commented 2 months ago

Hello, came across when I had the same problem. There is a solution for this called nix-ld, you can read about it here: https://nix.dev/guides/faq#how-to-run-non-nix-executables This lets you install dynamic libraries to be used like "normal" by unpackaged programs. Adding the following to my configuration.nix has solved this issue and similar ones for me:

  # Dynamic libraries for unpackaged programs
  programs.nix-ld.enable = true;
  programs.nix-ld.libraries = with pkgs; [
    glibc
    libcxx
  ];
martinetd commented 1 week ago

FWIW just removing the module apparently works around the problem? Not sure if it just falls back to js or whatever

https://github.com/NixOS/nixpkgs/pull/351966/files

  preBuild = ''
    # using sass-embedded fails at executing node_modules/sass-embedded-linux-x64/dart-sass/src/dart
    rm -r node_modules/sass-embedded*
  ''

(and I'm now doing the same for rmfakecloud, that appears to have built something and it doesn't look too wrong...)

ValJed commented 5 days ago

@martinetd I think it'll fallback on the JS version which is way slower..