Skip to content

Java modules

Every published jsass artifact is a Java module (JPMS), and its module name is its root package. On the class path none of this matters — everything below is for applications with their own module-info.java.

Module names

Artifact Module
io.bit3:jsass io.bit3.jsass
io.bit3:jsass.js io.bit3.jsass.js
io.bit3:jsass.javet io.bit3.jsass.javet
io.bit3:jsass.javet.v8.compiler io.bit3.jsass.javet.v8
io.bit3:jsass.javet.node.compiler io.bit3.jsass.javet.node
io.bit3:jsass.graaljs.compiler io.bit3.jsass.graaljs
io.bit3:jsass.node-modules-resolver io.bit3.jsass.js.nodemodules
io.bit3:jsass.webjar-module-resolver io.bit3.jsass.js.webjars (automatic)
io.bit3:jsass.webjar-importer io.bit3.jsass.webjar (automatic)
io.bit3:jsass.jackson2 io.bit3.jsass.json.jackson2
io.bit3:jsass.jackson3 io.bit3.jsass.json.jackson3

A typical consumer requires the engine facade and the resolver; the facade requires transitive the API (io.bit3.jsass), io.bit3.jsass.js and — for the Javet engines — io.bit3.jsass.javet:

module-info.java
module com.example.app {
  requires io.bit3.jsass.javet.v8;
  requires io.bit3.jsass.js.webjars;
  requires io.github.classgraph; // see below
}

The JSON deserializer needs no requires: jsass.jackson2 and jsass.jackson3 declare provides io.bit3.jsass.json.JsonDeserializer, and io.bit3.jsass uses it, so a provider on the module path is discovered like one on the class path.

Exported and internal packages

Only the packages documented in the Javadoc are exported. The internal packages (io.bit3.jsass.internal, io.bit3.jsass.js.internal, io.bit3.jsass.javet.internal and io.bit3.jsass.graaljs.internal) are either qualified exports to jsass's own modules or not exported at all — on the module path nothing else can reach them, and they are not API either way.

Engine types are fenced in, too:

  • Javet types appear in exactly one package, io.bit3.jsass.javet.spi, reached through javet(JavetOptions) on the Javet compiler builders.
  • GraalVM types appear only in io.bit3.jsass.graaljs.spi, reached through graaljs(GraalJsOptions) on the GraalJS builder.

Automatic modules

The two WebJar artifacts, jsass.webjar-module-resolver and jsass.webjar-importer, are automatic modules: an Automatic-Module-Name manifest entry, no descriptor. Their dependency webjars-locator-core carries no module name, so it stays on the class path — which a named module cannot requires. They get real descriptors once upstream ships a module name. Each has exactly one package, so nothing leaks in the meantime.

requires io.github.classgraph

Module-path consumers of either WebJar artifact also need requires io.github.classgraph. webjars-locator-core stays on the class path, but its dependency classgraph ships a real module descriptor and therefore lands on the module path — where nothing requires it. An application launched with -m never resolves it, and the locator dies with NoClassDefFoundError: io/github/classgraph/ClassGraph.

Add requires io.github.classgraph; to your module-info.java (plus io.github.classgraph:classgraph as a direct dependency), or start the JVM with --add-modules io.github.classgraph.

Javet native jars

Javet's native jars need a legal module name — on every Gradle version, with Maven, and with plain javac. javet-v8-linux-x86_64-6.0.2.jar and javet-node-linux-x86_64-6.0.2.jar declare Automatic-Module-Name: com.caoccao.javet.v8-linux-x86_64 (respectively …node-linux-x86_64), and v8-linux-x86_64 is not a Java identifier. As soon as such a jar reaches a module path, the JVM refuses to build the boot layer:

java.lang.module.FindException: Unable to derive module descriptor for javet-v8-linux-x86_64-6.0.2.jar
Caused by: Automatic-Module-Name: com.caoccao.javet.v8-linux-x86_64:
           Invalid module name: 'v8-linux-x86_64' is not a Java identifier

In Gradle, rename them with org.gradlex.extra-java-module-info — one automaticModule line per native jar you ship:

build.gradle.kts
plugins {
  id("org.gradlex.extra-java-module-info") version "1.14.2"
}

extraJavaModuleInfo {
  automaticModule("com.caoccao.javet:javet-v8-linux-x86_64", "com.caoccao.javet.v8.linux.x86_64")
  automaticModule("com.caoccao.javet:javet-node-linux-x86_64", "com.caoccao.javet.node.linux.x86_64")
  // The WebJars have no derivable module name; leave them untransformed on the class path,
  // which automatic modules read.
  failOnMissingModuleInfo.set(false)
}

Outside Gradle, keep the native jars off --module-path (they contain no classes a module needs, only the native library) or repackage them with a legal name.

Gradle below 9.7

Only on Gradle below 9.7: one more declaration, for the main javet jar. Those versions read jar manifests with JarInputStream, and javet-6.0.2.jar stores META-INF/MANIFEST.MF as its last zip entry, so Gradle never sees Automatic-Module-Name: com.caoccao.javet and puts Javet on the class path. Since io.bit3.jsass.javet declares requires transitive com.caoccao.javet, your own javac then fails with module not found: com.caoccao.javet. Add

automaticModule("com.caoccao.javet:javet", "com.caoccao.javet")

to the extraJavaModuleInfo block above. Gradle 9.7 and newer read the manifest with JarFile and need nothing.

Plain javac / java --module-path, Maven and jlink were never affected by the manifest problem: the JDK's own ModuleFinder opens the jar with random access and finds the manifest wherever it sits.

GraalJS

GraalJS on the module path needs no extra declarations. The GraalVM jars (polyglot, truffle-api, js-language, …) carry usable module names, so no extraJavaModuleInfo rule and no --enable-native-access flag are needed. Add requires io.bit3.jsass.graaljs; — it already requires transitive org.graalvm.polyglot — and add org.graalvm.polyglot:js-community (or js) as a runtime dependency; its jars land on the module path. The interpreter-only warning still appears on non-GraalVM JDKs unless you disable it.

A GraalJS consumer that reads dart-sass from WebJars still needs requires io.github.classgraph.

A complete consumer

examples/jpms is a runnable module-path consumer: its own module-info.java, the extraJavaModuleInfo rename for the V8 native jar and the classgraph requires. The unpublished jsass.jpms-test project additionally checks, on every build, that the facades are named modules, that the internal packages are not reachable, and that compiles work on V8, Node and GraalJS from the module path.