Skip to content

Getting started

Warning

jsass 6 is still in the early stages of development. API breaks are to be expected!

Requirements

  • Java 21 or newer. jsass 6 is compiled to Java 21 bytecode and is tested on 21 and 25.
  • A JavaScript engine: the Javet native library for your platform — see compatibility.
  • A copy of dart-sass: the sass WebJar (V8) or a node_modules/sass directory (Node). jsass never downloads it for you.

1. Add the repository

6.0.0-alpha.1 is not on Maven Central. Pre-releases live in this project's GitLab package registry, which is world-readable — no token, no login.

build.gradle.kts
repositories {
    mavenCentral()
    maven {
        name = "jsass"
        url = uri("https://gitlab.com/api/v4/projects/11056061/packages/maven")
    }
}
pom.xml
<repositories>
  <repository>
    <id>jsass</id>
    <url>https://gitlab.com/api/v4/projects/11056061/packages/maven</url>
  </repository>
</repositories>

Stable 5.x releases remain on Maven Central; 6.x will be published there again once the API settles.

2. Pick your three pieces

Every jsass 6 setup answers three questions. The defaults below are the V8 route, which needs no Node.js installation and no node_modules directory.

Decision V8 route Node route
Which engine? jsass.javet.v8.compiler jsass.javet.node.compiler
Where does Sass live? jsass.javet.webjar-module-resolver jsass.javet.node-modules-resolver
Who parses package.json? jsass.jackson2 or jsass.jackson3 jsass.jackson2 or jsass.jackson3

The engines page compares the two routes; the JSON SPI page explains why a JSON parser is part of the picture at all.

3. Add the dependencies

build.gradle.kts
dependencies {
    implementation("io.bit3:jsass:6.0.0-alpha.1")
    implementation("io.bit3:jsass.javet.v8.compiler:6.0.0-alpha.1")
    implementation("io.bit3:jsass.javet.webjar-module-resolver:6.0.0-alpha.1")
    implementation("io.bit3:jsass.jackson2:6.0.0-alpha.1")

    // dart-sass itself, and the native engine for your platform
    runtimeOnly("org.webjars.npm:sass:1.86.3")
    runtimeOnly("com.caoccao.javet:javet-v8-linux-x86_64:5.0.5")

    // any SLF4J provider
    runtimeOnly("org.slf4j:slf4j-simple:2.0.17")
}
pom.xml
<dependencies>
  <dependency>
    <groupId>io.bit3</groupId>
    <artifactId>jsass</artifactId>
    <version>6.0.0-alpha.1</version>
  </dependency>
  <dependency>
    <groupId>io.bit3</groupId>
    <artifactId>jsass.javet.v8.compiler</artifactId>
    <version>6.0.0-alpha.1</version>
  </dependency>
  <dependency>
    <groupId>io.bit3</groupId>
    <artifactId>jsass.javet.webjar-module-resolver</artifactId>
    <version>6.0.0-alpha.1</version>
  </dependency>
  <dependency>
    <groupId>io.bit3</groupId>
    <artifactId>jsass.jackson2</artifactId>
    <version>6.0.0-alpha.1</version>
  </dependency>

  <!-- dart-sass itself, and the native engine for your platform -->
  <dependency>
    <groupId>org.webjars.npm</groupId>
    <artifactId>sass</artifactId>
    <version>1.86.3</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>com.caoccao.javet</groupId>
    <artifactId>javet-v8-linux-x86_64</artifactId>
    <version>5.0.5</version>
    <scope>runtime</scope>
  </dependency>
</dependencies>

jsass.javet.v8.compiler pulls in jsass, jsass.javet and com.caoccao.javet:javet transitively, and the WebJar resolver adds org.webjars:webjars-locator-core. The native engine artifact is the one you must name yourself — it is platform-specific, so no POM can choose it for you.

Cross-platform builds

Declare every native you ship for. The artifacts do not conflict; Javet loads the one that matches the running OS and architecture. See compatibility for the list.

4. Compile something

Compile an inline SCSS string
import io.bit3.jsass.StringOptions;
import io.bit3.jsass.javet.v8.JavetV8JsassCompiler;
import io.bit3.jsass.javet.webjars.WebjarV8ModuleResolver;
import io.bit3.jsass.json.jackson2.Jackson2JsonDeserializer;
import java.net.URI;
import java.util.concurrent.TimeUnit;

var moduleResolver = WebjarV8ModuleResolver.builder()
    .jsonDeserializer(new Jackson2JsonDeserializer())
    .build();

try (var compiler = JavetV8JsassCompiler.builder()
    .moduleResolver(moduleResolver)
    .build()) {

  var options = StringOptions.builder()
      .url(URI.create("virtual:root.scss"))
      .sourceMap(false)
      .build();

  var output = compiler
      .compileString("$c: #36f; .button { color: $c; }", options)
      .get(30, TimeUnit.SECONDS);

  System.out.println(output.getCss());
}
Output
.button {
  color: #36f;
}

Compiling a file from disk is the same call with a Path:

var output = compiler.compilePath(Path.of("src/main/scss/app.scss"), options)
    .get(30, TimeUnit.SECONDS);

The compiler is a singleton, not a helper

Create one compiler and keep it for the lifetime of your application — a Spring bean, a static field, whatever fits. It owns an executor, the module resolver and its caches, and (if you configure one) the engine pool that makes repeated compiles cheap.

Spring Boot
@Bean
public JsassCompiler jsassCompiler() {
  var moduleResolver = WebjarV8ModuleResolver.builder()
      .jsonDeserializer(new Jackson2JsonDeserializer())
      .build();

  return JavetV8JsassCompiler.builder().moduleResolver(moduleResolver).build();
}

A compiler is safe to call from multiple threads: each compile runs on the compiler's executor. close() stops accepting new compiles, waits for the in-flight ones and releases the compiler-owned executor. It is idempotent, and after it every compileString / compilePath returns a future that has already failed with IllegalStateException.

Compiling a lot?

Out of the box every compile gets a fresh JavaScript runtime, which means dart-sass is evaluated again each time. An engine pool reuses runtimes — and with them the already-compiled Sass modules — and is the single biggest win for a service that compiles continuously.

Next steps

  • Options — source maps, load paths, output style, deprecation handling.
  • Custom functions — call back into Java from SCSS.
  • Importers — serve stylesheets from a database, a WebJar, or thin air.
  • Error handling — what a failed future carries.
  • Examples — runnable projects in the repository.