Architecture
Warning
Jsass 6 is still in the early stages of development. API breaks are to be expected!
jsass 6 is a stack of small, replaceable pieces. The interface at the top is what your code sees; everything below it can be swapped without touching a single call site.
flowchart TB
app["Your application"]
api["jsass — JsassCompiler, StringOptions, Output, SPIs"]
v8c["jsass.javet.v8.compiler"]
nodec["jsass.javet.node.compiler"]
shared["jsass.javet — interceptors, marshaller, compile task"]
webjar["WebjarV8ModuleResolver"]
nodemod["NodeModulesResolver"]
sass["dart-sass — the sass JavaScript library"]
app --> api
api --> v8c
api --> nodec
v8c --> shared
nodec --> shared
shared --> webjar
shared --> nodemod
webjar --> sass
nodemod --> sass
The compiler interface
JsassCompiler is the only type your application needs to depend on. It has three methods —
compileString, compilePath, close — and several implementations behind them.
classDiagram
class JsassCompiler
<<interface>> JsassCompiler
JsassCompiler : +compileString(String source, StringOptions options) CompletableFuture~Output~
JsassCompiler : +compilePath(Path path, StringOptions options) CompletableFuture~Output~
JsassCompiler : +close() void
class Output
Output : String css
Output : String sourceMap
Output : List~URI~ loadedUrls
class JavetV8JsassCompiler
JsassCompiler <|-- JavetV8JsassCompiler
class JavetNodeJsassCompiler
JsassCompiler <|-- JavetNodeJsassCompiler
class EmbeddedSassJsassCompiler
JsassCompiler <|-- EmbeddedSassJsassCompiler
What happens during a compile
sequenceDiagram
participant App as Your code
participant C as JsassCompiler
participant R as JS runtime
participant S as dart-sass
App->>C: compileString(source, options)
C-->>App: a pending future
C->>R: acquire a runtime (fresh, or from the engine pool)
R->>R: resolve the sass module via the module resolver
C->>R: marshal options, bind function / importer / logger interceptors
C->>S: compileString
S-->>R: a module is used
R-->>C: importer callback
C-->>S: stylesheet contents
S-->>C: css + source map + loaded urls
C-->>App: complete the future
The compile itself runs on the compiler's executor, guarded by a timeout. The interceptors are the
bridge in the other direction: when dart-sass calls a custom function, an importer or the logger,
it is calling into Java, and SassValueMarshaller translates the values at that boundary.
Backends
JavetV8JsassCompiler
The JavetV8JsassCompiler backend uses Javet in V8 mode to run dart-sass. Modules are
ECMAScript modules resolved from the classpath, so everything ships inside JARs. A URL polyfill
is installed into each runtime because bare V8 has no WHATWG URL implementation.
JavetNodeJsassCompiler
The JavetNodeJsassCompiler backend uses Javet in Node mode. The Node API surface is available,
modules come from a node_modules/ directory, and the result can be post-processed with PostCSS
and Autoprefixer.
EmbeddedSassJsassCompiler
Info
Conception phase!
The EmbeddedSassJsassCompiler backend would use the
Embedded Sass Protocol to
delegate the compile to an external process. That mode has the smallest in-JVM footprint, at the
price of requiring an embedded Sass service to be present.
More backends are being planned
Design decisions worth knowing
- jsass never bundles dart-sass. The Sass version is a dependency of your build, so upgrading Sass does not wait for a jsass release.
- Load paths are resolved in Java, not handed to dart-sass, because dart-sass's own file lookup needs an API that only exists in the Node runtime. Load paths therefore work identically on both engines.
- Every compile is bounded. A runaway stylesheet cannot pin a thread forever; the effective timeout defaults to 30 seconds.
- Error messages are sanitized by default.
getMessage()never leaks absolute filesystem paths or engine internals — the structured getters carry the full diagnostics. - Nothing is closed that jsass does not own. An injected executor, a caller-supplied engine
pool and Javet's
V8Hostsingleton all surviveclose().