2021/10/19にリリースされたNode.js v17の主な変更点を紹介します。
- QUICがサポートされたOpenSSLにアップデート
- V8 が 9.5 にアップデート
- Promiseベースのreadline APIの追加
- WHATWG Stream との互換性の強化
- ディープクローンが簡単になる structuredClone の追加
- Node.js v16からの機能
- まとめ
- 参考資料
QUICがサポートされたOpenSSLにアップデート
Node.jsのHTTPSなどのAPIで使われるOpenSSLのバージョンが1.1.0から3.0.0にアップデートされました。
OpenSSL-3.0.0はQUICをサポートしています。これによりNode.jsでもQUICをサポートできるようになります。
実は過去にQUICのサポートを実験的に行ったことがあります。
それについては以下の記事をお読みください。
しかし、後にこのQUIC実装は削除されます。
OpenSSLのメンテナンスに問題があったためです。OpenSSLのメンテナンスが滞っているため、QUIC実装のためにNode.js側でパッチをあてて対応していました。しかし、この先Node.js側でパッチをあて続けるのも困難なため、一度削除されて別の道を模索するようになりました。
QUIC実装の削除については、以下の記事にまとめられています。
今回のvNode.js v17では、AkamaiがMicrosoftが本家OpenSSLをforkしたquictls/opensslを使っています。
このforkされたOpenSSLはQUICを有効にするためにforkされて開発されています。
Node.jsで利用するOpenSSLのメンテナンスについては次のドキュメントに詳細が書かれています。
また、OpenSSL-3.0からは新しいFIPSモジュールが採用されており、Node.jsではビルド時のフラグにてFIPSを有効にするか指定できます。
$ ./configure --openssl-is-fips && make -j8 test
また、OpenSSL-3.0はNode.js v16以前で利用していたOpenSSL-1.1と互換性がない機能もあります。そのため、Node.js v17を使用していてERR_OSSL_EVP_UNSUPPORTED
のエラーが発生したときのためのオプションが用意されています。
次のように--openssl-legacy-provider
オプションを付けてNode.jsを実行することで古いOpenSSLのプロだバイダーを利用できます。
node index.js --openssl-legacy-provider
V8 が 9.5 にアップデート
JavaScriptエンジンのV8が9.5にアップデートされました。V8 9.5ではIntl APIの強化やWebAssemblyの例外のハンドリングがサポートされています。
Intl.DisplayNamesは言語、地域、文字体系の表示名を指定した言語で翻訳する機能で、V8 8.1からサポートされています。
Intl.DisplayNames
MDNからの抜粋ですが、次のように地域の名前を指定した言語、ここでは英語と繁体字(中国語)で表示します。
const regionNamesInEnglish = new Intl.DisplayNames(['en'], { type: 'region' }); const regionNamesInTraditionalChinese = new Intl.DisplayNames(['zh-Hant'], { type: 'region' }); console.log(regionNamesInEnglish.of('US')); // expected output: "United States" console.log(regionNamesInTraditionalChinese.of('US')); // expected output: "美國"
V8 9.5では言語や地域に加えて、カレンダーや日付フィールドがサポートされました。次の例はV8の記事からの抜粋ですが、"calendar"
と"dateTimeField"
をtype
に指定できます。
const esCalendarNames = new Intl.DisplayNames(['es'], { type: 'calendar' }); const frDateTimeFieldNames = new Intl.DisplayNames(['fr'], { type: 'dateTimeField' }); esCalendarNames.of('roc'); // "calendario de la República de China" frDateTimeFieldNames.of('month'); // "mois"
また言語の表示が強化され、方言の表示を指定できるようになりました。次のようにtype: 'language'
と一緒にlanguageDisplay: 'standard'
またはlanguageDisplay: 'dialect'
を指定します。
const jaDialectLanguageNames = new Intl.DisplayNames(['ja'], { type: 'language' }); const jaStandardLanguageNames = new Intl.DisplayNames(['ja'], { type: 'language' , languageDisplay: 'standard'}); jaDialectLanguageNames.of('en-US') // "アメリカ英語" jaDialectLanguageNames.of('en-AU') // "オーストラリア英語" jaDialectLanguageNames.of('en-GB') // "イギリス英語" jaStandardLanguageNames.of('en-US') // "英語 (アメリカ合衆国)" jaStandardLanguageNames.of('en-AU') // "英語 (オーストラリア)" jaStandardLanguageNames.of('en-GB') // "英語 (イギリス)"
Intl.DateTimeFormat
Intl.DateTimeFormatは、指定した言語に合わせた日時表示をするAPIです。
const date = new Date(); const df = new Intl.DateTimeFormat('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'America/Los_Angeles', timeZoneName: 'short' }); console.log(df.format(date)) // Tuesday, October 19, 2021, PDT
このAPI自体は古くからありますが、今回のV8 9.5からtimeZoneName
に指定できる値が増えて、次の値を指定できるようになりました。
'shortGeneric'
'longGeneric'
'shortOffset'
'longOffset'
'shortOffset'
と'shortGeneric'
を使った例は次のとおりです。タイムゾーンの表記が'short'
と異なっていることがわかります。
const date = new Date(); const df = new Intl.DateTimeFormat('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'America/Los_Angeles', timeZoneName: 'shortOffset' }); console.log(df.format(date)) // Tuesday, October 19, 2021, GMT-7
const date = new Date(); const df = new Intl.DateTimeFormat('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'America/Los_Angeles', timeZoneName: 'shortGeneric' }); console.log(df.format(date)) // Tuesday, October 19, 2021, PT
V8 9.5に関するアップデート内容はV8のブログに詳しく記載されています。
Promiseベースのreadline APIの追加
Node.jsのここ数バージョンでPromiseベースのNode.jsの標準APIが増えていっています。たとえば、timers/promises
はsetTimeout
などのPromiseベースのAPIを提供していたり、fs/promises
はreadFile
などのPromiseベースのAPIを提供していたりします。
Node.js v17では、新たにreadlineにPromiseベースのAPIが追加されています。
Promiseベースのため、次のようにawaitを使うこともできます。
import * as readline from ‘node:readline/promises’; import { stdin as input, stdout as output } from ‘process’; const rl = readline.createInterface({ input, output }); // Promiseベースのため、async/awaitで記述できる const answer = await rl.question('What is your favorite food? '); console.log(`Oh, so your favorite food is ${answer}`); rl.close();
また、AbortController
を使ったキャンセルもできます。
import * as readline from ‘node:readline/promises’; import { stdin as input, stdout as output } from ‘process’; const rl = readline.createInterface({ input, output }); // AbortControllerからAbortSignalを取得 const ac = new AbortController(); const signal = ac.signal; // AbortSignalを設定し、キャンセル可能にする const answer = await rl.question('What is your favorite food? ', { signal }); console.log(`Oh, so your favorite food is ${answer}`); // キャンセル時に一度だけコンソールにメッセージを表示 signal.addEventListener('abort', () => { console.log('The food question timed out'); }, { once: true }); // 10秒(10000ミリ秒)後にキャンセルする setTimeout(() => ac.abort(), 10000);
WHATWG Stream との互換性の強化
Node.jsには古くからStream APIがありますが、このAPIはWeb標準であるWHATWG Streamとは互換性のないAPIでした。
この数年のNode.jsはWeb標準との互換性を重視しています。たとえば、cyrpto APIもWeb Crypto APIが追加されていたりします。
StreamもWHATWG Streamと互換のあるWeb Streams APIがNode.js v16.5.0から追加されています。
しかし、古いStream APIとWeb Streams APIでは互換性がないため、古いStreamを使ったコードやライブラリから今後入ってくるWeb標準互換のAPIやライブラリへの移行が大変です。
そこでNode.js v17では、古いStream APIとWeb Streams APIで互換性を強化するために、fromWeb
関数とtoWeb
関数が追加されています。
import {Readable} from 'node:stream'; import {fetch} from 'undici'; const response = await fetch(url); // Web StreamからNode.jsの古いStreamへ変換 const readableNodeStream = Readable.fromWeb(response.body); // 古いStreamからWeb Streamへ変換 const readableWebStream = Readable.toWeb(readableNodeStream);
このように変換機能を使うことで古いStreamとWeb Streams APIを両方使うことができるようになります。今後Node.jsにはfetch
のようなWeb標準のAPIが追加されていく予定です。そのため、このような古いAPIとの互換性は重要な課題となっています。
ディープクローンが簡単になる structuredClone の追加
structuredClone
関数はJavaScriptの値をコピーするための関数です。
この関数を使うことでディープクローンが簡単に行えるようになっています。
この関数はWHATWGのHTML Standardに仕様定義されており、Web標準と互換性のあるAPIになっています。
const original = { foo: { bar: 1, hoge: { message: "Hello" } } }; original.self = original; const clone = structuredClone(original); console.log(clone.foo.bar); // 1 console.log(clone.foo.hoge.message); // Hello clone.foo.bar = 2; console.log(original.foo.bar); // 1 console.log(clone.foo.bar); // 2
structuredClone
はオブジェクトに限らず、number
やstring
などのプリミティブ値やDate
やArrayBuffer
、RegExp
、Map
、Error
、Array
など様々な型をサポートしています。
もし、Promise
などサポートしていない型を含む場合、DOMException
がスローされます。
structuredClone
に関する詳しい解説は以下の記事をお読みください。
structuredClone
で使われている構造化複製アルゴリズムについては以下の記事をお読みください。
Node.js v16からの機能
Node.js v17は当たり前ですが、v16の機能をすべて含んでいます。
Node.js v16では、V8のアップデートによるJavaScriptの機能が追加されていたり、パッケージマネージャー管理ツールであるCorepackの追加など大きな変化が含まれています。
次のようなJavaScriptの機能が追加されています。
パッケージマネージャー管理ツールのCorepackについてはNode学園 37時限目で話したので、そちらの資料をお読みください。
その他のNode.jsの変更点については以下の記事にも書いています。
まとめ
ユーザー視点で見ると、Node.js v17はとても嬉しい機能追加はないかもしれません。しかし、内部的にはずっと問題になっていたOpenSSLのアップデート、Web標準との互換性のための機能といった今後のNode.jsの進化に必要なものが追加されたと思います。 Node.js v17はLTSにならないバージョンのため、使う人は少ないかもしれませんが今後大きな機能が追加されていき、その後のLTSになるv18にとっても大きな機能追加があるかもしれません。
参考資料
- Node v17.0.0 (Current) | Node.js
- Node.js 17 is here!. This blog was written by Bethany… | by Node.js | Node.js Collection | Oct, 2021 | Medium
- 「OpenSSL 3.0.0」が公開 ~ライセンスは「Apache License 2.0」に - 窓の杜
- V8 release v9.5 · V8
- Intl.DisplayNames - JavaScript | MDN
- Intl.DateTimeFormat() コンストラクター - JavaScript | MDN
- Web Streams Everywhere (and Fetch for Node.js) | CSS-Tricks