はじめに
Rollup was designed with libraries rather than apps in mind, and it is a perfect fit for React's use case.
Behind the Scenes: Improving the Repository Infrastructure - React Blog でこれを見て、少し驚きました。こんな素晴らしいものが、なぜライブラリ専用なのだろう?何が原因で App 構築には不向きなのか?
零.webpack

webpack は複雑な SPA のモジュール構築に尽力しており、非常に魅力的なのが多様な loader です:
Essentially, webpack loaders transform all types of files into modules that can be included in your application's dependency graph.
さまざまなリソース依存関係を一貫した方法で処理し、loader を通じてリソースタイプの差異を隠蔽します(js は module、css は module、img も module……)。利点は以下の通りです:
No more carefully placing your files in the right folders and hacked-together scripts for adding hashes to file URLs?—?webpack can take care of it for you.
その他、非常に強力な機能には以下が含まれます:
-
Code Splitting:本番環境でのオンデマンドロード/並列ロード
-
Tree Shaking:ビルド時に不要なコード(export)を削除
-
HMR:開発中のモジュールホットスワップ
-
Commons Chunk:ビルド時の共通依存関係の抽出
-
Dependency Graph:ビルド完了後にモジュール依存関係グラフを出力し、bundle に可読性を持たせる
一.初衷
rollup は当初から ES6 module 向けに設計されていました:
Next-generation ES6 module bundler.
当時、AMD、CMD、UMD 形式之争はまだ熱く、ES6 module にはブラウザ実装がありませんでした。rollup はこうして現れました
Rollup was created for a different reason: to build flat distributables of JavaScript libraries as efficiently as possible, taking advantage of the ingenious design of ES2015 modules.
���Webpack and Rollup: the same but different より引用、rollup 作者本人による記述)
ES6 module 機制を最大限に活用し、構造が扁平で性能に優れたライブラリ bundle を構築すること、つまりライブラリ向けに設計されたものです
二.コア優勢
It solves one problem well: how to combine multiple modules into a flat file with minimal junk code in between.
rollup が人を驚嘆させるのはその bundle の清潔さです。特に iife 形式は内容が非常にクリーンで、余分なコードがほとんどなく、本当に各モジュールを依存順序に従って先後に拼接しただけです
これは rollup のモジュール処理思路に関係しています:
To achieve this, instead of turning modules into functions like many other bundlers, it puts all the code in the same scope, and renames variables so that they don't conflict. This produces code that is easier for the JavaScript engine to parse, for a human to read, and for a minifier to optimize.
すべてのモジュールを bundle ファイル内の最外层作用域にフラットに配置し、モジュール間に作用域隔離はありません。重命名に頼って同一作用域下の命名衝突問題を解決します。いくつかの明らか利点:
-
ランタイム性能(コード構造が扁平で、解析しやすい)
-
bundle ソースコードの可読性(自然な順序構造、モジュール定義/ジャンプなし)
-
圧縮最適化性(モジュール定義などの圧縮できないボイラープレートコードなし)
こうする欠点も明らかです:
-
モジュールシステムがあまりに静的で、HMR などの機能実装が困難
-
ES6 module のみ向けで、cjs、umd 依存関係を確実に処理できない(rollup-plugin-commonjs を使うたびに問題に遭遇します)
ライブラリ向けであれば、第一点の不支持でも構いませんが、第二点は本当に頭が痛く、二次依存は制御不能で、常に不可避免地に cjs モジュールを ES6 module に自動変換できない問題に遭遇します。例えば:
'foo' is not exported by bar.js (imported by baz.js)
一部のシナリオは Troubleshooting を通じて namedExports 方式であまり楽しくなく解決できます。別の時には external または globals で回避し、さらに plugin 適用順序を調整する解法もあります……しかし这类問題を彻底して解決する方法はありません:
Webpack gets around the need for namedExports by keeping everything as CommonJS modules and implementing Node's require system in the browser, which is why the resulting bundles are larger and take longer to start up. Both options are valid, but that's the tradeoff to be aware of — more config (Rollup, when using modules like React), or larger bundles (webpack).
(Is "named exports" feature or bug? より引用)
cjs はいずれ歴史になるでしょうが、目前および当分は、npm に依然として相当数の cjs モジュールが存在し、SPA でも library でも、依然として頻繁に cjs モジュール依存関係を処理する問題に直面します
三.選用原則
Use webpack for apps, and Rollup for libraries
App を構築するなら webpack が合适、ライブラリならもちろん rollup の方が良い
webpack が App を構築する優勢は以下の方面に表れます:
-
強力なプラグイン生態系、主流フロントエンドフレームワークに対応する loader あり
-
App 向け機能サポート、例えば之前に言及した HMR、Code Splitting、Commons Chunk などは App 開発に必須の機能
-
Web 開発の各リンクを簡素化、画像自動 base64、リソースキャッシュ(chunkId)、ルート別コード拆分、懒加载など、実装が難しくない
-
信頼できる依存モジュール処理、rollup のような cjs 問題に直面せず、
__webpack_require__にこれらの悩みなし
rollup にはこれらの優勢がなく、コード拆分などを行うと解決が容易でない問題に遭遇します。十分な時間と把握がなければ、轻易に rollup を App 構築ツールとして試してはいけません
rollup の優勢は高效率な bundle にあり、这正是ライブラリが追求するもので、多少周折しても(React 16 が行ったように)、性能のために価値があります
注意、この原則は合适的なツールで合适的なことをすると言うだけで、多数の一般シナリオに適用されます。rollup で App を構築し、webpack でライブラリを構築するのも珍しくありません:
That's not a hard and fast rule?—?lots of sites and apps are built with Rollup, and lots of libraries are built with webpack. But it's a good rule of thumb.
典型的に、ビジネス本身にあまり第三者モジュール依存関係がなく、かつ風格約定が ES6 module に従う場合、rollup で App を構築するのも合适です(Code Splitting なども完全にできないわけではない)
P.S.另外、rollup は glup や webpack のように stream ベースの拡張を行いにくく、例えば vue ファイルから三部分を分離して分別処理するなど(vue プラグイン 好像还不支持 ts)
四.外部依存
React のようなライブラリの場合、できるだけ第三者依存関係として独立させるべきで、bundle に build すべきではありません。いくつかの理由:
-
性能が悪化する、例えば React 16 は大変努力して rollup + GCC(Google Closure Compiler)に切り替え 109kb に達したが、自分で編むとすぐに解放前に戻る
-
キャッシュに不利、ライブラリは頻繁に更新せず、静的リソースとしてキャッシュ優勢を発揮できるが、手動 build の内容はツール設定影響を受ける
rollup 下では external + globals 配置を通じて外部依存関係をマークできます:
external: ['react', 'react-dom'],
output: {
globals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
}
こうして生成された bundle は:
// iife
(function (React,ReactDOM) {
//...
}(React,ReactDOM));
// cjs
var React = _interopDefault(require('react'));
var ReactDOM = _interopDefault(require('react-dom'));
したがって一般にビジネスコードを iife に打包し、script を通じて CDN 第三者ライブラリを引用します:
<script crossorigin src="https://unpkg.com/react @16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom @16/umd/react-dom.production.min.js"></script>
<!-- または聚合バージョン -->
<script crossorigin src="//cdn.jsdelivr.net/combine/npm/react @16.0.0/umd/react.production.min.js,npm/react-dom @16.0.0/umd/react-dom.production.min.js"></script>
P.S.rollup の external と globals は少し奇妙で、key も value も、またこれら 2 つが配合使用しなければならないことも。詳細情報は [question] Difference between externals and globals を参照
コメントはまだありません