別にしんどくないブログ

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

Node.js v14の主な変更点

f:id:Shisama:20200422011813p:plain

4/21 にリリースされた Node.js v14 の主な変更点を紹介します。

この記事では Changelog の Notable Change から一部を簡単に紹介します。

github.com

TL;DR

  • V8 が 8.1 になりOptional chining や Nullish coalescing が使えるようになった
  • fs.promises が 'fs/promises' でロード可能になった
  • ES Modules の警告が表示されなくなった

目次

deps: update V8 to 8.1

github.com

Node.js は V8 というJavaScript エンジンを使っています。
V8 はGoogle Chrome などでも使われています。
Node.js v14 で使うV8 のバージョンは 8.1 になります。
Node.js v13 が V8 7.9 を使用しているので、Node.js v14 では新しく V8 8.0 と 8.1 の機能を使うことが可能になります。
この V8 のバージョンアップにより、JavaScript (ECMAScript) の新機能が使えるようになったりパフォーマンスが改善されています。

JavaScript

V8 のバージョンが上がることで使用できる JavaScript の構文や機能が増えます。
Node.js v14 で新しく使うことができる機能について簡単に説明します。

Optional chaining

Optional chaining - JavaScript | MDN

Optional chaining は参照したオブジェクトや関数などの値が undefined または null の可能性があっても、その値が持つプロパティに安全にアクセスすることができます。
まずは以下のコードを見てください。
これまではオブジェクトが持つプロパティにアクセスする前にそのオブジェクトが undefined や null でないことをチェックするための if 文を書いたり、三項演算子が必要でした。

let id = null;
if (user) {
  id = user.id;
}

しかし、Optional chaining を使うことで上記のコードを簡潔に書くことができます。

const id = user?.id;

上記の ?. が Optional chaining の構文です。この ?. をつけることで user オブジェクトが undefined や null の場合はそれが持つプロパティにはアクセスせずに undefined を返します。
以下のようにチェーンされたプロパティでも使用可能です。

const city = user?.org?.location?.city;

これまでは以下のように if の条件で都度チェックする必要があったので、Optional chaining を使うことで非常に簡潔にかけるのがわかると思います。

if (
  user &&
  user.org &&
  user.org.location
) {
  city = user.org.location.city;
}

Nullish coalescing

Null合体演算子 - JavaScript | MDN

Nullish coalescing は値が undefined または null の場合にデフォルト値を取得することができます。
もっと具体的に言うと A ?? BA が undefined または null の場合は B の値を取得することが可能な論理演算子です。

以前から || という論理演算子がありました。
例えば、A || B という式があったとき、A が falsy な値の場合は B を取得するという論理演算子です。 JavaScript の falsy な値は undefinednullfalse0'' があります。そのため、 || 演算子では undefinednull だけのチェックはできません。
結局は以下のように個別で判定する必要があります。

let lang;
if (user.lang !== undefined && user.lang !== null) {
  lang = user.lang;
} else {
  lang = 'en';
}

Nullish coalescing ( ?? )を使うことで以下のように簡潔に書けます。

const lang = user.lang ?? 'en';

前述の Optional chaining と併用することで undefined や null の取り扱いが非常に簡単になります。

const lang = user?.lang ?? 'en';

Intl.DisplayNames

Intl.DisplayNames - JavaScript | MDN

Intl.DisplayNames は 国際化APIIntl の1つで、指定したロケールとオプションに基づいた表示名称の翻訳結果を取得することができます。

例えば、フランス語で指定した言語(以下では en-USja )の表示名称は以下のように取得することができます。

const langName = new Intl.DisplayNames(['fr'], {type: 'language'});
languageNames.of('en-US') // 'anglais américain'
langName.of('ja') // 'japonais'

Intl.DisplayNames をインスタンス化する際に、第一引数で翻訳する言語を指定して、第二引数はどのように変換するかオプションで指定します。

上記の例では type'language' を指定していますが、他にも以下のオプションを指定することが可能です。

プロパティ名 指定可能な値 デフォルト値
localeMatcher "lookup", "best fit" "best fit"
style "narrow", "short", "long" "long"
type "language", "region", "script", "currency" "language"
fallback "code", "none" "code"

例えば、type に "currency" を指定した場合は通貨の名称を取得することができます。

const currencyName = new Intl.DisplayNames(['fr'], {type: 'currency'});
currencyName.of('JPY') // 'yen japonais'

前述の MDN のページには type 以外の記載は無いので詳しくは ECMAScript のproposalをご確認ください。

tc39.es

パフォーマンス

V8 v8.0 からはメモリの使用量の削減やビルトインの高階関数の最適化がされているためパフォーマンスの向上が期待できます。

詳しくはV8のブログを参照してください。

fs: add fs/promises alias module

github.com

Node.js v10 から追加された fs モジュールのPromise APIについて読み込み方法が追加されました。
これまでは以下のように fs モジュールをロードして promises を参照する必要がありました。

// CommonJS
const fs = require('fs');
const fsPromises = fs.promises;
// ES Modules
import fs from "fs";
const fsPromises = fs.promises;

これからは以下のように読み込むことが可能になりました。

// CommonJS
const fsPromises = require('fs/promises');
// ES Modules
import fsPromises from 'fs/promises';

'fs/promisesfs.promisesエイリアスのため、今までの読み込み方法も問題ないです。
この変更により次のBreaking Change が発生します。
この Breaking Change は Node.js のモジュールの読み込み優先度に関係しています。 require は以下の順でモジュールを探索して読み込みます。

  1. Node.js のコアモジュール (e.g. require('fs') )
  2. ルートパス (e.g. require('/') )
  3. 相対パスの指定先 (e.g. require('./lib') )
  4. 読み込み元から親ディレクトリを辿り最も近い package.json の name に一致するパッケージ
  5. node_modules 以下のパッケージ

※ 詳しくは Modules | Node.js v13.13.0 Documentation にあるモジュール読み込みのアルゴリズムをお読みください。

上記のようにnode_modules より Node.js のコアモジュールが優先されて読み込まれます。
これまでは node_modules/fs/promises が読み込まれていましたが、Node.js v14 からはコアの fs/promises が優先されて読み込まれるようになります。 そのため、 node_modules/fs/promises というパスで独自のパッケージを置いている場合、このパッケージは読み込まれなくなります。

この fs/promises ですが、ちょっと変わった経緯があります。
最初 fs に Promise が追加されたときは fs.promises で読み込むように実装されていましたが、 fs/promises で読み込むように変更されました。

github.com

しかし、再度 fs.promises に戻されました。

github.com

当時の fs/promisesfs.promises に変えるかどうかの議論は以下の issue にあります。

github.com

module: remove experimental modules warning

github.com

これまでは ES Modules を使用した場合、以下のような警告が表示されていました。

ExperimentalWarning: The ESM module loader is experimental.

Node.js v14 からは上記の警告は表示されなくなります。
ただしAPI のドキュメントにも記載の通りまだ Experimental なので使用する際は注意しましょう。

nodejs.org

cli, report: move --report-on-fatalerror to stable

github.com

Node.js を実行するときのCLIオプション --report-on-fatalerror が stable (安定)になりました。これにより Diagnostic Report が stable になりました。 このCLIオプションを使うことで、致命的なエラーが発生したときにヒープ、スタック、イベントループの状態、リソースの消費などのレポートがJSONで出力されます。

nodejs.org

次のように --report-on-fatalerror を指定します。他にも例外が catch されなかったときにレポートを出力する --report-uncaught-exception などがあります。

node --report-on-fatalerror server.js

レポートは指定がなければ report.20200422.004935.44547.0.001.json のような日付を含むファイル名で以下のように細かい情報が出力されます。

{
  "header": {
    "reportVersion": 2,
    "event": "write after end",
    "trigger": "Exception",
    "filename": "report.20200422.004935.44547.0.001.json",
    "dumpEventTime": "2020-04-22T00:49:35Z",
    "dumpEventTimeStamp": "1587484175317",
    "processId": 44547,
    "threadId": 0,
    :
    "cpus": [
      {
        "model": "Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz",
        "speed": 2600,
        "user": 13548430,
        "nice": 0,
        "sys": 9623000,
        "idle": 70546540,
        "irq": 0
      },
      :

その他

紹介しきれなかったのですが、他にもまだまだ変更点はあります。
非同期処理を行う libuv がバージョンアップしていたり、V8の新しいArrayBufferが使われるようになったりしています。
Node.js の内部の実装に興味がある方はチェックしてみてください。

Deprecate になった API もいくつかあるので、チェックしてみてください。

まとめ

proposal の段階では QUIC も入れようと提案されていましたが、結局入らなかったみたいですね。
この Node.js v14 ですが、順調に進めば例年通り 10 月末に LTS になります。しかし、コロナが収束していなければ遅れる可能性もあります。
LTS になるまでの間も機能が追加されていくと思うので、Pull RequestCHANGELOGNode.js 公式ページの News をチェックしてみてください。

最後までお読みいただきありがとうございました。不備や質問があれば @shisama_ 宛にメンションしていただくかブコメなどでお伝え下さい。