🦾

主なNode.js独自API

2022/02/26に公開1

本稿では、Node.jsに存在するがWebブラウザに存在しないAPIを一覧します。こういったAPIの中には一見してNode.jsの独自APIとわかりにくいものもあり、一通りそのようなAPIを把握しておくことでよりポータビリティーの高いコードを書けるようになることが期待できます。

特によく問題になる独自API

  • Buffer (Uint8Arrayのラッパーであり、Uint8Array + TextEncoder/TextDecoderでおおよそ代替できる)
  • querystring (npmから提供されているqsやquery-stringが代替としてよく使われる)

なぜ独自APIを気にする必要があるのか

モジュールバンドラーの普及により、同一のJavaScriptコードをNode.jsとWebブラウザーの両方で動かすことが容易になりました。

Node.jsのAPIのなかには、Bufferやquerystring, EventEmitterやstreamなどのように外界へのインターフェースではない (= Node.jsとは独立したライブラリとしても提供可能な) ものもいくつか存在します。これらを利用するJavaScriptコードはWebブラウザーでも動くことが期待されるため、WebpackなどのモジュールバンドラーはしばしばこれらのAPIをその互換実装で置き換えてくれますが、残念ながらこういった互換実装はメイン実装ほど積極的に開発されておらず、計画的にまとまった形で開発されているわけでもないようです。 (やや古い資料ですがNode.jsコアモジュールとブラウザ向けShimのギャップについてなどが参考になります)

そのため、以下のような機能で置き換えたほうが信頼性が高くなる可能性があります。

  • JavaScriptの標準機能 (BufferUint8Array など)
  • WebブラウザとNode.jsが直接実装している互いに互換性のある機能 (crypto → SubtleCrypto など)
  • npm等から入手できる純JavaScript製のライブラリ (querystringqs, query-string など)

Node.jsが提供するAPIの種類

グローバル変数として提供されているものは、以下のようにそのまま使うことができます。

Buffer.from("abc")

Node.jsの組み込みモジュールとして提供されているものは、 import / require を通じて利用することができます。

// CommonJS Modules の場合
const fs = require("fs");
const fs = require("node:fs");
// ES Modules の場合
import fs from "fs";
import fs from "node:fs";

process のように、グローバル変数としても組み込みモジュールとしても提供されているAPIもあります。

global

globalThisの別名です。Webブラウザでは windowself がglobalThisの別名として定義されていますが、Node.jsには windowself はなく、かわりに global が定義されています。

Buffer

ArrayBuffer, TypedArray (Uint8Arrayなど), DataView はJavaScriptの標準機能です。一方、BufferはNode.jsの独自機能です。

現在では、Bufferを使わなければいけない特段の事情はありません。

Node.js APIs accept plain Uint8Arrays wherever Buffers are supported as well.

ただし、Bufferにはstringとの相互変換や入出力に便利な処理がいくつか定義されています。また、Node.js自身のAPIにはBufferを返すものも多く存在しています。もしBufferを使う必要がある場合はBufferとプレーンなUint8Arrayの差分も把握しておくといいかもしれません。

setImmediate / clearImmediate

setImmediateは setTimeoutqueueMicrotask の仲間で、タイマーを使わずに直接タスクキューにコールバックを登録する関数です。タスクとマイクロタスクについてはJavaScriptの非同期処理をじっくり理解する (1) 実行モデルとタスクキューなどを参照してください。

setImmediateはWebブラウザには存在せず、同等の機能を提供するAPIも存在しません。類似の機能である setTimeout(0) を使うか、 postMessage を使ってエミュレートする手段があり、polyfillも存在します。

process

process はNode.jsプロセスの情報にアクセスするためのAPIであり、ほとんどNode.js固有です。

ただし、モジュールバンドラーにはソースコード中の process.env に対する参照を一定の規則に基づいて置き換えるルールが実装されていることがあります。特に process.env.NODE_ENV は慣習的によく使われます。

// process.env.NODE_ENV が定義されている or モジュールバンドラーによって置換されることを期待している
if (process.env.NODE_ENV === "development") {
  console.warn("this function is deprecated");
}

組み込みモジュール (1)

組み込みモジュールのうち、一見するとNode.jsのAPIである必要がないものは以下の通りです。

(本稿ではわかりやすさのために node: をつけていますが、 node: をつけなくてもimport/require可能であることに注意が必要です)

  • assert
    • node:assert は不変条件が満たされないときに例外を送出する関数が定義されているライブラリです。
    • Jestなどのテストフレームワークはそれぞれがアサーション用の関数を提供しているので、それで置き換えることができます。npm上にも keywords:assertkeywords:assertion でヒットするパッケージが多数存在します。
  • node:async_hooks
  • node:console
    • node:consoleConsole クラスを提供します。シングルトンである console と同等の機能をもつロガーを自由に生成することができます。
    • シングルトンの console はNode.jsとWebブラウザで共通で利用できますが、 Console はNode.jsにしかありません。
  • node:crypto
    • node:crypto は暗号関連処理を提供します。 (暗号学的な安全性が求められる処理を自分で実装するのはやめましょう)
    • グローバル変数 crypto, Crypto, CryptoKey, SubtleCryptoWeb Cryptography APIと互換性があるため、こちらを使うのがよいでしょう。
  • node:events
    • Node.jsのイベント通知機構 EventEmitter を提供します。
    • ブラウザ互換の Event / EventTarget とおおよそ同等のため、 EventTarget を使うほうがよいでしょう。
  • node:punycode
  • node:querystring
    • node:querystringはクエリ文字列のパーサーです。
    • 非推奨ではないもののレガシーコード向けAPIとしてマークされています。
    • Webブラウザ互換のURLライブラリのURLSearchParamsでの置き換えが推奨されています。
    • qsquery-stringなども広く使われています。
  • node:stream
    • node:streamはストリーム (一括ではなく先頭から少しずつ送受信されるデータ) を扱うためのライブラリです。
    • WHATWG Streamsの実装が node:stream/web から提供されているため、可能であればこちらを使うのがよいでしょう。
    • 現時点では node:stream/web の内容はグローバル変数としては提供されていません。将来的にグローバル変数として提供されるようになったら、そちらに移行するのがよいでしょう。
  • node:string_decoder
    • node:string_decoderは名前の通り、UTF-8等でエンコードされたデータを string にデコードするライブラリです。
    • 現在はWebブラウザ互換のTextDecoderがあるので、そちらを使うのがよいでしょう。
  • node:timers
    • node:timerssetTimeout などのタイマー関連APIを提供します。
    • active, enroll, unenroll など非推奨化されたAPIを除き、グローバル変数として提供されます。
      • このうち setTimeout, clearTimeout, setInterval, clearInterval はWebブラウザのものとほぼ同等の使い方ができます。 (独自APIを備えたオブジェクトが返ってくるという違いがある程度)
      • setImmediate, clearImmediate には同等のAPIはありません (前述の記述を参照)
  • node:url
    • node:url はURL操作 (パーサー等) を提供します。
    • Webブラウザ互換の URL がグローバル変数として提供されているので、そちらを使うのがよいでしょう。
  • node:util
    • node:utilはごった煮です。
    • ひとつひとつの代替を紹介するのは大変なので省略します。
  • node:zlib
    • node:zlib は名前の通りzlibのバインディングを提供します。
    • ポータビリティーが必要であればCライブラリに依存するわけにはいかないので、npmで純JSの実装を探すといいかもしれません。

組み込みモジュール (2)

よりNode.js固有に近いものには以下のようなものがあります。こういったAPIを使っている場合は本質的にNode.jsに固有の処理を書いている可能性が高いので、それぞれの詳しい説明は省略します。

  • node:process
    • process グローバル変数と同じ。
  • node:diagnostics_channel
    • ライブラリから警告等の診断情報を流すための汎用API。
  • node:wasi
    • WebAssembly で起動するWebAssemblyプログラムに外部環境へのアクセス手段を提供するモジュール。
  • node:path
    • ファイルパスの操作用ですが、ファイルパスの振舞いはOS依存なので実質的にはスタンドアロン環境に固有の処理と考えられます。
  • プロセス・スレッド関連
    • node:child_process
    • node:cluster
    • node:worker_threads
  • 各種I/Oモジュール
    • node:dns
    • node:fs
    • node:http
    • node:http2
    • node:https
      • http, http2, https を使った処理は fetch で置き換えられる可能性があります。
      • fetch はNode.jsによる公式実装が実験的に利用可能なほか、非公式のnode-fetchが長年デファクトスタンダードとして使われています。
        • cross-fetchisomorphic-fetch など、 fetch のpolyfillを環境に応じて出し分けるライブラリもあります。
    • node:net
    • node:os
    • node:readline
    • node:tls
    • node:tty
    • node:dgram
  • Node.js固有の機能へのアクセス
    • node:domain
    • node:inspector
    • node:module
    • node:perf_hooks
      • require("node:perf_hooks").performanceglobalThis.performance と同じもので、これはWebブラウザとある程度の互換性があります。
    • node:repl
    • node:trace_events
    • node:v8
    • node:vm

CommonJS Modules 向けAPI

require, module, exports, __dirname, __filename はCommonJS Modules向けAPIです。モジュールスコープで自動的に定義される名前であり、厳密にはグローバル変数ではありません。

WebpackなどCommonJS Modulesをサポートしたモジュールバンドラーを使う場合、これらのAPIは特別な指定がなくても利用可能です。

Webブラウザと共通のAPI

ここに挙げられているAPIがNode.jsとWebブラウザで完全に互換な振舞いをするとは限らないことに注意してください。

Discussion

petamorikenpetamoriken

ちょっとした補足です。

setImmediateはWebブラウザには存在せず、同等の機能を提供するAPIも存在しません。

setImmediate は元々 Microsoft が提案した仕様で、IE10 以降のブラウザで使うことができました。旧 Edge にも搭載されていましたが、Chromium Edge になった段階で廃止されたと言う認識です。

https://w3c.github.io/setImmediate/#si-setImmediate

ちなみに core-js にて polyfill が提供されています。