- 适用于 JDK 23 的 GraalVM(最新)
- 适用于 JDK 24 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发版本
在 GraalJS 中使用 JavaScript 模块和包
GraalJS 与最新的 ECMAScript 标准兼容,可以在各种基于 Java 的嵌入式场景中运行。根据嵌入方式,JavaScript 包和模块的使用方式可能有所不同。
通过 Context
API 嵌入 Java #
当嵌入到 Java 应用程序中(使用 Context
API)时,GraalJS 可以执行 JavaScript 应用程序和模块,这些应用程序和模块不依赖于 Node.js 的内置模块(例如 'fs'
、'events'
或 'http'
)或 Node.js 特定的函数(例如 setTimeout()
或 setInterval()
)。另一方面,依赖于此类 Node.js 内置模块的模块无法在 GraalVM 多语言环境 Context
中加载。
支持的 NPM 包可以使用以下方法之一在 JavaScript Context
中使用
- 使用包捆绑器。例如,将多个 NPM 包组合成一个 JavaScript 源文件。
- 在本地文件系统上使用 ECMAScript (ES) 模块。可选地,可以使用自定义 Truffle 文件系统 来配置如何解析文件。
默认情况下,Java Context
不会使用 CommonJS require()
函数加载模块。这是因为 require()
是一个 Node.js 内置函数,不是 ECMAScript 规范的一部分。可以通过 js.commonjs-require
选项启用对 CommonJS 模块的实验性支持,如下所述。
ECMAScript 模块 (ESM) #
GraalJS 支持完整的 ES 模块规范,包括 import
语句、使用 import()
的动态模块导入以及 顶层 await
等高级功能。
只需评估模块源代码,就可以在 Context
中加载 ECMAScript 模块。GraalJS 根据其文件扩展名加载 ECMAScript 模块。因此,任何 ECMAScript 模块都应具有文件扩展名 .mjs。或者,模块 Source 应具有 MIME 类型 "application/javascript+module"
。
例如,假设您有一个名为 foo.mjs 的文件,其中包含以下简单的 ES 模块
export class Foo {
square(x) {
return x * x;
}
}
此 ES 模块可以通过以下方式在多语言环境 Context
中加载
public static void main(String[] args) throws IOException {
String src = "import {Foo} from '/path/to/foo.mjs';" +
"const foo = new Foo();" +
"console.log(foo.square(42));";
Context cx = Context.newBuilder("js")
.allowIO(true)
.build();
cx.eval(Source.newBuilder("js", src, "test.mjs").build());
}
请注意,ES 模块文件具有 .mjs 扩展名。还要注意,提供了 allowIO()
选项以启用 IO 访问。有关 ES 模块使用的更多示例,请访问 此处。
模块命名空间导出
--js.esm-eval-returns-exports
选项(默认情况下为 false)可用于将 ES 模块命名空间导出的对象公开到 Polyglot Context
。当直接从 Java 使用 ES 模块时,这可能很方便
public static void main(String[] args) throws IOException {
String code = "export const foo = 42;";
Context cx = Context.newBuilder("js")
.allowIO(true)
.option("js.esm-eval-returns-exports", "true")
.build();
Source source = Source.newBuilder("js", code)
.mimeType("application/javascript+module")
.build();
Value exports = cx.eval(source);
// now the `exports` object contains the ES module exported symbols.
System.out.println(exports.getMember("foo").toString()); // prints `42`
}
Truffle 文件系统 #
默认情况下,GraalJS 使用多语言环境 Context
的内置文件系统来加载和解析 ES 模块。可以使用 FileSystem 自定义 ES 模块加载过程。例如,可以使用自定义文件系统使用 URL 解析 ES 模块
Context cx = Context.newBuilder("js").fileSystem(new FileSystem() {
private final Path TMP = Paths.get("/some/tmp/path");
@Override
public Path parsePath(URI uri) {
// If the URL matches, return a custom (internal) Path
if ("http://localhost/foo".equals(uri.toString())) {
return TMP;
} else {
return Paths.get(uri);
}
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
if (TMP.equals(path)) {
String moduleBody = "export class Foo {" +
" square(x) {" +
" return x * x;" +
" }" +
" }";
// Return a dynamically-generated file for the ES module.
return createByteChannelFrom(moduleBody);
}
}
/* Other FileSystem methods not shown */
}).allowIO(true).build();
String src = "import {Foo} from 'http://localhost/foo';" +
"const foo = new Foo();" +
"console.log(foo.square(42));";
cx.eval(Source.newBuilder("js", src, "test.mjs").build());
在这个简单的例子中,自定义文件系统用于在应用程序尝试导入 http://localhost/foo
URL 时加载动态生成的 ES 模块。
可以在 此处找到加载 ES 模块的自定义 Truffle 文件系统的完整示例。
CommonJS 模块 #
默认情况下,Context
API 不支持 CommonJS 模块,并且没有内置的 require()
函数。为了从 Java 的 Context
中加载和使用,CommonJS 模块需要捆绑到一个独立的 JavaScript 源文件。这可以使用许多流行的开源捆绑工具(例如 Parcel、Browserify 和 Webpack)来实现。可以通过 js.commonjs-require
选项启用对 CommonJS 模块的实验性支持,如下所述。
Context
API 中 CommonJS NPM 模块的实验性支持
js.commonjs-require
选项提供了一个内置的 require()
函数,该函数可用于在 JavaScript Context
中加载与 NPM 兼容的 CommonJS 模块。目前,这只是一个实验性功能,不适合生产使用。
要启用 CommonJS 支持,可以按以下方式创建 JavaScript 上下文
Map<String, String> options = new HashMap<>();
// Enable CommonJS experimental support.
options.put("js.commonjs-require", "true");
// (optional) directory where the NPM modules to be loaded are located.
options.put("js.commonjs-require-cwd", "/path/to/root/directory");
// (optional) Node.js built-in replacements as a comma separated list.
options.put("js.commonjs-core-modules-replacements",
"buffer:buffer/," +
"path:path-browserify");
// Create context with IO support and experimental options.
Context cx = Context.newBuilder("js")
.allowExperimentalOptions(true)
.allowIO(true)
.options(options)
.build();
// Require a module
Value module = cx.eval("js", "require('some-module');");
"js.commonjs-require-cwd"
选项可用于指定 NPM 包已安装的主文件夹。例如,这可以是执行 npm install
命令的目录,或者包含您的主 node_modules/ 目录的目录。任何 NPM 模块都将相对于该目录解析,包括使用 "js.commonjs-core-modules-replacements"
指定的任何内置替换。
与 Node.js 内置 require()
函数的区别
Context
内置的 require()
函数可以加载用 JavaScript 实现的常规 NPM 模块,但不能加载本机 NPM 模块。内置的 require()
依赖于 FileSystem,因此需要在使用 allowIO
选项在上下文创建时启用 I/O 访问。内置的 require()
旨在与 Node.js 大致兼容,我们希望它能够与在浏览器中运行的任何 NPM 模块(例如,使用包捆绑器创建的模块)一起使用。
安装要通过 Context
API 使用的 NPM 模块
要从 JavaScript Context
中使用,NPM 模块需要安装到本地目录,例如,通过运行 npm install
命令。在运行时,可以使用 js.commonjs-require-cwd
选项指定 NPM 包的主安装目录。require()
内置函数根据默认的 Node.js 包解析协议 解析包,该协议从通过 js.commonjs-require-cwd
指定的目录开始。当选项没有提供目录时,将使用应用程序的当前工作目录。
Node.js 核心模块模拟
某些 JavaScript 应用程序或 NPM 模块可能需要 Node.js 内置模块(例如 'fs'
和 'buffer'
)中可用的功能。此类模块在 Context
API 中不可用。值得庆幸的是,Node.js 社区为许多 Node.js 核心模块开发了高质量的 JavaScript 实现(例如,用于浏览器的 'buffer' 模块)。可以使用 js.commonjs-core-modules-replacements
选项将此类备用模块实现公开到 JavaScript Context
,方法如下
options.put("js.commonjs-core-modules-replacements", "buffer:my-buffer-implementation");
正如代码所示,该选项指示 GraalJS 在应用程序尝试使用 require('buffer')
加载 Node.js buffer
内置模块时加载名为 my-buffer-implementation
的模块。
全局符号预初始化
NPM 模块或 JavaScript 应用程序可能期望在全局范围内定义某些全局属性。例如,应用程序或模块可能期望在 JavaScript 全局对象中定义 Buffer
全局符号。为此,应用程序用户代码可以使用 globalThis
来修补应用程序的全局范围
// define an empty object called 'process'
globalThis.process = {};
// define the 'Buffer' global symbol
globalThis.Buffer = require('some-buffer-implementation').Buffer;
// import another module that might use 'Buffer'
require('another-module');