Node.js には --require=module
と --import=module
というオプションがあります。このオプションを使うと、エントリポイントとなるプログラムよりも前に、任意のモジュールを実行できます。
例えば以下のようなコマンドを実行すると、Node.js ランタイムはまず最初に preload.cjs
を実行し、それから main.mjs
を実行できます。
node --require ./preload.cjs main.mjs
エントリポイントよりも前に、何かしらの処理を実行したい時に使うことを想定しています。
--require
と --import
の違い
--import
も --require
と同じように、モジュールをプリロードするためのオプションです。両者の違いはプリロードするモジュールの読み込み方です。
--require
は require(...);
相当、--import
は import '...';
相当のコードでモジュールを読み込みます。
# `require('./preload.cjs');` 相当 node --require ./preload.cjs main.mjs # `import './preload.mjs';` 相当 node --import ./preload.mjs main.mjs
ところで、import
文では ES module 形式だけでなく、CommonJS 形式のモジュールも読み込めます。つまり --import
で CommonJS 形式のモジュールもプリロードできるのです。
node --import ./preload.cjs main.mjs
また、Node.js v22.0.0 からは --experimental-require-module
オプションを用いることで、top-level await を含まない ES module 形式のモジュールに限って require(esm)
が可能になりました (参考1, 参考2)。従って --experimental-require-module
を併用することで、ES module 形式のモジュールを --require
で読み込めます。
node --experimental-require-module=module --require ./preload.mjs main.mjs
従って、--experimental-require-module=module
+ --require
と --import
のどちらを使っても、ほぼ変わりないです。 とはいえ、--experimental-require-module=module
+ --require
は top-level await 非対応なので、基本的には --import
を使っておくと良いのではないかと思います。
npm package を --require
/--import
で読み込む
--require
/--import
はユーザ定義のモジュールだけでなく、npm package も読み込めます。import '3rd-party-preload';
と書くように、node --import 3rd-party-preload
とするだけです。
npm i -S 3rd-party-package node --import 3rd-party-package main.mjs
複数のモジュールをプリロードする
--require
/--import
を複数個指定して、複数のモジュールをプリロードできます。プリロードされるモジュールは、左から順に実行されます。
例えば以下のようなコマンドを実行した場合、3rd-party-package-1
=> 3rd-party-package-2
=> 3rd-party-package-3
=> main.mjs
の順で実行されます。
node \ --import 3rd-party-package-1 \ --import 3rd-party-package-2 \ --import 3rd-party-package-3 \ main.mjs
--require
/--import
の主な用途
主にモジュールの読み込み方法をカスタマイズするために利用されます。
例えば ts-node
を使うと、事前のトランスパイルをせずとも *1 Node.js で直接 .ts
が読み込めるようになります。
npm install -D ts-node node --import ts-node/register main.mts
更に tsconfig-paths
と組み合わせると、tsconfig.json
の compilerOptions.paths
を Node.js でも解決できるようになります。
npm install -D ts-node tsconfig-paths node \ --import ts-node/register \ --import tsconfig-paths/register \ main.mts
僕が作った @mizdra/node-next-image-loader
を使うと、画像を base64 化しつつモジュールとして読み込むこともできます。
npm install -D @mizdra/node-next-image-loader node --import @mizdra/node-next-image-loader main.mjs
import burnAllGIFs from "./assets/burnallgifs.png"; console.log(burnAllGIFs); // { // src: 'data:image/png;base64,...', // width: 199, // height: 117, // }
また、power-assert をサポートするための @power-assert/node
というパッケージもあります (以下、公式 example より引用)。
npm install -D @power-assert/node node \ --enable-source-maps \ --import @power-assert/node \ --test \ demo.test.mjs
> node --enable-source-maps --import @power-assert/node --test demo.test.mjs ▶ power-assert demo ✖ Array#indexOf (8.774208ms) AssertionError [ERR_ASSERTION]: assert(ary.indexOf(zero) === two) | | | | | | | | | 2 | | | false | | 0 | 0 [0,1,2] 0 === 2 at TestContext.<anonymous> (/path/to/demo.test.mjs:9:5) at Test.runInAsyncScope (node:async_hooks:206:9) at Test.run (node:internal/test_runner/test:824:25) at Test.start (node:internal/test_runner/test:721:17) at node:internal/test_runner/test:1181:71 at node:internal/per_context/primordials:488:82 at new Promise (<anonymous>) at new SafePromise (node:internal/per_context/primordials:456:29) at node:internal/per_context/primordials:488:9 at Array.map (<anonymous>) { generatedMessage: false, code: 'ERR_ASSERTION', actual: 0, expected: 2, operator: '===' }
余談: tsx
を使う
完全に余談ですが、ts-node
+ tsconfig-paths
相当のことをやりたいのであれば、tsx
を使いるのがオススメです。
npm install -D tsx node --import tsx main.mjs
ts-node
よりもトランスパイルが高速で、compilerOptions.paths
を使ったモジュールの解決を組み込みでサポートしています。他にも色々と ts-node
+ tsconfig-paths
との違いがあります。詳しくは以下の FAQ を参照してください。
--require
/--import
は合成可能
何個もモジュールをプリロードしていると、コマンドラインオプションのリストが長くなってしまいます。
node \ --enable-source-maps \ --import tsx \ --import @mizdra/node-next-image-loader \ --import @power-assert/node \ --test \ demo.test.mts
ここで、--require
/--import
が require(...);
/import '...';
相当の挙動をすることを思い出してみましょう。そう、以下のように書くと --require
/--import
を合成できるのです。
node --enable-source-maps --import ./preload.mjs --test demo.test.mts
// preload.mjs import 'tsx'; import '@mizdra/node-next-image-loader'; import '@power-assert/node';
プリロードしたいモジュールが増えても安心ですね。
プリロードされるモジュールをカスタマイズ可能にする
./preload.mjs
を用意すればプログラマブルなことができるという点に着目すると、面白いことができます。例えば、@mizdra/node-next-image-loader
から API を export しておいて、ユーザが自由に @mizdra/node-next-image-loader
をカスタマイズできるように、といったことが可能です。
(理論上の話で、実際にはこの機能は @mizdra/node-next-image-loader
に実装されてないことに注意)
import { registerCustomHook } from '@mizdra/node-next-image-loader/custom'; registerCustomHook({ export: { src: { // base64 文字列ではなく Buffer として export する type: 'buffer', }, // width, height は export しない width: false, height: false, }, });
あとがき
Node.js の --require
/--import
は、シンプルながらも柔軟なオプションです。本当に色々なことができて面白いので、皆さんも是非機会があれば触ってみてください。
*1:node コマンドを実行するよりも前にトランスパイルが不要なだけで、node コマンドの実行中にオンデマンドでトランスパイルしています。見かけ上のビルドプロセスが単純化されるメリットはありますが、最終的にトランスパイルしてから実行することに変わりはありません。