OpenVessel

ビルドと保守

このページの見出し
  1. 要るもの
  2. npm のスクリプト
  3. scripts/build.mjs
  4. Node のデータ
  5. 互換パッケージ
  6. 型の宣言
  7. 手元のサーバー: serve.mjs
  8. Node.js の新しい版に追従する
  9. リリース
  10. リポジトリの並び
  11. 関連するページ

OpenVessel をソースからビルドし、新しく保つ方法です。要る道具、npm のスクリプト、ビルドのスクリプトがそれぞれ何を読んで何を書くか、Node.js の新しい版への追従、リリースの出し方を扱います。試験の動かし方は 試験 にあります。

要るもの

道具何に要るか
Node.js と npmすべて。単体の試験の束は node22 向けにビルドする。記録と基準のスクリプトは npx -y node@<固定した版> を取ってくるので、npm のレジストリへの通信が要る
Docker(linux/amd64 のイメージを動かせること)scripts/gen-node-data.mjs と scripts/node-upgrade-check.mjs。Node の版ごとに 1 回、vendor/node/ と src/node/data/ を作る
curlgen-node-data.mjs(Node の LICENSE を取る)、zlib の WebAssembly のビルド
Rust(cargo)と rustup target add wasm32-wasip1vendor/compat/pagefind/ が無いときの、Pagefind の互換パッケージのビルド
wasm32 向けの LLVM の clang と wasm-ldbrotli/zstd の WebAssembly を作り直すときだけ(src/nodejs/bindings/zlib/wasm/build.mjs)
Google Chrome端から端までの試験・Node の試験・コーパス(playwright-core から channel: 'chrome' で動かす)
gittest/node-compat/fetch.mjs(nodejs/node の一部だけの clone)
ログイン済みの GitHub CLI ghcorpus/collect.mjs、リリースの gh release create

取ってきたばかりのリポジトリは、次のようにビルドします。

npm install
node scripts/gen-node-data.mjs   # 1 回だけ(Docker)。vendor/node/ と src/node/data/
npm run build                    # dist/
npm run serve                    # ホスト :5170、ランタイム :5171、プレビュー :5172

npm のスクリプト

スクリプトコマンドすること
buildnode scripts/build.mjsdist/ へ全部ビルドする
devnode scripts/build.mjs --watch & node scripts/serve.mjs変更のたびにビルドし直して配る
servenode scripts/serve.mjsdist/ を 3 つのオリジンで配る
testnode scripts/test.mjs単体の試験(試験)
typechecktsc --noEmit -p .src/・test/・playground/ の型を確かめる(tsconfig.json)
build:typesnode scripts/build-types.mjsホストの API の型の宣言を dist/types/ に出す

scripts/build.mjs

esbuild で全部を束ねます。フラグ:

フラグ効き目
(なし)dist/ を消し、静的なファイルを写し、esbuild のビルドを全部 1 回ずつ実行し、そのあと nodelib.js を作り、amaro を写し、互換パッケージを作る
--watchdist/ を消さない。静的なファイルを写し、入口ごとに esbuild の watch を始める。この形では nodelib.js・amaro.js・互換パッケージは作らない(作り直さない)ので、前の全部のビルドで作ってある必要がある
--minifyすべての束を小さくする

esbuild の共通の設定: bundle・sourcemap: 'linked'・target: 'es2022'・platform: 'browser'・legalComments: 'none'・define: { global: 'globalThis' }。旧ランタイムとほかの束は、events・process・string_decoder・abort-controller を src/node/ の代わりの物に読み替え、process/・string_decoder/・cjs-module-lexer/lexer.js を小さなプラグインで解決します。移植版ランタイムの束は読み替えを使いません。

入口ソース出力形式
kernelsrc/kernel/index.tsdist/runtime/kernel.jsESM。dist/runtime/chunks/[name]-[hash].js に分割
processsrc/node/worker.tsdist/runtime/process.js(旧ランタイム)ESM。kernel と分割の塊を共有
runtimesrc/runtime/runtime.tsdist/runtime/runtime.jsIIFE
swsrc/runtime/sw.tsdist/runtime/sw.js(ランタイムの Service Worker)IIFE
node-processsrc/nodejs/boot.tsdist/runtime/node-process.js(移植版ランタイム)。./nodelib.js は外の import のままESM
ts-stripsrc/nodejs/ts-strip.tsdist/runtime/ts-strip.js(require(esm) のための sucrase)IIFE
relay・injectsrc/preview/relay.ts・src/preview/inject.tsdist/preview/__ov/relay.js・dist/preview/__ov/inject.jsIIFE
__ov-swsrc/preview/sw.tsdist/preview/__ov-sw.js(プレビューの根に置く。Service-Worker-Allowed のヘッダー無しでオリジン全体を受け持つため)IIFE
indexsrc/host/index.tsdist/host/index.js(パッケージの main)ESM
appplayground/app.tsdist/playground/app.js(CSS は css のローダーで)ESM

写す静的なファイル: src/runtime/runtime.html → dist/runtime/runtime.html、src/preview/relay.html → dist/preview/__ov/relay.html、playground/index.html と playground/app.css → dist/playground/、node_modules/@xterm/xterm/css/xterm.css → dist/playground/xterm.css。

esbuild のビルドのあと(全部のビルドのときだけ):

手順出力
genNodeLib()(vendor/node/natives.json があるとき)dist/runtime/nodelib.js と dist/runtime/NODE-LICENSE.txt(Node の LICENSE。同梱の依存の告知も入っている)。vendor/node が無いと vendor/node missing: run node scripts/gen-node-data.mjs to build the ported Node runtime と出し、移植版ランタイムは起動できない
amaro を写すnode_modules/amaro/dist/index.js → dist/runtime/amaro.js。dist/runtime/AMARO-LICENSE.txt(amaro と swc のライセンス)
buildCompat()dist/runtime/compat/*.tgz と dist/runtime/compat/index.json

最後に built dist/ と出します。

Node のデータ

gen-node-data.mjs

node scripts/gen-node-data.mjs は、固定した版(src/node/version.ts の NODE_VERSION)の本物の Node.js から、OpenVessel が要るものを全部吸い出します。公式のイメージの中で、吸い出すスクリプトを標準入力から実行します。

docker run --rm -i --platform linux/amd64 --hostname openvessel -e HOME=/home/user \
  node:<version>-bookworm-slim node --expose-internals --no-warnings -

OpenVessel は自分を Linux x64 と名乗るので、errno・シグナル・O_* の値がカーネルと合うよう、Linux x64 のイメージを使います。スクリプトは決まった 68 個のバインディングの名前について internalBinding() を呼び(その版に無い名前は __missing と記録)、それぞれの形を記録します。internalBinding('builtins').natives とプロセスのデータも記録します。

出力中身git に入るか
vendor/node/natives.json組み込みモジュールのソースの全部(lib/ と deps/)。Node が埋め込んでいるそのままいいえ
vendor/node/lib/<id>.js同じソースをファイルにしたもの。読む・grep するためいいえ
vendor/node/LICENSENode のライセンス。raw.githubusercontent.com/nodejs/node/v<version>/LICENSE から curl で取るいいえ
src/node/data/bindings.json各バインディングの定数と API の形(関数、クラスとそのプロトタイプのメソッド、型付き配列、Map、Symbol)と uv.__errmapはい
src/node/data/process.jsonversions・release・features・config・optionValues・optionInfo(オプションと別名)・embedderOptions・envSettings・types・builtinCategories・globals(起動後のグローバルの名前 → 列挙されるか)はい

形の書き方(src/nodejs/bindings/shape.ts が読み戻す。バインディング を参照): 関数は { __fn, proto?, statics? }、型付き配列は { __typed, length, values? }(値は 64 個まで)、ほかに __num・__bigint・__symbol・__map・__set・__ref・__object・__array・__throws・__missing を使います。深く入れ子になった値は打ち切ります(__object・__array)。スクリプトを動かしたこと自体で変わったものは戻します(--expose-internals・--no-warnings・--require を元に戻し、標準入力のスクリプトのグローバル __filename・__dirname・module・exports・require・shape を globals から除く)。最後に node v<version>: <n> builtins, <m> bindings と出します。

gen-nodelib.mjs

build.mjs から呼ばれます(単独でも動き、node scripts/gen-nodelib.mjs は dist/runtime/nodelib.js を書いて { count, bytes, overrides } を出す)。vendor/node/natives.json を読み、src/nodejs/overrides/<id>.js があるものはそれに置き換え(Node.js の層 を参照)、internal/deps/v8/tools/*(V8 のプロファイラーの道具)を除き、1 つの ES モジュールを書きます。

// Generated by scripts/gen-nodelib.mjs from Node.js 22.23.4 — see vendor/node/LICENSE.
export const ids = [/* 組み込みの ID の全部。並べ替えたもの */];
export const params = { 'fs': 'exports, require, module, process, internalBinding, primordials', /* … */ };
export const sources = { 'fs': '…ソースの文字列…', /* … */ };

ソースは文字列のまま持ち、使うときに 1 つずつコンパイルします。Node.js の層 を参照してください(ids には除いた internal/deps/v8/tools/* も残りますが、params と sources にはありません)。

ほかの生成の道具

スクリプト読むもの書くものいつ
scripts/gen-builtins.mjs手元の Node の module.builtinModules と、各モジュールの export の名前src/shared/builtin-exports.json(組み込みの殻の名前つき export。旧ランタイムのためのカーネルの ESM の配信が使う)組み込みの一覧が変わったとき
scripts/vendor-node-path.mjs [lib/path.js のパス]Node の lib/path.js(既定は test/node-compat/.node/lib/path.js。ただし test/node-compat/fetch.mjs の一部だけの取り出しには入っていないので、vendor/node/lib/path.js を渡す)src/shared/node-path.ts(primordials をふつうの呼び出しにし、process.cwd()/process.env と glob を OpenVessel につなぐ)Node を上げて lib/path.js が変わったとき
scripts/regen-fixtures.mjs [name…]固定した版の本物の Node(npx -y node@<version>)test/unit/fixtures/*.json、src/nodejs/bindings/icu.ts の中の ICU の幅の表、src/nodejs/bindings/crypto/tables.tsNode を上げたあと(試験)
src/nodejs/bindings/zlib/wasm/build.mjsbrotli 1.1.0 と zstd 1.5.7 のリリースの tarball(sha256 を確かめる)、node-brotli.patchsrc/nodejs/bindings/zlib/brotli-wasm.ts・zstd-wasm.ts(raw deflate と base64。git に入れる)ライブラリの版かビルドのフラグを変えるときだけ。clang/wasm-ld が要る。CLANG・WASM_LD で置き換えられる。作業のディレクトリは WORK_DIR(既定は <tmp>/ov-zlib-wasm)

互換パッケージ

scripts/build-compat.mjs(build.mjs からも呼ばれる。単独では node scripts/build-compat.mjs で、compat: <名前> と出す)は、ネイティブのバイナリだけを配っている npm のパッケージの代わりを作ります。npm のインストーラーは、頼まれた名前と版のまま、ネイティブのパッケージの代わりにこれを入れます(ネイティブのモジュール を参照)。

compat/<id>/package/    パッケージ自身のファイル(package.json は作る)
compat/<id>/build.mjs   任意: 作った物を vendor/compat/<id>/ に置く(取っておく。git の外)
ID代わるもの範囲出す名前作る物
pagefind@pagefind/linux-x64^1.0.0@openvessel/pagefind-wasi 1.5.2pagefind.wasm: crates.io の pagefind 1.5.2 の crate に compat/pagefind/wasi.patch を当て、cargo build --release --target wasm32-wasip1 --no-default-features(strip・LTO・codegen unit 1)
cn-font-splitcn-font-split^7.0.0@openvessel/cn-font-split-wasi(版は元のパッケージの 7.4.3)npm のレジストリの公開版 7.4.3 と、プロジェクトの GitHub のリリースの libffi-wasm32-wasip1.wasm 7.6.8。依存の koffi と postinstall のスクリプトは外す
resvg-js@resvg/resvg-js^2.0.0@openvessel/resvg-js-wasm 2.6.2(@resvg/resvg-wasm 2.6.2 に依存)なし

パッケージごとに: vendor/compat/<id>/ に作る物が欠けていて build.mjs があれば、それを動かします。失敗すると compat <id>: not built (<理由>); <名前> will stay unavailable と出し、それ抜きで続けます。ファイルは package/ のディレクトリの下に並べ、ustar で固めます(tar -czf <id>-<version>.tgz --format ustar、COPYFILE_DISABLE=1)。index.json は代わる名前ごとに { range, file, integrity, pkg } を持ち、integrity は tarball の sha512-… です。

型の宣言

node scripts/build-types.mjs は dist/types/ を消し、npx tsc -p tsconfig.types.json(宣言だけ。src/host/index.ts とそれが import するもの)を実行し、出てきた .d.ts の中の .ts の import を .js に書き換えます。全部のビルドは dist/ を消すので、npm run build のあとに実行します。package.json の types は dist/types/host/index.d.ts を指します。

手元のサーバー: serve.mjs

node scripts/serve.mjs は dist/ を 3 つのオリジンで、127.0.0.1 で配ります。

サイトポートディレクトリヘッダー
ホストOV_HOST_PORT、無ければ OV_PORT_BASE(既定 5170)dist/playgroundCOOP same-origin・COEP require-corp・CORP same-origin
ランタイムOV_RUNTIME_PORT、無ければ基準 + 1dist/runtimeCOOP・COEP・CORP cross-origin と CSP: default-src 'self'; script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval' blob: data:; connect-src 'self' data: blob:; worker-src 'self' blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; frame-ancestors http://localhost:<ホストのポート>。ただし通信の関所である /sw.js には付けない(connect-src 'self' を引き継いではならない)
プレビューOV_PREVIEW_PORT、無ければ基準 + 2dist/previewCOOP・COEP・CORP cross-origin・Service-Worker-Allowed: /

どの応答にも cache-control: no-cache を付けます。/ で終わるパスは index.html を返します。Content-Type は .html・.js・.mjs・.css・.map・.json・.wasm・.svg・.png(ほかは application/octet-stream)です。無いファイルは、サイトのヘッダーを付けた 404 not found です。起動するとサイトごとに 1 行(playground: http://localhost:5170/ など)出します。OV_PORT_BASE で、1 台のマシンで複数のサーバー(と試験)を動かせます。本番の配置に要るものは 配信 にあります。

Node.js の新しい版に追従する

OpenVessel が移植する Node の版は、src/node/version.ts の NODE_VERSION の 1 か所で決めています。package.json は同じ版を openvessel.node に持ちます。いまの版と履歴は NODE-VERSION.md にあります。

  1. 何が変わるかを見る。 node scripts/node-upgrade-check.mjs <新しい版> は、同じ Docker の吸い出しで新しい版を取り、固定しているデータ(vendor/node・src/node/data)と比べて、docs/node-upgrades/<いま>-to-<新しい版>.md を書きます。中身は次の区分です。
    • Builtins (lib/): 増えた・消えた・変わった組み込みと、消えた行・増えた行の数。新しい行に internalBinding( の呼び出しがある変更には (binding use changed) の印
    • Bindings: バインディングごとに、増えた・消えた・種類(function・class(メソッド)・型付き配列と長さ・Symbol・値)の変わったメンバー
    • CLI options: 増えた・消えた・既定値の変わったオプション
    • process.versions: 変わった同梱の依存の版最後に 1 行の要約を出します。版を渡さないと usage: node scripts/node-upgrade-check.mjs <version> と出して 1 で終わります。名前を変えずに引数が変わったもの(22.23 の ModuleWrap.link() など)は、この報告には出ません。
  2. src/node/version.ts の版(と package.json の openvessel.node)を変えます。
  3. データと期待値を作り直します。
    node scripts/gen-node-data.mjs     # vendor/node・src/node/data(Docker)
    node scripts/regen-fixtures.mjs    # 新しい版の本物の Node でバインディングの記録を取り直す
    node test/node-compat/fetch.mjs    # Node の試験も同じ版に
  4. 報告に出たバインディングの変化を src/nodejs/bindings/ に反映し、src/nodejs/overrides/ のファイルが新しい lib/ に合っているか確かめます。
  5. ビルドし直して計ります。
    node scripts/build.mjs
    node test/node-compat/run.mjs --baseline
    node test/node-compat/run.mjs --port && node test/node-compat/run.mjs --port --report
    node corpus/run.mjs --port
    数字が前の版より下がっていないことを確かめます(試験)。
  6. docs/NODE-VERSION.md のいまの版と履歴を書き換えます。

前回の追従の報告は node-upgrades/22.20.0-to-22.23.4.md です。

リリース

手順の全部は RELEASING.md(英語)にあります。要点:

  • 版は OpenVessel 自身の semver です。移植している Node の版は、リリースの題(OpenVessel <version> — Node.js <node>)・リリースノート・package.json の openvessel.node に出します。直しや Node のパッチの版への追従はパッチ、機能の追加や Node のマイナーの版への追従はマイナー、ホストの API や埋め込みの条件を壊すもの・Node のメジャーの版はメジャーです。
  • リリースはメンテナーの手元で、main からビルドします(ビルドに vendor/ と取っておいた互換パッケージが要るため)。package.json と package-lock.json の版を変え、openvessel.node を確かめ、CHANGELOG.md に節を足し、npm run build && node scripts/build-types.mjs && npm run typecheck && npm test。ランタイムが変わったなら Node の試験とコーパスを流し直して、README.md と docs/compatibility.md の数字を直します。npm pack、Release <version> でコミットし、v<version> のタグを付け、両方を push します。gh release create v<version> openvessel-<version>.tgz --title "OpenVessel <version> — Node.js <node>"(リリース候補は --prerelease)。必要なら同じ tarball を npm publish --provenance --access public します。
  • 公開するパッケージに入るのは dist/ だけです(package.json の files)。semver の対象は dist/host のホストの API とその宣言、3 つのオリジンでの埋め込みの条件です。対象外は src/、dist/runtime と dist/preview の中のファイル、互換パッケージの中身です。

リポジトリの並び

パス中身git に入るか
src/host/ホストの API(OpenVessel.boot など) — ホスト APIはい
src/kernel/カーネル: VFS・プロセス・パイプ・TTY・ソケット・sh・coreutils・npm・ESM の配信・仮想ポート・外への HTTPはい
src/nodejs/移植版の Node.js ランタイム: boot.ts・loop.ts・options.ts・env.ts・fetch.ts・typescript.ts・bindings/・overrides/はい
src/node/旧ランタイム。ほかに version.ts(固定した Node の版)・data/(吸い出した Node のデータ)・移植版ランタイムも使うモジュール(sys.ts・native.ts・wasi.ts)はい
src/runtime/ランタイムの iframe のページと、その Service Workerはい
src/preview/プレビューの中継・差し込むスクリプト・Service Workerはい
src/shared/カーネル・ランタイム・ホストで共有するコード(パス・解決・通信の方針・errno・同期の通り道)はい
playground/ホストのポートで配る開発用のプレイグラウンドはい
compat/互換パッケージのソースはい
scripts/ビルド・データ・配信・試験のスクリプトはい
test/unit/・test/fixtures-gen/単体の試験と、記録を作るスクリプトはい
test/e2e/端から端までの試験のスクリプト(ヘッドレスの Chrome)はい
test/node-compat/Node の試験を動かす道具と limits/。.node/ と results/ は git の外一部
corpus/Astro のサイトの一覧と、それを流す道具。sites/ と実行のログは git の外一部
docs/文書。docs/node-upgrades/ に版の追従の報告はい
vendor/vendor/node/(Node のソースとライセンス)・vendor/compat/(互換パッケージの作った物)いいえ
dist/ビルドの出力いいえ

関連するページ

この文書の元は、リポジトリの docs/reference/building.ja.md です(英語版は building.md)。