Node.js 10がサポート終了したので、Node.js 12以降だとできるようになることをまとめる
2021-04-30でNode.js 10.x LTSのサポートが終了しました。
そのため、ライブラリなどではNode.js 12.x以上がサポート対象となります。
Version | LTS | Date | V8 | npm | NODE_MODULE_VERSION |
---|---|---|---|---|---|
Node.js 12.22.1 | Erbium | 2021-04-06 | 7.8.279.23 | 6.14.12 | 72 |
Node.js 12.x 以降をサポート対象としたときに新たにできるようになることをまとめましょう。
自由に追加、補足コメント入れておいてください。
ECMAScript Modules
Added in: 12.7.0
Node.js 12.7.0で--experimental-modules
フラグが外れたため、Node.js 12 LTSではECMAScript Modules(ESM)が利用できるようになります。
これによりライブラリがCommonJS形式(require
/exports
)ではなく、ESM形式(import
/export
)で公開できるようになります。そのため、多くのライブラリがESM形式のみでの公開へと切り替わっていきます。
ESMはブラウザ(IE以外)、Node.js、rollup/webpackなどのbundler、TypeScript(exports
フィールドは未対応)/Babel、Jestなど主要なツールがサポートしている状態です。IEの場合は、bundlerなどのツールを経由すれば良いため(またbundlerはESMだと最適化できるため)、ライブラリそのものはESM形式をnpmなどのregistryにpublishするのが適切な状態です。
Node.jsがESMにネイティブ対応する以前も、package.json
のmain
フィールドでCommonJS形式とmodule
フィールドでESM形式の両方を公開していました。
しかし、Node.js 12でESMに対応したため、ESM形式のみで十分なケースが増えてきてたのと、ESMは静的に依存関係をパースできるため最適化しやすいなど理由からESMへの移行が進んでいます。
Node.js 12からはpackage.json
のexports
フィールドによって、宣言的にCommonJSとESMの両方へと対応ができるようになっています。
しかし、一部のツールではまだexports
フィールドに対応していないものもあります。
参考:
AsyncLocalStorage
Added in: v13.10.0, v12.17.0
async_hooks
モジュールのAsyncLocalStorageが利用できるようになります。
非同期処理のコンテキストに紐づくストレージです。
他の言語ならスレッドストレージなどが近い存在です。
AsyncLocalStorageを使うと、Node.jsサーバでリクエストのコンテキストに紐づくログやトレーシングを簡単に扱えるようになります。
リクエスト自体は並列に来るため、そのリクエストを区別して扱うためのIDを管理するインメモリなStorageとして扱えます。
- medibloc/nestjs-request-context: NestJS Request Context using AsyncLocalStorage
-
dd-trace-js/async_local_storage.spec.js at 7b27059fe3b9de6788c0517a2b499ff6b78da5ff · DataDog/dd-trace-js
- datadogのtracingでも利用されている
async_hooks
モジュール自体はNode 10以前にもあったので、cls-hookedのようなライブラリで似たようなことができました。
express-http-context
もこの async_hooks
(cls-hooked
経由)を使っています。
crypto.randomInt([min, ]max[, callback])
Added in: v14.10.0, v12.19.0
Math.random()
と違ってSecure RandomなIntを返す crypto
モジュールのメソッドが扱えます。
UUIDはもうちょい先になりそうです
fs.rmdirSync(path[, options])
Added in: v12.10.0
再帰的に削除するオプション recursive
が入ったため、node -e "fs.rmdirSync('dist',{recursive:true,force:true})"
で glob は使えませんが rimraf 相当のことが可能になりました。
なお v14.14.0 で fs.rmSync(path[, options])
などが追加され、v16.0.0 で fs.rmdirSync()
などの recursive
オプションは非推奨になったため、fs.rmSync()
がある環境では node -e "fs.rmSync('dist',{recursive:true,force:true})"
としたほうが良さそうです。
Source Map対応
Added in: v12.12.0
Node v12.12.0から --enable-source-maps
でスタックトレースがSource Mapをサポートしています。
Readable.from(iterable, [options])
Added in: v12.3.0, v10.17.0
Node.js 10にもbackportされていますが、Iterator/Async IteratorからStreamを作成できるReadable.from
が追加されています。
IteratorやAsync Iteratorを Readable.from(iterator)
に渡せるので、
Iteratorである文字列や配列、Generator関数/Async Genrator関数などからStreamを手軽に作成できます。
Diagnostic report
Node.jsで例外発生時などに診断用のレポートを生成できるDiagnostic ReportがNode v12.17.0でStableになっています。
assert.CallTracker
Added in: v14.2.0, v12.19.0
ある関数が呼び出されたかをチェックできる assert.CallTracker
が追加されています。
次のようにテスト時に、テストしたい関数が実際に何度呼ばれることを期待するかをチェックできます。
it("should lazy load router has sub path", async () => {
const app = express();
const requestTracker = new assert.CallTracker();
const requestHandler: RequestHandler = requestTracker.calls((_, res) => {
res.json({
ok: true
});
}, 1); // 1回呼ばれることを期待する
// /api/status
app.use(
"/api",
lazyLoad(async () => createMockRouter(requestHandler, "/status"))
);
return request(app)
.get("/api/status")
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.then((response) => {
assert.deepStrictEqual(response.body, { ok: true });
requestTracker.verify(); // 期待された回数呼ばれているならpass
});
});
参考
ES compatibility
詳細は https://node.green/ を参照
ES2019
- Object.fromEntries
- Array.prototype.{flat, flatMap}
ES2020
- String.prototype.matchAll
- Promise.allSettled (12.9.0+)
- globalThis
ES2021
- numeric separators (12.5.0+)
Stage 3
- instance class fields (optional private field access を除く)
- static class fields
assert.match(string, regexp[, message])
Added in: v12.16.0
文字列が正規表現にマッチするかをテストできるassert.match
がNode.js v12.16.0で追加されています。
import assert from 'assert/strict';
assert.match('I will fail', /pass/);
// AssertionError [ERR_ASSERTION]: The input did not match the regular ...
assert.match(123, /pass/);
// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
assert.match('I will pass', /pass/);
// OK