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 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 throughjavet(JavetOptions)on the Javet compiler builders. - GraalVM types appear only in
io.bit3.jsass.graaljs.spi, reached throughgraaljs(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:
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
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.