Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support running in a browser #25

Closed
matanlurey opened this issue Nov 5, 2016 · 44 comments
Closed

Support running in a browser #25

matanlurey opened this issue Nov 5, 2016 · 44 comments
Labels
enhancement help wanted JavaScript Issues particular to the Node.js distribution

Comments

@matanlurey
Copy link
Contributor

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

@nex3
Copy link
Contributor

nex3 commented Nov 7, 2016

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.

@matanlurey matanlurey changed the title Possible to avoid a dependency on dart:io? Support running in a browser Nov 7, 2016
@matanlurey
Copy link
Contributor Author

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

@nex3
Copy link
Contributor

nex3 commented Nov 7, 2016

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 nex3 added the JavaScript Issues particular to the Node.js distribution label Nov 8, 2016
@HopperMCS
Copy link

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...

@yatsyk
Copy link

yatsyk commented Mar 26, 2018

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.

@nex3
Copy link
Contributor

nex3 commented Mar 26, 2018

@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.

@jathak
Copy link
Member

jathak commented Oct 30, 2018

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?

@loilo
Copy link

loilo commented Oct 30, 2018

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.

@nex3
Copy link
Contributor

nex3 commented Oct 31, 2018

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.

@TheComputerM
Copy link

Has in-browser use been added?

@akmandev
Copy link

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

@nex3
Copy link
Contributor

nex3 commented Oct 28, 2020

See #25 (comment) for an overview of how to get started.

@initplatform
Copy link

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.

@TheRakeshPurohit
Copy link

TheRakeshPurohit commented Feb 11, 2021

./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

@TheComputerM

This comment has been minimized.

@Pantura
Copy link

Pantura commented Feb 10, 2022

Could the getEnvironmentVariable environment be made optional?

String? getEnvironmentVariable(String name) => io.Platform.environment[name];

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
Copy link

Pantura commented Feb 10, 2022

Some advances. Created an issue to node_preamble mbullington/node_preamble.dart#28 to fix an issue with wrapped global object that breaks access to self/window.location.

@markwhitfeld
Copy link

@Pantura I have been trying to get some official support for this into Dart-SASS.
You have stumbled across the exact preamble that we had to hot patch for StackBlitz (one of the few other things we had to do). Maybe we can share notes and knock out what needs to be done to get this over the line. (You can DM me on twitter if you would like to set something up). At the moment I am blocked by my lack of Dart knowledge.

@curran
Copy link

curran commented Mar 15, 2022

I'm interested in taking a stab at this. Does anyone have a WIP repo/PR to share that gets half way there?

I've done a browser build for ShareDB that has worked out well, maybe some techniques from there can be leveraged https://github.com/vizhub-core/sharedb-client-browser

Notable tools I'm thinking of trying:

  • Rollup
  • @rollup/plugin-node-resolve
  • @rollup/plugin-commonjs
  • rollup-plugin-polyfill-node if any Node requires remain

@jimmywarting
Copy link

jimmywarting commented Apr 15, 2022

Just tried the custom file importer but it's badly documented with very few examples.

I tried running this in the browser, to see if any console log would appear

var {default: sass} = await import('https://jspm.dev/sass')
sass.compile('style.scss', {
  // An importer for URLs like `bgcolor:orange` that generates a
  // stylesheet with the given background color.
  importers: [{
    findFileUrl(url) {
      console.log(url)
    },
    canonicalize(url) {
      console.log(url)
    },
    load(canonicalUrl) {
      console.log(url)
    }
  }]
})

there where 0 logs with one error that I was faced with:

sass.dart.js:18277 Uncaught TypeError: Cannot read properties of undefined (reading 'isTTY')
    at StaticClosure.compile0 (sass.dart.js:18276:65)
    at Object.Primitives_applyFunction (sass.dart.js:1313:54)
    at Object.Function_apply (sass.dart.js:5901:18)
    at _callDartFunctionFast (sass.dart.js:24360:18)
    at Object.sass.compile (sass.dart.js:24334:20)
    at <anonymous>:2:6

that corsponds to this line: color = color0 == null ? J.$eq$(self.process.stdout.isTTY, true) : color0,

Guess it assumes that stuff is in a NodeJS env with a global process...

Would be nice if things could get less dependent on NodeJS modules to also work in browser and also in Deno

@curran
Copy link

curran commented Apr 18, 2022

Is there a way to stub out self.process.stdout.isTTY and other things like that as part of the build process?

Similar to what this does https://www.npmjs.com/package/rollup-plugin-polyfill-node

@jimmywarting
Copy link

Woop woop! 🎉
Ready to try importers out when next version is released. maybe i can start using this then.

@curran
Copy link

curran commented May 20, 2023

This is exciting! How can we run it in a browser? Is there a working reference example to study? Thanks!

@jerivas
Copy link
Contributor

jerivas commented May 21, 2023

See: https://github.com/sass/dart-sass#dart-sass-in-the-browser. TLDR: you only need to load the sass module and its dependencies. Then you can call sass.compileString() directly

@jimmywarting
Copy link

I want to use importers instead of compileString as i wanna be able to resolve dependencies dynamically

@jerivas
Copy link
Contributor

jerivas commented May 22, 2023

You can pass your own importers as part of the settings object when calling compileString and compileStringAsync. Here's an example that treats imports as regular URLs and fetches them:

function canonicalize(url) {
  return new URL(url, window.location.toString())
}
async function load(canonicalUrl) {
  console.log(`Importing ${canonicalUrl} (async)`)
  const response = await fetch(canonicalUrl)
  if (!response.ok) throw new Error(`Failed to fetch ${canonicalUrl}: ${response.status} (${response.statusText})`);
  const contents = await response.text()
  return {
    contents,
    syntax: canonicalUrl.pathname.endsWith('.sass') ? 'indented' : 'scss'
  }
}
const importers = [{ canonicalize, load }]
const compiled = await sass.compileStringAsync('...', { importers })

And the sync version

function canonicalize(url) {
  return new URL(url, window.location.toString())
}
function load(canonicalUrl) {
  console.log(`Importing ${canonicalUrl} (sync)`)
  const request = new XMLHttpRequest();
  request.onreadystatechange = () => {
    if (request.readyState === 4 && request.status !== 200)
      throw new Error(`Failed to fetch ${canonicalUrl}: ${request.status} (${request.statusText})`);
  };
  request.open("GET", canonicalUrl, false);
  request.send();
  return {
    contents: request.responseText,
    syntax: canonicalUrl.pathname.endsWith('.sass') ? 'indented' : 'scss'
  };
}
const importers = [{ canonicalize, load }]
const compiled = sass.compileString('...', { importers })

@jimmywarting
Copy link

jimmywarting commented May 22, 2023

Thx for this async demo.
trying to play around with it by rapid prototyping in devtool. but it seems like sass really wasn't meant to be supported by any CDN

tried with some content delivers that would be able to convert cjs to esm on the fly. but everyone seems to fail

await import('https://esm.sh/sass') // fail
await import('https://cdn.jsdelivr.net/npm/sass/+esm') // fail
await import('https://unpkg.com/sass') // fail
var {default: sass} = await import('https://jspm.dev/sass')
sass.compileString('') // fail sass.dart.js:18277 Uncaught TypeError: Cannot read properties of undefined (reading 'isTTY')

Seems like it tries to load so many unnecessary stuff into the browser fs, chokidar, util, buffer, streams, readline which to me seems all pointless to have in a browser bundle

@jgerigmeyer
Copy link
Contributor

@jimmywarting I don't think the version of Dart Sass with browser support has been released yet -- can you confirm @nex3?

@jimmywarting
Copy link

i would be nice to see a official ESM release that isn't in cjs in any form

@nex3
Copy link
Contributor

nex3 commented May 22, 2023

@jgerigmeyer is correct, this will be released as part of Dart Sass 1.63.0 in a week or two. As the README indicates, it will be able to run in pure ESM mode without any bundling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement help wanted JavaScript Issues particular to the Node.js distribution
Projects
None yet
Development

No branches or pull requests