In a browser
Compiling Sass client side, including a whole design system, with the WebAssembly build.
The compiler builds to wasm32-unknown-unknown and exposes a JavaScript API
shaped after dart-sass's. The demo compiles Bulma and USWDS from
source in the page, with nothing sent anywhere.
Install the package with npm install @zoosky/accent-sass, or build it
yourself -- see Install. The examples
import it by name, which needs a bundler or an import map; with a local build
and neither, import ./pkg/index.js instead.
Compile a single stylesheet
import init, { compileString } from "@zoosky/accent-sass"; await init(); const { css } = compileString("a { b: calc(1rem - 2px); }");
Compile something with imports
There is no filesystem in a browser, so you supply one. files is a map of
path to source, and the compiler resolves @use, @forward and @import
against it:
const files = { "theme/_colors.scss": "$brand: #bada55 !default;", "theme/_index.scss": '@forward "colors";', }; const { css, loadedUrls } = compileString( '@use "theme" with ($brand: rebeccapurple);\na { color: theme.$brand; }', { files }, );
loadedUrls reports the files the compile read. It counts reads rather than
distinct files, so a stylesheet consulted twice appears twice.
Warning
Every file must be in files before you call. Reads are synchronous: the
compiler asks for a file and gets bytes back, with nothing to await. An
importer cannot fetch, cannot await, and cannot reach the File System
Access API. Load your theme's stylesheets into the map first, then compile.
Compile a whole framework
A framework is just a large files map plus a load path. This is what the demo
does with USWDS -- 605 stylesheets, about 800 KiB of Sass, compiled to roughly
33,700 lines of CSS in a few seconds:
const files = await fetch("uswds.json").then((r) => r.json()); const { css } = compileString( '@use "uswds-core" with ($theme-color-primary: "red-50v");\n@forward "uswds";\n', { files, loadPaths: ["uswds"] }, );
Configuring a framework through @use ... with (...) recompiles it from
source, so you get the stylesheet the framework would have produced rather than
overrides layered on top.
Note
Inside @use ... with (...) a bare comma separates arguments. A list value
needs parentheses: $family: ("Inter", system-ui, sans-serif). Quoting the
whole stack instead parses, but produces one font name that matches nothing.
Keep the page responsive
USWDS takes a few seconds. Run the compiler in a worker so typing and scrolling are not blocked:
// worker.js import init, { compileString } from "@zoosky/accent-sass"; const ready = init(); self.onmessage = async ({ data }) => { await ready; try { const result = compileString(data.source, { files: data.files }); self.postMessage({ ok: true, css: result.css }); } catch (error) { self.postMessage({ ok: false, message: error.message, formatted: error.formatted }); } };
A module worker is required, because the package is an ES module.
Show errors where they happened
A failed compile throws a real Error, so instanceof Error holds. Its
message is the Sass message alone, and it carries the formatted block plus
the position an editor needs to underline:
try { compileString("a { color: ; }"); } catch (error) { console.error(error.message); // Expected expression. console.error(error.formatted); // ...with the source line and a caret console.error(error.line, error.column); // 1-based }
Capture warnings
compileString(source, { files, logger: (event) => console.warn(`${event.type} ${event.file}:${event.line}`, event.message), });
Pass quiet: true instead to silence them. The JavaScript
API lists every option.
Serving it
The package is an ES module and init() fetches the .wasm, so a browser
refuses both over file:. Serve the directory over HTTP.