- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (早期アクセス)
- GraalVM for JDK 21
- GraalVM for JDK 17
- アーカイブ
- 開発ビルド
GraalJSにおけるJavaScriptモジュールとパッケージの使用
GraalJSは最新のECMAScript標準と互換性があり、さまざまなJavaベースの埋め込みシナリオで実行できます。埋め込みに応じて、JavaScriptパッケージとモジュールは異なる方法で使用される場合があります。
Context
APIによるJava埋め込み #
Javaアプリケーションに埋め込まれた場合(Context
APIを使用)、GraalJSは、'fs'
、'events'
、または'http'
などのNode.js組み込みモジュールや、setTimeout()
やsetInterval()
などのNode.js固有の関数に依存しないJavaScriptアプリケーションとモジュールを実行できます。一方、このようなNode.js組み込みに依存するモジュールは、GraalVMポリグロットContext
にロードできません。
サポートされているNPMパッケージは、次のいずれかの方法を使用してJavaScript Context
で使用できます。
- パッケージバンドラーの使用。たとえば、複数のNPMパッケージを単一のJavaScript Sourceファイルに結合する場合。
- ローカルファイルシステムのECMAScript(ES)モジュールの使用。オプションで、カスタムTruffle FileSystemを使用して、ファイルの解決方法を構成できます。
デフォルトでは、Java Context
はCommonJSのrequire()
関数を使用してモジュールをロードしません。これは、require()
がNode.js組み込み関数であり、ECMAScript仕様の一部ではないためです。以下に示すように、js.commonjs-require
オプションを使用して、CommonJSモジュールの実験的なサポートを有効にできます。
ECMAScriptモジュール(ESM) #
GraalJSは、import
ステートメント、import()
を使用した動的モジュールインポート、およびトップレベルのawait
などの高度な機能を含む、完全なESモジュール仕様をサポートしています。
ECMAScriptモジュールは、モジュールソースを評価するだけでContext
にロードできます。GraalJSは、ファイル拡張子に基づいてECMAScriptモジュールをロードします。したがって、すべてのECMAScriptモジュールには、ファイル名拡張子.mjsが必要です。または、モジュールSourceのMIMEタイプは"application/javascript+module"
である必要があります。
例として、次の単純なESモジュールを含むfoo.mjsというファイルがあると仮定しましょう。
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拡張子があることに注意してください。また、IOアクセスを有効にするためにallowIO()
オプションが提供されていることにも注意してください。ESモジュールの使用例の詳細については、こちらを参照してください。
モジュール名前空間のエクスポート
--js.esm-eval-returns-exports
オプション(デフォルトではfalse)を使用すると、ESモジュール名前空間のエクスポートオブジェクトをポリグロットContext
に公開できます。これは、ESモジュールがJavaから直接使用される場合に便利です。
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 FileSystem #
デフォルトでは、GraalJSはポリグロットContext
の組み込みFileSystemを使用して、ESモジュールをロードおよび解決します。FileSystemを使用して、ESモジュールのロードプロセスをカスタマイズできます。たとえば、カスタムFileSystemを使用して、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モジュールをロードするために、カスタムFileSystemが使用されます。
ESモジュールをロードするためのカスタムTruffle FileSystemの完全な例は、こちらにあります。
CommonJSモジュール #
デフォルトでは、Context
APIはCommonJSモジュールをサポートしておらず、組み込みのrequire()
関数はありません。JavaのContext
からロードおよび使用するには、CommonJSモジュールを自己完結型のJavaScriptソースファイルにバンドルする必要があります。これは、Parcel、Browserify、Webpackなどの多くの一般的なオープンソースバンドルツールを使用して実現できます。以下に示すように、js.commonjs-require
オプションを使用して、CommonJSモジュールの実験的なサポートを有効にできます。
Context
APIでのCommonJS NPMモジュールの実験的なサポート
js.commonjs-require
オプションは、JavaScript Context
でNPM互換のCommonJSモジュールをロードするために使用できる組み込みのrequire()
関数を提供します。現在、これは実験的な機能であり、本番環境での使用には適していません。
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/ディレクトリを含むディレクトリにすることができます。"js.commonjs-core-modules-replacements"
を使用して指定された組み込みの代替を含め、すべてのNPMモジュールは、そのディレクトリを基準に解決されます。
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()
関数は、js.commonjs-require-cwd
を介して指定されたディレクトリから開始して、デフォルトのNode.jsのパッケージ解決プロトコルに従ってパッケージを解決します。オプションでディレクトリが提供されない場合、アプリケーションの現在の作業ディレクトリが使用されます。
Node.jsコアモジュールのモック
一部のJavaScriptアプリケーションまたはNPMモジュールでは、Node.jsの組み込みモジュール(たとえば、'fs'
や'buffer'
)で利用可能な機能が必要になる場合があります。このようなモジュールは、Context
APIでは利用できません。ありがたいことに、Node.jsコミュニティは、多くのNode.jsコアモジュール(たとえば、ブラウザー用の‘buffer’モジュール)の高品質なJavaScript実装を開発しています。このような代替モジュール実装は、次の方法で、js.commonjs-core-modules-replacements
オプションを使用して、JavaScript Context
に公開できます。
options.put("js.commonjs-core-modules-replacements", "buffer:my-buffer-implementation");
コードが示唆するように、このオプションは、アプリケーションがrequire('buffer')
を使用してNode.js buffer
組み込みモジュールをロードしようとしたときに、GraalJSにmy-buffer-implementation
というモジュールをロードするように指示します。
グローバルシンボルの事前初期化
NPMモジュールまたはJavaScriptアプリケーションでは、特定のグローバルプロパティがグローバルスコープで定義されていることを期待する場合があります。たとえば、アプリケーションまたはモジュールでは、Buffer
グローバルシンボルがJavaScriptグローバルオブジェクトで定義されていることを期待する場合があります。この目的のために、アプリケーションユーザーコードは、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');