Node.js v18がリリースされました 🎉
この記事では Node.js v18 の主な変更点を抜粋して紹介します!
- fetch() がフラグ無しで実行可能に (experimental)
- HTTP requestTiemout()のデフォルト値の変更
- node:test モジュール(テストランナー)の追加 (experimental)
- V8 アップデートによる新しい JavaScript の API の追加
- Web Streams API のグローバルへの追加、実行時の警告の削除 (experimental)
- まとめ
fetch() がフラグ無しで実行可能に (experimental)
Node.js で fetch()
がフラグ無しで実行できるようになりました。
ブラウザの fetch()
とインターフェースは合わせていますが、fetch()
はもともとブラウザでリソースを取得するすべてのFetchingを JS の API として使えるようにする。XHRで行う API へのリクエストの代替というだけでなく、 <img>
要素などで取得も JS から行えるようにするものです。そのため、Node.js の fetch()
がブラウザの fetch()
と全く同じものというわけではありません。
とはいえ、HTTP クライアントとしての fetch()
関数自体のインターフェースは同じです。
また、Node.js のモジュールとして提供されているわけではなく、ブラウザの fetch()
と同じようにグローバルに生えています。
ですから、require
関数やimport
を使わなくても次のようなコードが書けます( https://github.com/nodejs/node/pull/42262 から引用)。
const res = await fetch('https://nodejs.org/api/documentation.json'); if (res.ok) { const data = await res.json(); console.log(data); }
実は fetch()
は Node.js v17.5.0 から利用可能です。しかし、次のように --experimental-fetch
フラグを使って実行しなければいけませんでした。
node index.js --experimental-fetch
しかし、 Node.js v18.0.0 からはフラグなしでも利用可能です。
反対に fetch()
を無効にしたいときは次のように --no-experimental-fetch
フラグを使って実行しなければいけません。
node index.js --no-experimental-fetch
また、フラグなしで fetch()
が使えるようになったとしても、まだ experimental (実験的な機能)であることに注意しなければいけません。
実験的な機能であるため、まだ安定していないので、プロダクトではまだ使わないほうが安全でしょう。
少し内部的な話をすると、この fetch()
は undici という Node.js とは別のソフトウェアを内部では利用して実装されています。
もともと undici は Node.js のメンテナーの一人が個人開発していましたが、現在では nodejs organization へ移管されており、複数のメンテナーがいます。
開発初期から Node.js の http
モジュールの代わりとして使えるように、net
や tls
を使ってフルスクラッチで開発されました。
元々 http
モジュールはレガシーなコードでメンテナンスも難しい状態でした。
その中でも HTTP パーサーはメンテナンスが難しい状態でした。
そこで undici では llhttp という次世代の HTTP パーサーを利用しています。
現在では、llhttp は Node.js 本体の HTTP パーサーとしても利用されており、nodejs organization でメンテナンスがされています。
llhttp はメンテナンス性を考慮して、TypeScript で開発されています。
しかし、高速に実行するために TypeScript のコードを C に変換しています。
また、Node.js や JavaScript からだけでなく、Python や Ruby からも使えるようになっています。
undici はこの llhttp を Wasm へビルドされたものを利用しています。
また、undici 自体は次のように単体でも利用可能です。
import { request } from 'undici' const { statusCode, headers, trailers, body } = await request('http://localhost:3000/foo') for await (const data of body) { console.log('data', data); }
あと、fetch()
に関連して、FormData、 Headers 、 Request 、 Response も追加されています。これらもグローバルに定義されています。
HTTP requestTiemout()のデフォルト値の変更
server.requestTimeout
のタイムアウト時間のデフォルト値が 0
から 300000
(5分)になります。
これにより、これまでブラウザなどクライアントからのリクエストの待機時間が長くてもエラーにならなかったのが、5分でエラーになる可能性があります。
これまでデフォルトの値のままにしていた開発者は十分な時間を確保できる値を設定しなければいけません。
server.requestTimeout
には任意の値を設定することができます。
しかし、0
に設定するのは推奨されていません。なぜなら、0
は永遠に待つことを意味しており、DoS攻撃などがあったときに攻撃が終わらないかぎりリクエスト待ち状態になります。
このような観点からデフォルトの値も5分へと変更になったようです。
node:test モジュール(テストランナー)の追加 (experimental)
Node.js の標準APIとしてテストランナーが追加されました。次のように test
モジュールを読み込んで利用します( https://github.com/nodejs/node/pull/42262 から引用)。
import test from 'node:test'; import assert from 'assert/strict'; test('top level test', async (t) => { await t.test('subtest 1', (t) => { assert.strictEqual(1, 1); }); await t.test('subtest 2', (t) => { assert.strictEqual(2, 2); }); });
また、skip
や concurrency
、todo
といったオプションも用意されています。
test('skip option', { skip: true }, (t) => { // This code is never executed. });
まだ、experimental な機能であり、単一のファイルの実行しかできないので、JestやMochaなどの既存のテストランナーから乗り換えるのは時期尚早だと思います。
しかし、将来的に stable になれば、ちょっとしたテストなら Node.js の標準テストランナーでも事足りるケースであれば、 Jest などの npm パッケージをインストールしなくてもよくなるかもしれません。
利用するにあたって注意点があります。
この node:test
はこれまでの Node.js の標準 API とは違い、 import
や require
でモジュールを読み込むときに node:
プレフィックスが必須です。
もしプレフィックスを付けずに require('test')
とするとユーザーランドの test
モジュールを探索しにいきます。他の Node.js の標準 API とは異なる挙動なので注意しましょう。
V8 アップデートによる新しい JavaScript の API の追加
Node.js で利用される JavaScript エンジンの V8 が 10.1 にアップデートしました。
これによりいくつかの新しい JavaScript の API が利用可能になります。
Array#findLast(), Array#findLastIndex()
findLast()
と findLastIndex()
は配列(Array)の新しいメソッドです。
find()
や findIndex()
が配列の先頭から指定した条件に一致する要素を探すのに対して、findLast()
と findLastIndex()
は末尾から探索します。
findLast()
は配列の末尾から指定した条件に一致する要素の値を返し、findLastIndex()
は末尾から条件に一致する要素番号を返します。
const arr = [1, 2, 3, 4, 5]; arr.findLast(el => el % 2 === 0); // 結果は 4(2ではない) arr.findLastIndex(el => el % 2 === 0) // 結果は 3 (1ではない)
Intl.supportedValuesOf()
Intl.Locale
オブジェクトに格納されている calendars
や collations
、timeZones
といったプロパティがあります。
たとえば、calendars
というプロパティには、どの暦が使われているか一覧が配列で格納されています。
次のコードのように指定される Locale によってサポートされているプロパティは異なります。
const jpLocale = new Intl.Locale('ja'); jpLocale.calendars; // ['gregory'] が格納されている。 jpLocale.timeZones // 'ja' は timeZones をサポートしていないので、undefinedになる
Intl.supportedValuesOf()
を利用すれば、それらのプロパティがどの Locale をサポートしているか確認できます。
Intl.supportedValuesOf('calendar'); // ['buddhist', 'chinese', 'coptic', 'dangi', 'ethioaa', 'ethiopic', 'gregory', 'hebrew', 'indian', 'islamic', 'islamic-civil', 'islamic-rgsa', 'islamic-tbla', 'islamic-umalqura', 'iso8601', 'japanese', 'persian', 'roc'] Intl.supportedValuesOf('timeZone'); // ['Africa/Abidjan', 'Africa/Accra', ...]
その他の改善
V8 のアップデートによって、class fields や private class methods のパフォーマンス改善もされています。
Web Streams API のグローバルへの追加、実行時の警告の削除 (experimental)
Web Streams API の一連のクラスがグローバルに定義されるようになりました。また実行時の警告が削除されました。
Web Streams API は Web (ブラウザ)の Stream API と同じインターフェースや仕様に沿った新しい Stream の API です。
元々 Node.js には Stream API がありましたが、ブラウザとの互換性がない API でした。
Node.js は Web との互換性を重視するようになり、Web の API を積極的に Node.js 本体に実装しています。
そのため、従来の Node.js の Stream API は残しつつも、Web 互換な Stream API を実装しました。
この Web Streams API には、読み込み専用のReadableStream
、書き込み用のWritableStream
、変換用の TransformStream
をはじめとした多くのクラスが存在します。
以前は 'node:stream/web'
から ReadableStream
などを import して利用しなければいけませんでしたが、v18 からは次のようにグローバルから読み込んで利用できるようになりました。
// Streamを読み込むためのオブジェクトを生成 const stream = new ReadableStream({ start(controller) { controller.enqueue('a'); }, }); // 変換処理を行うためのオブジェクトを生成 const transform = new TransformStream({ transform(chunk, controller) { controller.enqueue(chunk.toUpperCase()); } }); const transformedStream = stream.pipeThrough(transform); for await (const chunk of transformedStream) console.log(chunk);
Web Streams API は Node.js v16.5.0 から利用可能でしたが、experimentalな機能で利用時にはコンソールに警告が表示されていました。
しかし、v18 から警告の表示はされません。
まとめ
Node.js v18 の変更点を抜粋して紹介しました。
fetch()
関数の追加や Web Streams API の安定版への移行など Web との互換が今回も目立った印象があります。
そのほかにも Blob
や BroadcastChannel
が Node.js v18 からグローバルで利用できるようになっています。
これらもブラウザのようにグローバルで使えるようにするための変更です。
Node.js の Web 互換の流れはまだまだ続きそうです。
テストランナーは便利そうですね。まだまだ Jest や Mocha のように機能が充実しているわけではないですが、npm パッケージを別途インストールせずにテストコードを実行できるようになるのは便利です。 今後、既存のテストランナーにある機能を Node.js のテストランナーでも利用できるようにしてほしいといった要望も出てくるかもしれません。どういった機能が今後追加されていくのか楽しみです。
最後までお読みいただきありがとうございました。不備や質問があれば、お手数ですが@shisama_までお願いします。