- 適用於 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 可以執行 *不* 依賴 Node.js 內建模組(如 'fs'
、'events'
或 'http'
)或 Node.js 特定函數(如 setTimeout()
或 setInterval()
)的 JavaScript 應用程式和模組。另一方面,依賴此類 Node.js 內建模組的模組無法在 GraalVM 多語環境 Context
中載入。
支援的 NPM 套件可以使用以下方法之一在 JavaScript Context
中使用
- 使用套件捆綁器。例如,將多個 NPM 套件合併到單個 JavaScript Source 檔案中。
- 在本地檔案系統上使用 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 模組命名空間匯出的物件公開給多語環境 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 模組。可以使用 檔案系統來自訂 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 ("https://#/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 'https://#/foo';" +
"const foo = new Foo();" +
"console.log(foo.square(42));";
cx.eval(Source.newBuilder("js", src, "test.mjs").build());
在此簡單範例中,當應用程式嘗試導入 https://#/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()
依賴於 檔案系統,因此需要在建立環境時使用 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');