dart-sass icon indicating copy to clipboard operation
dart-sass copied to clipboard

Support running in a browser

Open matanlurey opened this issue 7 years ago • 35 comments

This would enable in-browser use, use in a service worker, etc.

matanlurey avatar Nov 05 '16 01:11 matanlurey

We already isolate our dart:io dependency behind a configured import. I'm retargeting this to focus on adding a web-compatible implementation of the IO APIs we use.

This is something that would be nice to have, but I don't think I'm going to have time to implement or maintain it myself.

nex3 avatar Nov 07 '16 19:11 nex3

I think just having the capability would be enough, for now. A sass_browser package could always implement the gritty bits.

matanlurey avatar Nov 07 '16 19:11 matanlurey

Since this is handled by a configured import, we can't really expose hooks for an external package. It has to be done in our codebase, since we handle the JS compilation.

nex3 avatar Nov 07 '16 21:11 nex3

Would love to make SASS available natively to shared hosting, even if slow, by using a JS version in the site's files. DreamHost shared doesn't (easily, if at all) provide Ruby and has a cap on processes running, so nodeJS won't fly.

I love SASS's simplistic syntax and would love to run/compile it "natively" from my website, instead of uploading CSS files. Also, I don't know enough about Dart/node/JS to do this myself, otherwise I would. I'm struggling to figure out whether to learn Dart or Go, Dart seems to be more tempting PR/marketing-wise.

Oh, and as a student I don't have the resources to pay for a VPS or anything above, as I'm lucky to have shared hosting to begin with.

I'm really tempted to learn Dart to see if this is possible...

HopperMCS avatar Nov 10 '16 04:11 HopperMCS

I think that one option could be if this module will ignore file parameter in compile api and use only data if there is no file system available as in case of browser.

There is also a drop-in replacement of node file system api for browsers: https://github.com/jvilk/BrowserFS . But even after enabling this module for fs replacement in webpack sass npm is not usable due to other globals misses in browser. I have no previous experience with dart: is it something like target (node or browser) we could change to fix result js? I've not found any information for npm build in readme.

yatsyk avatar Mar 26 '18 09:03 yatsyk

@yatsyk It's more complicated than just dropping in a filesystem implementation. We'd probably want to do a separate compilation that targets the web. You'd need to start by adding a web-compatible implementation of the IO functions, but it's also not clear that the render() API would work great on the web—the importer API strongly assumes it's working with file paths, and it returns a typed array that may be difficult to work with in a browser context.

nex3 avatar Mar 26 '18 23:03 nex3

In terms of what sort of importers would be necessary for the web, I can think of two possibilities:

  • an AsyncImporter that just fetches source files from URLs
  • a non-async importer that just sort of fakes a filesystem with a map from filenames to source file contents

Do both of these sound reasonable? Are there others we'd want to support?

Also, is the goal here just to have a Dart library that can be imported on the web, or would we also want to distribute a compiled JS library that could be depended on by non-Dart web apps?

jathak avatar Oct 30 '18 22:10 jathak

Based on @jathak's suggestion, I could also imagine an implementation agnostic file system abstraction "interface" that allows for asynchronous answers to request. On top of that, Sass code could be stored, fetched or even generated on the fly.

loilo avatar Oct 30 '18 23:10 loilo

In terms of what sort of importers would be necessary for the web, I can think of two possibilities:

  • an AsyncImporter that just fetches source files from URLs
  • a non-async importer that just sort of fakes a filesystem with a map from filenames to source file contents

Do both of these sound reasonable? Are there others we'd want to support?

I think for starters, it's fine to just do this without any importers at all, and leave it up to end-users to write importers if they want them. If we do add first-party importers, I think they should probably be distributed separately from Dart Sass itself. Running client-side is already kind of a niche feature, and I don't want to devote a ton of API surface in this package to it.

Also, is the goal here just to have a Dart library that can be imported on the web, or would we also want to distribute a compiled JS library that could be depended on by non-Dart web apps?

Ideally both. For example, it would be super cool to be able to make examples on the Sass site editable by users, and that would be a lot easier if it was available as just a chunk of JS. But starting with Dart support is probably a good idea.

nex3 avatar Oct 31 '18 23:10 nex3

Has in-browser use been added?

TheComputerM avatar Oct 18 '20 16:10 TheComputerM

I would love to support and give some time to make this possible if you can show me how it should be done.

akmandev avatar Oct 27 '20 08:10 akmandev

See https://github.com/sass/dart-sass/issues/25#issuecomment-376348604 for an overview of how to get started.

nex3 avatar Oct 28 '20 22:10 nex3

It seems like renderSync could be run in the browser, if it relied solely on a custom importer to resolve all the file paths, used with a giant map of pathNames to scss contents.

example:

data.json

"files": [
        {
            "filePath": "test.scss",
            "scss": "@import 'demo';"
        },
        {
            "filePath": "demo.scss",
            "scss": "SCSS_FILE_CONTENTS"
        },
renderSync({
    data: "@import 'test",
    importer: (url, prev, done) => {
        const foundIndex = data.files.findIndex(file => file.filePath === url);
        return {
            file: data.files[foundIndex].filePath,
            contents: data.files[foundIndex].scss,
        }
    },
})

The problem I hit was I can't just import renderSync.

When importing node_modules/sass/sass.dart.js

self.chokidar = require("chokidar");
self.readline = require("readline");
self.fs = require("fs");

This assumes I'm running in node and not the browser.

initplatform avatar Feb 01 '21 20:02 initplatform

./node_modules/sass/sass.dart.js Module not found: Can't resolve 'readline' in '..t/node_modules/sass' using npm i sass in react app for in browser

TheRakeshPurohit avatar Feb 11 '21 05:02 TheRakeshPurohit

Any updates on this?

TheComputerM avatar May 12 '21 05:05 TheComputerM

Did some experimentations here, removed the require on chokidar and readline so that it imports well in a browser env. Tried using renderSync like @initplatform mentioned but getting some Illegal Invocations errors which probably has something to do with using call on system functions

bruno12mota avatar Jun 30 '21 10:06 bruno12mota

A different approach would be to compile to wasm and have a small js interface to communicate with it, similar to how libs like esbuild did https://esbuild.github.io/getting-started/#wasm

bruno12mota avatar Jun 30 '21 10:06 bruno12mota

@markwhitfeld Now that https://github.com/google/dart_cli_pkg/pull/91 has landed, the ball's in your court here. You can use that to set the fs, readline, and chokidar requires to null, but you'll also need to update the JS interop in lib/src/io/node.dart to gracefully degrade when fs isn't available and update lib/src/node/utils.dart to use a typed array rather than a buffer in a non-Node context. Let me know if you need any more guidance!

nex3 avatar Sep 28 '21 22:09 nex3

Fantastic! Thank you. I'll look into it towards the end of the week.

markwhitfeld avatar Sep 29 '21 09:09 markwhitfeld

@nex3 After a very busy few weeks, I finally had a chance to get back on to this. I have some questions:

Currently, the output of both the css and the map properties of the result returned by the render calls are of the Buffer type (as exposed in the @types/sass typings). If we want to remain consistent with these types, then we could use the buffer package as a pollyfill in the browser to replace the node Buffer.from usage. This is same pollyfill that we use at StackBlitz to get dart-sass working. What do you think about using this package? (it would be an optionalDependency, which would only be needed for browser usages)

markwhitfeld avatar Oct 25 '21 21:10 markwhitfeld

@markwhitfeld Sorry for the delay! I'd love to avoid having an optionalDependency given that we want to discourage people from using the legacy API in the first place... is there a way we can say that it's a plain ArrayBuffer on the web? Or even just say that only the new compile API is supported for the web?

nex3 avatar Nov 10 '21 23:11 nex3

@markwhitfeld Are you still interested in working on this?

nex3 avatar Dec 04 '21 01:12 nex3

@nex3 Yes I am. Apologies for the radio silence, my focus switched in the beginning of November to adding support for Angular 13 in StackBlitz. It is now done, so I plan to get back on to this soon. I may reach out for some guidance on the dart side of things. I'll investigate the ArrayBuffer option.

markwhitfeld avatar Dec 13 '21 19:12 markwhitfeld

We've just launched our new JS API, so at this point it's probably a good idea to focus on that for JS compatibility. That API doesn't use anything Node-specific, so it should be easier.

nex3 avatar Dec 14 '21 05:12 nex3

We've just launched our new JS API, so at this point it's probably a good idea to focus on that for JS compatibility. That API doesn't use anything Node-specific, so it should be easier.

can you mention the API?

TheRakeshPurohit avatar Dec 14 '21 09:12 TheRakeshPurohit

It's documented on the website.

nex3 avatar Dec 14 '21 10:12 nex3

@nex3 This is great news! So, what does this mean for browser support? To be clear, does it not use Buffer or any filesystem Node APIs? If there is nothing that is node specific then would this mean that this should "just work™"?

Or is there an aspect of compatibility that may still need to be provided? Happy to hop onto a call to flesh this out if needed.

markwhitfeld avatar Dec 14 '21 12:12 markwhitfeld

The API is compatible, but the infrastructure isn't necessarily. We'll still need to factor out the internal components that load or use Node-specific libraries, provide useful error messages if the user tries to load files from a filesystem on the web, and so on.

nex3 avatar Dec 14 '21 20:12 nex3

With some selected options, the only thing that prevented me from running in service worker was SASS_PATH calculation.

// Node.js specific exports, check to see if they exist & or polyfilled

if (typeof process !== "undefined") {
  self.process = process;
} else {
  self.process = {
    env: {
      SASS_PATH: null
    }
  }
}
const compilationResult = sass.compileString(scss, {
            logger: Logger.silent,
            alertColor: false,
            sourceMap: false,
            quietDeps: true,
            functions: null,
            sourceMapIncludeSources: false,
            importers: null

        });

Pantura avatar Feb 10 '22 12:02 Pantura

Could the getEnvironmentVariable environment be made optional? https://github.com/sass/dart-sass/blob/55cb4fd5097975dc8fe11fa423ac9c1520c4f950/lib/src/io/vm.dart#L91

Edit: Well it seems my celebration was too quick. Sure the compilation worked but that was with silent logger but more trouble with error printing.

Pantura avatar Feb 10 '22 12:02 Pantura