rollup是面向library的?!

寫在前面css

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
rollup是面向library的?!vue

webpack致力於複雜SPA的模塊化構建,很是吸引人的是各類loader:react

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……),優點以下:webpack

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.

另外一些很是強大的特性包括:web

Code Splitting:生產環境按需加載/並行加載npm

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,即面向library設計的

二.核心優點

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都會遇到問題)

若是隻是面向lib的話,第一點不支持也沒關係,但第二點着實頭疼,二級依賴是不可控的,老是不可避免地會遇到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之類的類庫,應該儘量地做爲第三方依賴獨立出去,而不是build進bundle,幾個緣由:

性能會變差,好比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,仍是這兩個東西居然要配合使用,更多信息請查看[question] Difference between externals and globals

參考資料

Webpack and Rollup: the same but different:頗有意思的文章,發現得太晚了

Handling 3rd-party JavaScript with Rollup

相關文章
相關標籤/搜索