別にしんどくないブログ

技術のことや読書メモを書いています

Node.js v19 の主な変更点

https://cdn-ak.f.st-hatena.com/images/fotolife/S/Shisama/20200422/20200422011813.png

Node.js v19がリリースされました 🎉

nodejs.org

この記事では Node.js v19 の主な変更点を抜粋して紹介します!

HTTP(S)/1.1 KeepAlive by default

github.com

Node.js から送信されるすべてのHTTP(S)接続において Keep-Alive を使用することになります。これによりハンドシェイクを減らすことができパフォーマンスの改善に繋がります。

Keep-Aliveについては色々解説記事があるので、ここでは割愛します。

もともと Node.js の http.Agent は keepAlive プロパティを渡せるようになっていますが、指定しない場合、つまりデフォルトでは false となります。このデフォルト値が Node.js v19 からは true になります。

また、サーバの処理にHTTP Keep-Aliveを使用しているアイドル状態のクライアントを server.close() の呼び出し時に自動的に切断する処理も追加されています。つまり、Keep-Alive を使ってサーバに接続されているがリクエストを送信していないようなクライアントとの接続を切断します。

V8 10.7

github.com

現在 Stage 3 の Intl.NuberFormat V3 が使えるようになります。

github.com

Intl.NumberFormat は昔からある API ですが、この API 群にformatRange, formatRangeToParts, selectRange といった新しい関数が追加されます。

Proposal に書かれているサンプルコードからの引用ですが、次のように指定した範囲がフォーマットされます。

const nf = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "CHF",
  maximumFractionDigits: 0,
});
nf.formatRange(3, 5);  // "CHF 3–5"
const nf = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "EUR",
  signDisplay: "always",
});
nf.formatRange(2.999, 3.001);  // "~+€3.00"

その他に追加された API については前述の TC39 の Proposal を見てください。

ちなみに Intl.NumberFormat V3 は Google Chrome では既に 106 から利用可能です。

chromestatus.com

ファイル変更時に自動的にプロセス再起動するwatchモード (experimental)

https://github.com/nodejs/node/pull/44366

こちらは Node.js v18.11.0 でも利用できる機能ですが、最近追加された良い機能なので紹介します。

JestとかWebpackとかでファイルを変更したときに自動的に再実行とかされる--watchモードのような機能です。

Node.jsに新しい起動フラグとして--watch--watch-pathが追加されました。

Node.js を実行するときに次のように --watch フラグを付与するとwatchモードとして起動します。

node index.js --watch

実行中のプログラムの依存関係にあるファイル、つまりimportして使っているファイルに変更があったときに自動でNode.jsのプロセスが再起動します。

たとえば、次のようにエントリーポイントである index.js が module A を import し、module A が module C を importしていたとします。 このプログラムを node index.js --watch で起動します。
もし module C に変更があれば、起動しているNode.jsのプロセスは再起動します。

モジュールの依存ツリー

--watch フラグを有効にしておくことで、Node.jsのサーバをローカルで起動して開発しているときに、サーバの処理を変更後に再起動をわざわざ行わなくても良くなります。

また、--watch-pathフラグも追加されています。

こちらは次のように指定したフォルダ内のファイルを監視するフラグです。 次の例では ./src./tests を監視しています。

node --watch-path=./src --watch-path=./tests index.js

起動しているエントリーポイントの依存関係にあるファイルだけでなく、上記のようにテストファイルを変更したときに再起動するといったことが可能になります。

Next.js を使っていたりすると当たり前のような機能ですが、そういったwatchモードをサポートしていない技術スタックで開発していたり、ちょっとしたスクリプトを書いたりするときは便利ですね。

ちなみにこちらはNode.js v16やv18の最新版でも利用可能です。

--experimental-specifier-resolution フラグの削除

github.com

CJS では、require('./foo') とした場合、.js.cjs が自動的に補完して読み込みます。
しかし、ESM では、仕様に基づき拡張子の指定が必須です。

以下のように拡張子が付いていない場合エラーになります。

// index.js

import './foo';
// Error [ERR_MODULE_NOT_FOUND]

ただ、モジュールローディングをする際に、以下のように--experimental-specifier-resolution=nodeを指定すると拡張子を自動で補完してくれるようになり、前述のindex.jsからfoo.jsを読み込むことができます。

node --experimental-specifier-resolution=node index.js

この便利そうな--experimental-specifier-resolution=nodeですが、Node.js v19 で非推奨となります。

代わりに Loaders API を使用すべしとのことです。

nodejs.org

Loaders API を使うことで Custom Loader を自ら作成することができます。

以下の記事では CSSimport で読み込むための Custom Loader の説明がされています。

raphael.medaer.me

この記事では次のような Custom Loader 用のファイルを作成しています。

/* loader.mjs */
import { URL } from "url";
import { readFile } from "fs/promises";

/**
 * This function loads the content of files ending with ".css" to an ECMAScript Module
 * so the default export is a string containing the CSS stylesheet.
 */     
export async function load(url, context, defaultLoad) {
    if (url.endsWith(".css")) {
        const content = await readFile(new URL(url));

        return {
            format: "module",
            source: `export default ${JSON.stringify(content.toString())};`,
        }
    }

    return defaultLoad(url, context, defaultLoad);
}

そして以下のように実行時に --experimental-loader フラグで Custom Loader を指定します。

node --no-warnings --experimental-loader ./loader.mjs index.mjs

そうすると次のようなコードが含まれる ESM な JavaScript ファイルが実行できます。

import styles from "./styles.css" assert { type: "css" };

このように Custom Loader を定義できるようになったため、--experimental-specifier-resolution=node が削除されることになりました。

Web Crypto API が stable に昇格

github.com

Web 標準な Crypto API が Node.js v15 で Experimental な機能として追加されました。

shisama.hatenablog.com

古くから Node.js には Crypto API がありますが、Web との互換性はありませんでした。

最近の Node.js は Web互換を重要視しており、Web標準API を積極的に追加しています。

この Web Crypto API もその1つです。

WebCrypto APIJavaScript で暗号化や復号、署名やその検証等の処理を行うことができる Web 標準の API です。

以下は Web Crypto API を使った公開鍵と秘密鍵の生成のサンプルコードです。

const { subtle } = require("node:crypto").webcrypto;
const generateRsaKey = async () => {
  const { publicKey, privateKey } = await subtle.generateKey(
    {
      name: "RSASSA-PKCS1-v1_5",
      modulusLength: 4096,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: "SHA-256",
    },
    true,
    ["sign", "verify"]
  );

  return {
    public: publicKey,
    private: privateKey,
  };
};

その他の API についてはドキュメントをご確認ください。

nodejs.org

ShadowRealm (experimental)

github.com

執筆時点で Stage 3 の ShadowRealm の初期実装が追加されました。まだ experimental です。

ShadowRealm の Proposal は以下です。

tc39.es

ShadowRealm については以下の記事がわかりやすいです。

zenn.dev

ざっくり言うと、グローバルを汚染しないために新しいJavaScript実行コンテキストを作成することができるAPIです。

前述の @petamoriken さんの記事からの抜粋ですが、以下のようにShadowRealmのコンテキスト内のグローバルと通常のグローバルではコンテキストが異なります。

const realm = new ShadowRealm();

// 異なるグローバル環境を持つ
globalThis.foo = "foo";
realm.evaluate(`globalThis.foo = "bar";`);
console.log(foo); // "foo"
console.log(realm.evaluate(`foo`)); // "bar"

コンテキストが異なるため、他のJavaScriptの実行には影響がないので、ユーザーが入力したスクリプトなんかもセキュアに実行できそうです。

そういったユースケースがあるためか、Salesforce が推しています。

developer.salesforce.com

前述のpetamorikenさんの記事でも紹介されていますが、ShadowRealm ができることは Node.js の vm モジュールですでに実現可能です。

const vm = require("vm");

const result = vm.runInNewContext(`
  function add(a, b) {
    return a + b;
  }
  add(2, 3);
`);
console.log(result); // 5

ですが、ShadowRealm は ECMAScript に提案された仕様のため、Webブラウザと互換があります。

まだ Experimental なので、今後どうなるかわかりませんが、Webブラウザでも Node.js でも動かしたいようなケースではこちらを使うほうが良いかもしれません。

Deprecations and Removals

package.json の imports と exports に // を指定することを非推奨

github.com

package.json でモジュールのimport/exportのパスを紐付ける機能があります。

shisama.hatenablog.com

そのパスで // (ダブルスラッシュ)を含めるパスの指定が非推奨となりました。
また、最初と最後に / があるパターンもダメなようです。

以下は元となった issue からの抜粋です。これらのパターンはすべて invalid であり、非推奨となります。

  • { "./x": ".//x/.js" } - invalid double slash in target
  • { "./*": "./x*" } for import 'pkg/x/z.js - invalid matched pattern starting with a /
  • { "./*": "./x*" } for import 'pkg/xz/ - invalid matched pattern ending with a /
  • { "./*": "./*x" } for import 'pkg//x' - invalid matched pattern starting with a /

process.exit() の引数に特定の型以外を渡すことを非推奨

github.com

process.exit() の引数および process.exitCode に代入する値に undefinednull、整数、'1'のような整数文字列以外を渡すことが非推奨となりました。

まとめ

Node.js v19 の変更点を抜粋して紹介しました。

紹介した内容の他にもNode.jsのビルド関連での変更や fs のパラメータの型チェックなどが入っています。最近は TypeScript を使う人が増えたので、影響範囲は少ないかもしれませんが気になる人はご確認ください。

Keep-Alive がデフォルト true にするのは良い変更だと思います。
コネクションを確立しつづけることになるので、Keep-Alive を明示的に指定していないアプリケーションでは影響があるかもしれないので検証が必要かもしれません。
影響がわからないから変更したくない場合は明示的に false を指定しておいても良いかと思います。

個人的には--watch フラグも良いと思いました。
現在主流のフレームワークやバンドラー、テストランナーには当たり前のように実装されている機能で開発には欠かせなくなってきています。 Node.jsのプログラムを書く時も同じような開発体験を得られるのは良いですね。

あとは Web Crypto API が stable になったり、ShadowRealm が実装されたり、Webブラウザとの互換性を高める機能の追加の流れは今後も続きそうです。

最後に自分の話をすると、久々に Node.js にコミットした変更が Node.js v19 に含まれています。
結構ギリギリでマージされたので入らないかもと思っていましたが入って良かったです。

github.com

AbortControllersignalnull を指定した場合にエラーをスローするように変更しました。これは AbortController の仕様に合わせるための変更です。

同僚の @nissy_dev が Node.js に WPT のテストを追加したときに気づきました。
ありがとうにっしー。

github.com

最後までお読みいただきありがとうございました。不備や質問があれば、お手数ですが@shisama_までお願いします。