一.webpack を放棄する理由
1.webpack モジュールの可読性が低すぎる
// モジュール参照
var _myModule1 = __webpack_require__(0);
var _myModule2 = __webpack_require__(10);
var _myModule3 = __webpack_require__(24);
// モジュール定義
/* 10 */
/***/function (module, exports, __webpack_require__) {...}
// ソースコード
_myModule2.default.xxx()
このようなコードは読むのが非常に大変です。まず _myModule2 に対応する __webpack_require__ id を見つけ、対応するモジュール定義を探し、最後にそのモジュールの exports に何が付属しているかを確認します。このモジュール定義部分が非常に厄介で、参照チェーンの読み取りを長くします
もちろん、通常は bundle を読む必要はないので、これは致命的ではありません
2.ファイルが非常に大きい
上記で述べたように、これらの追加の bundle コード(サブモジュール定義、サブモジュール参照など)によりファイルサイズが膨張します。理由は以下の通り:
-
ソースコードの各独立ファイルの外側にモジュール定義のレイヤーが包まれている
-
モジュール内の他のモジュールへの参照に
__webpack_require__宣言が挿入されている -
__webpack_require__ツール関数自体のサイズ
ファイルサイズは転送負荷をもたらすだけでなく、Compile 時間にも影響します。パッケージングソリューションの bundle size は重要な指標です
3.実行が非常に遅い
サブモジュール定義とランタイム依存関係処理(__webpack_require__)は、ファイルサイズの増大だけでなく、パフォーマンスも大幅に低下させます。下図の通り:

(画像は webpack_require is too slow より)
パッケージングソリューションはパフォーマンスに大きな影響を与える ことは、最も致命的で、耐えられない点です
二.rollup の利点
1.ファイルが非常に小さい
ほぼ余分なコードがなく、必要な cjs, umd ヘッダーを除けば、bundle コードは基本的にソースコードと変わらず、奇妙な __webpack_require__, Object.defineProperty などはありません
bundle サイズの比較は以下の通り:
webpack 132KB
rollup 82KB
2.実行が非常に速い
上記で述べたように、余分なコードがほとんどないため、webpack bundle はサイズが大きいだけでなく、非ビジネスコード(__webpack_require__, Object.defineProperty)の実行時間も無視できません
rollup はこれらの追加のものを生成しないため、実行時間は主に Compile Script と Evaluate Script にあり、残りの部分は無視できます。下図の通り:
[caption id="attachment_1531" align="alignnone" width="844"]
rollup-performance[/caption]
3.es モジュールおよび iife 形式のサポート
// rollup
amd – Asynchronous Module Definition, used with module loaders like RequireJS
cjs – CommonJS, suitable for Node and Browserify/Webpack
es – Keep the bundle as an ES module file
iife – A self-executing function, suitable for inclusion as a <script> tag. (If you want to create a bundle for your application, you probably want to use this, because it leads to smaller file sizes.)
umd – Universal Module Definition, works as amd, cjs and iife all in one
// webpack
"var" - Export by setting a variable: var Library = xxx (default)
"this" - Export by setting a property of this: this["Library"] = xxx
"commonjs" - Export by setting a property of exports: exports["Library"] = xxx
"commonjs2" - Export by setting module.exports: module.exports = xxx
"amd" - Export to AMD (optionally named - set the name via the library option)
"umd" - Export to AMD, CommonJS2 or as property in root
es6 モジュールのパッケージングをサポートしており、ベースライブラリなどに適しています。es6 プロジェクトは通常 babel で一度変換するため、1 回 の統一された babel 変換を保証できます
iife へのパッケージングをサポートしており、非常に小さくなります。また、最終 bundle サイズから見ると:
default uglify
cjs 81KB 34K
amd 81KB 30KB
iife 81KB 30KB
umd 82KB 30KB
umd は cjs より有利 で、奇妙に見えますが、実際の結果は確かにそうです。bundle の違いは主に機能名の単純化にあり、cjs bundle には多くの長い機能名が保持されており、難読化されていません
三.rollup の欠点
最新バージョン(0.50.0)はまだ 0.x の不安定な状態にあり、バージョン関連の問題が多いです(特定の問題はバージョンダウングレードで解決する必要がある場合さえあります)
- プラグインエコシステム が比較的苦手で、いくつかの一般的な要件を満たせません
例えば、複数の依存ライブラリをパッケージングし、共通の依存項を抽出する(webpack の CommonsChunkPlugin)
-
以前のバージョン(0.43)は循環依存関係の処理が不十分 で、パッケージング/実行エラーが発生する可能性があります
-
ドキュメントが比較的多くなく、問題に遭遇しても迅速に解決できません
例えば、一般的なエラー 'foo' is not exported by bar.js (imported by baz.js)、Troubleshooting は FAQ ですが、詳細で信頼できる解決策を提供していません(つまり、従っても解決できない可能性があります)
四.babel 設定
babel 変換は一般的に不可欠で、rollup/webpack パッケージングプロセスの中間処理环节として、相应的なラッパープラグインを提供しており、babel 設定を埋め込むことができます。実際に掌握する必要があるのは babel 設定です
babel preset
In Babel, a preset is a set of plugins used to support particular language features.
一般的なものは以下の通り:
-
es2015:ES6 特性のみをサポート。preset にこの項目がある場合、ES6 構文を ES5 に変換します
-
stage-0:最新の es7 や es8 特性もサポート。実際には ES Stage 0 Proposals を指します。preset にこの項目がある場合、ESn を ES6 に変換します
-
react:React JSX をサポート
stage-0 は最も過激なアプローチで、babel が変換できる すべての JS 新機能を使用することを意味し、安定しているかどうかは関係ありません。es2015 は最も保守的で、仕様はすでにリリースされており、機能が不安定なリスクはありません。stage-0 のように打てるものがさらに 4 つあります(TC39 仕様策定プロセス):
- stage-0 - Strawman: just an idea, possible Babel plugin.
- stage-1 - Proposal: this is worth working on.
- stage-2 - Draft: initial spec.
- stage-3 - Candidate: complete spec and initial browser implementations.
- stage-4 - Finished: will be added to the next yearly release.
P.S.最近 babel は babel-preset-env を提供しており、ターゲットプラットフォーム環境に応じて自動的に preset を追加します。そのため、一堆の esxxx をインストールする必要はありませんが、ES サポートのみを提供し、react や polyfill は内置されていませんし、内置されるべきでもありません。env に関する詳細情報は babel-preset-env: a preset that configures Babel for you をご覧ください
注意、各 preset は 1 段階の変換のみを担当します。例えば stage-0 は ESn を ES6 に変換できますが、ES5 ではありません。つまり、構文が非常に過激なプロジェクトを ES5 に変換したい場合、以下のような babel 設定が必要です:
{
"presets": [
["stage-0"],
["es2015", {"modules": false}]
],
"plugins": [
"external-helpers"
]
}
P.S.その中で、{"modules": false} は rollup が必要で、babel-preset-es2015-rollup の代わりに使用します。external-helpers の役割は後で紹介します
ES6 スタイルを保持したい場合、以下のような babel 設定が必要です:
{
"presets": [
["stage-0"]
],
"plugins": [
"external-helpers"
]
}
変換後に得られるのは、プロジェクトの各モジュールファイルを一緒に繋げたES6 モジュール ��、コード内の class、const、let はすべて保持されます。ES6 はこれらの特性をサポートしているためですが、async&await などのより高度な特性は ES6 に変換されます
babel plugin
babel の 3 つの処理环节において:
parsing -> transforming -> generation
プラグインは 2 番目の环节(transforming)に作用します。つまり、ソース構文の解析完了後、それを等価なターゲット構文に変換する段階で、プラグインを通じてさらに処理できます。例えば簡単なもの:
// 識別子メンバーアクセスをリテラル形式に変換、例えば a.catch -> a['catch']
es3-member-expression-literals
// 識別子メンバー宣言をリテラル形式に変換、例えば{catch: xxx} -> {'catch': xxx}
es3-property-literals
また一般的なものは以下の通り:
// class 静的プロパティとインスタンスプロパティをサポート、例えば class A{instanceProp = 1; static staticProp = 2;}
transform-class-properties
// babel 自身が使用する共通メソッドを抽出、例えば_createClass, _inherits など
external-helpers
// 定数変更チェック、const 宣言の定数が変更された場合にエラーを報告
check-es2015-constants
したがって babel plugin は大まかに3 種類に分類されます:
-
ES5/ES6 パッチ、より低い環境に関連する問題を修正(es3-xxx、es2015-xxx)
-
静的チェック、例えば const 変更エラーを「コンパイル」段階に前倒し
-
リスク特性、例えば class-properties など stage に含めるのに適さない争议的な特性
パッチは生産環境を対象とし、静的チェックは品質保証の一部であり、リスク特性はより過激な JS 構文です
babel polyfill
babel が ESn 高度構文を ES5/ES3 に変換する際、4 つの状況に遭遇します:
-
単純な構文糖。無脳変換、例えば
for...of, arrow function -
複雑な構文糖。ツール関数処理が必要、例えば
createClass, inherits -
低い環境に不足している基礎特性。polyfill が必要、例えば
Symbol, Promise, String.repeat -
polyfill できない特性。例えば
Proxy
低い環境に不足している基礎特性について、babel はデフォルトで polyfill を提供しません(babel 変換結果には polyfill が含まれていません)。babel-polyfill を導入するか、必要な特殊な polyfill(より軽量で小型のもの、またはより信頼性の高い重量級のものを)導入できます
babelHelpers
babel には変換関連のツール関数があります。例えば:
_typeof
_instanceof
_createClass
_interopRequireDefault
_classCallCheck
_inherits
asyncGenerator
これらのツール関数はすべて babelHelpers に属し、完全な helpers はコマンドで生成できます:
npm install babel-cli --save-dev
// type 選択可能 global/umd/var
./node_modules/.bin/babel-external-helpers -t umd > helpers.js
P.S.babelHelpers 生成に関する詳細情報は External helpers をご覧ください
デフォルト設定では、これらのツール関数は複数回生成され、つまり bundle 内に複数の _createClass 宣言が存在し、冗長コードです。プラグイン設定で最適化または削除できます
デフォルト設定、bundle 内に複数の helper 宣言が存在:
{
"presets": [
["es2015"]
]
}
external-helpers プラグインを追加し、helper 宣言を bundle 顶部に抽出し、複数の宣言が存在しないように:
{
"presets": [
["es2015"]
],
"plugins": [
"external-helpers"
]
}
外部 babelHelpers を参照し、bundle 内に helper 宣言を含まない:
{
"presets": [
["es2015"]
],
"plugins": [
"external-helpers"
],
externalHelpers: true
}
一般的に external-helpers を追加して helper を bundle 顶部に抽出すれば最適化要件を満たせるため、babel 設定は少なくとも external-helpers プラグインを追加して冗長 helper コードを削除することを推奨します
externalHelpers: true は複数の bundle(multi entry)の状況针对で、追加しないと各 bundle 顶部に 1 つの helper 宣言があり、追加後は bundle すべてが外部 helper を参照します。例えば:
babelHelpers.createClass(xxx)
babelHelpers は bundle 内で未定義のため、事前に導入する必要があります。例えば web 環境:
<script src="babelHelpers.js"></script>
<script src="bundle.js"></script>
五.まとめ
webpack と比較して、rollup は比類のないパフォーマンス利点を持っています。これは依存関係処理方法によって決定され、コンパイル時依存関係処理(rollup)は当然ランタイム依存関係処理(webpack)よりパフォーマンスが良くなりますが、循環依存関係の処理は 100% 信頼できません。内部実装(または設計)を通じて回避するように努め、循環依存関係を解決する一般的なテクニック は以下の通り:
-
依存関係の引き上げ、相互に依存する部分を 1 レベル引き上げる
-
依存関係の注入、ランタイムにモジュール外部から依存関係を注入
-
依存関係の検索、ランタイムにモジュール内部で依存関係を検索
依存関係の引き上げは不合理な設計针对で、此类の循環依存関係は本来回避可能です。例えば A->B, B->A は C を提案して A->C, B->C に変換できる可能性があります
回避できない循環依存関係については、ランタイム依存関係注入と依存関係検索で解決できます。例えば factory->A, A->factory、簡単な依存関係注入方案は:
// factory.js
import A from './A';
export create() {
// 構築関数注入
return new A(create);
// プロパティ注入
// let a = new A();
// a._createFromFactory = create;
// return a;
}
// A.js
class A {
constructor(create) {
this._createFromFactory = create;
}
// factory から注入される
_createFromFactory() {
return null;
}
}
したがって循環依存関係は設計/実装から解決可能で、大きな問題ではありません
应用场景而言、rollup は単一ファイルへのパッケージングに最も適しています。現在 rollup は multi entry にあまり友好的ではないためです(共通依存項を抽出できません)。また、安定性およびプラグインエコシステム、ドキュメントなどは webpack に劣りますが、パフォーマンスを厳しく要求 する场景では、rollup が唯一の選択肢です
コメントはまだありません