JSerレポート #2: Node.jsコアモジュールとBundler(webpackなど)によるpolyfillのギャップ
JSerレポート #2: Node.jsコアモジュールとBundler(webpackなど)によるpolyfillのギャップ:
このレポートは、現在進行形で機能追加や仕様変更が行われているNode.jsコアモジュールとブラウザ向けpolyfillにおける挙動の違い(ギャップ)が広がってきている問題について調べたものです。
ここでは https://nodejs.org/api/ に掲載されているうち
webpackやbrowserifyなどのbundlerは、コード中にあるコアモジュールを代替モジュールへとすり替えます。この代替モジュールはブラウザ向けpolyfillライブラリとよび、このpolyfillライブラリはブラウザで動くようにNode.jsコアモジュールと同等また空のダミー実装をしています。
というコードはwebpackなどでbundleすると、次のように書いたのと同じようにモジュールの差し替えが行われます。
webpackでは、このNode.jsコアモジュールへの差し替えをnodeオプションによって設定が可能です。
ここでいうギャップというのは、次のようなケースを並べています。
そのため次の表は古くなっている可能性があります。
注記: 依存しているpolyfillそのものはアップデートで解決されている場合があります。しかし、bundlerが古いバージョンを使っている場合があります。
次にそれぞれでのテスト結果を示します。
Gapリスト通りのpolyfillが使われています。
多くのコアモジュールにおいては、問題が表面化しない可能性もあります。
しかし、
また、これらの問題が解決できた場合にも、webpackやbrowserifyには暗黙的にpolyfillライブラリを差し替える仕組み上は、バージョン違いといった互換性の問題が発生するかもしれません。
最近になって、2018年12月21日にwebpack 5 alphaが公開されました。
webpack 5では自動的にNode.jsコアモジュールのpolyfillを自動的に入れないようにする変更が予定されています。
(2018年12月25日時点ではただの予定であるため、該当Issueにおいてフィードバックを求めています。)
今までは
一方で、現在ではブラウザ向けに書かれたは多くのモジュールがあるため、webpackが自動的にpolyfillを入れる必然性が小さくなってきています。
また、
少しブラウザとは異なりますが、React NativeのBundlerもNode.jsコアモジュールのpolyfillを自動的に差し替えない仕組みとなっています。
このように、BundlerがNode.jsコアモジュールのpolyfillを暗黙的に入れるという挙動の状況は少し変わりつつあります。
これは、webpack 5の変更予定にも書かれていたように、Bundlerの目的の1つがNode.js向けに書かれたモジュールをブラウザ向けに変換することでした。
しかし、現在は多くのブラウザ向けに書かれたモジュールがあり、Bundlerはそれを効率的に扱うという目的に変わってきている点も関係しているのかもしれません。
このレポートは、現在進行形で機能追加や仕様変更が行われているNode.jsコアモジュールとブラウザ向けpolyfillにおける挙動の違い(ギャップ)が広がってきている問題について調べたものです。
ここでは https://nodejs.org/api/ に掲載されているうち
assertのようにNode.jsにバンドルされているモジュールのことをNode.jsコアモジュールと呼びます。コアモジュールはNode.jsでの利用のみを想定しているため、Node.jsに依存した処理を多く含んでいます。そのため、コアモジュールのコードをコピーしてブラウザなどで動かすことは難しいです。webpackやbrowserifyなどのbundlerは、コード中にあるコアモジュールを代替モジュールへとすり替えます。この代替モジュールはブラウザ向けpolyfillライブラリとよび、このpolyfillライブラリはブラウザで動くようにNode.jsコアモジュールと同等また空のダミー実装をしています。
Node.jsコアモジュールのpolyfillライブラリの例
webpackとbrowserifyは変換時に、コード中に現れるassertモジュールをcommonjs-assertというpolyfillライブラリに自動的にすり替えます。const assert = require("assert")
const assert = require("commonjs-assert")
polyfill library
webpackとbrowserifyが利用するpolyfillライブラリは次の場所で管理されています。- webpack:
- browserify:
機能のギャップ
このレポートの本題であるNode.jsコアモジュールとブラウザ向けpolyfillのギャップがあったものをまとめた表です。ここでいうギャップというのは、次のようなケースを並べています。
- Node.jsコアモジュールで追加されたAPIがpolyfillライブラリには存在しない
- Node.jsコアモジュールとpolyfillライブラリで挙動が異なる
- 利用されているpolyfillライブラリがDeprecatedになっている
そのため次の表は古くなっている可能性があります。
注記: 依存しているpolyfillそのものはアップデートで解決されている場合があります。しかし、bundlerが古いバージョンを使っている場合があります。
実装状況
この調査リポジトリには簡単な機能テストも実装されています。次にそれぞれでのテスト結果を示します。
Node v11.5.0
24コのテストをすべてパス(これがpolyfillの元なので当然ですが…) 24 passing (146ms)
Browserify 16.2.3
4/24のテストをパス。 gap-test
assert
1) Error#code
2) assert.deepEqual
3) assert.strict
4) assert.rejects
5) assert.doesNotReject
events
6) off
✓ eventNames
✓ getMaxListeners()
✓ prependListener()
✓ prependOnceListener()
os
7) constants
path
8) posix
9) win32
10) parse
11) format
process
12) platform
13) execArgv
14) cpuUsage()
15) emitWarning()
url
16) URL
util
17) inspect.defaultOptions
18) callbackify()
19) promisify()
vm
20) isContext
4 passing (293ms)
20 failing
webpack 4.82.2
すべてのテストが失敗しました。Gapリスト通りのpolyfillが使われています。
gap-test
assert
1) Error#code
2) assert.deepEqual
3) assert.strict
4) assert.rejects
5) assert.doesNotReject
events
6) off
7) eventNames
8) getMaxListeners()
9) prependListener()
10) prependOnceListener()
os
11) constants
path
12) posix
13) win32
14) parse
15) format
process
16) platform
17) execArgv
18) cpuUsage()
19) emitWarning()
url
20) URL
util
21) inspect.defaultOptions
22) callbackify()
23) promisify()
vm
24) isContext
0 passing (134ms)
24 failing
おわりに
このレポートは、webpackやbrowserifyを使っているとあまり意識されないpolyfillライブラリに潜在的な問題があることを調べる目的で書きました。この問題の難しさは各polyfillライブラリの管理者やバランスが異なるにもかかわらず、polyfillライブラリ群として暗黙的に参照されている点です。多くのコアモジュールにおいては、問題が表面化しない可能性もあります。
しかし、
assert、events、urlはブラウザ向けとしてよく使われているにもかかわらず、差異が分かる程度にはあります。また、これらの問題が解決できた場合にも、webpackやbrowserifyには暗黙的にpolyfillライブラリを差し替える仕組み上は、バージョン違いといった互換性の問題が発生するかもしれません。
- Node.js Errors — Changes you need to know about – Node.js Collection – Medium
assert結果のError#nameなどが異なるため、Node.jsでは通るがブラウザ(polyfill)では失敗するテストができる- MapやSetなどES2015以降のビルトインオブジェクトに対応していない
- Node v7 で入った WHATWG URL 実装について | blog.jxck.io
- Node.jsがブラウザのWHATWG URLをサポートしたが、ブラウザ(polyfill)ではサポートされていない
最近になって、2018年12月21日にwebpack 5 alphaが公開されました。
webpack 5では自動的にNode.jsコアモジュールのpolyfillを自動的に入れないようにする変更が予定されています。
(2018年12月25日時点ではただの予定であるため、該当Issueにおいてフィードバックを求めています。)
In the early days, webpack's aim was to allow running most node.js modules in the browser, but the module landscape changed and many module uses are now written mainly for frontend purposes.CHANGELOGにこのように書かれているのように、webpackはNode.jsモジュールをブラウザ向けにpack(polyfill)する役割から、フロントエンド向けに書かれたモジュールをbundleする役割へ変わってきています。
-- https://github.com/webpack/changelog-v5/blob/master/README.md#automatic-nodejs-polyfills-removed
今までは
BufferなどNode.jsのコアAPIに対応するモジュールを自動的にbundleすることで、Node.js向けに書かれたモジュールをブラウザでも動かせるようにしていました。一方で、現在ではブラウザ向けに書かれたは多くのモジュールがあるため、webpackが自動的にpolyfillを入れる必然性が小さくなってきています。
また、
Bufferのpolyfillなどはファイルサイズがほどほどに大きいため、パフォーマンス面においては自動的にpolyfillを行わないメリットもあります。(polyfillを行うかどうかは、webpack 4でもnodeオプションによって設定が可能です)少しブラウザとは異なりますが、React NativeのBundlerもNode.jsコアモジュールのpolyfillを自動的に差し替えない仕組みとなっています。
このように、BundlerがNode.jsコアモジュールのpolyfillを暗黙的に入れるという挙動の状況は少し変わりつつあります。
これは、webpack 5の変更予定にも書かれていたように、Bundlerの目的の1つがNode.js向けに書かれたモジュールをブラウザ向けに変換することでした。
しかし、現在は多くのブラウザ向けに書かれたモジュールがあり、Bundlerはそれを効率的に扱うという目的に変わってきている点も関係しているのかもしれません。
コメント
コメントを投稿