Webpack原理與實踐:關(guān)于如何選擇合適的打包工具Rollup和Webpack?
寫在前面
Rollup是一款ES Modules打包器,它也可以將項目中散落的細(xì)小模塊打包成整塊代碼,從而使得這些劃分的模塊可以更好地運行在瀏覽器或node.js環(huán)境。Rollup與webpack的作用非常類似,但是小巧的多,誕生的初衷就是希望能夠提供一個高效地ES Modules打包器,充分利用ES Modules的各項特性。
Rollup的使用
Rollup 對代碼模塊使用新的標(biāo)準(zhǔn)化格式,這些標(biāo)準(zhǔn)都包含在 JavaScript 的 ES6 版本中,而不是以前的特殊解決方案,如 CommonJS 和 AMD。ES6 模塊可以使你自由、無縫地使用你最喜愛的 library 中那些最有用獨立函數(shù),而你的項目不必攜帶其他未使用的代碼。ES6 模塊最終還是要由瀏覽器原生實現(xiàn),但當(dāng)前 Rollup 可以使你提前體驗。
首先當(dāng)然是需要執(zhí)行安裝命令進(jìn)行安裝rollup,接著就和webpack一樣需要配置相關(guān)的config文件。我們可以配置不同的配置文件直接用cli進(jìn)行打包,但是如果添加更多地選項,這種命令行的方式就變得很麻煩。為此我們需要創(chuàng)建配置文件來囊括所需的選項,配置文件以rollup.config.js形式命名,比cli更加靈活。
- $ npm i rollup -D
我們看到其實rollup配置和webpack大同小異,想要更詳細(xì)的配置項信息可以異步rollup官網(wǎng)。
- //src/helloWorld.js
- export default function sayHello(){
- console.log("nihao");
- }
- //src/index.js
- import sayHello from "./helloWorld"
- sayHello()
- //rollup.config.js
- export default {
- input:"src/index.js",//打包入口
- output:{//文件輸出配置
- file:"dist/bundle.cjs.js",//打包后生成的文件位置和文件名
- format:"cjs",//文件的輸出格式,遵循cjs規(guī)范,這也是官方模塊化規(guī)范
- name:"bundleName"//包的全局變量名
- }
- }
我們在package.json文件中配置的打包命令是"build":"rollup -c",我們在執(zhí)行打包命令npm run build后,看到生成的文件dist/bundle.cjs.js中的代碼是非常簡潔清爽的,可讀性非常強。
- //dist/bundle.cjs.js
- 'use strict';
- function sayHello(){
- console.log("nihao");
- }
- sayHello();
接下來,我們就詳細(xì)談?wù)剅ollup的相關(guān)配置問題,我們知道rollup默認(rèn)采用的是ESM規(guī)范,但是如果你想使用CommonJS規(guī)范,你可以使用.cjs后綴進(jìn)行區(qū)分:
- //rollup.config.cjs
- module.export = {
- input:"src/index.js",
- output:{
- file:"dist/bundle.cjs.js",
- format:"cjs"
- }
- }
前面我們已經(jīng)創(chuàng)建了一個簡單的打包配置文件,但是當(dāng)你需要創(chuàng)建更加復(fù)雜的bundles時,你將需要更大的彈性,比如:通過npm安裝導(dǎo)入模塊、通過babel編譯代碼,使用json配置文件等等。對此,我們可以使用plugins插件,通過在捆綁過程的關(guān)鍵點來改變rollup的行為。比如,我們可以使用@rollup/plugin-json插件來允許導(dǎo)入JSON文件,使用命令行npm install --save-dev @rollup/plugin-json安裝。
- // src/main.js
- import {version} from "../package.json";
- export default function(){
- console.log('version ' + version);
- }
- // rollup.config.js
- import json from '@rollup/plugin-json';
- export default {
- input: 'src/main.js',
- output: {
- file: 'bundle.js',
- format: 'cjs'
- },
- plugins: [json()]
- };
這樣,執(zhí)行npm run build命令進(jìn)行打包,生成的文件是:
- 'use strict';
- var version = '1.0.0';
- function main() {
- console.log('version ' + version);
- }
- module.exports = main;
當(dāng)然,一些特殊的插件依賴于一些輸出,有關(guān)特定于輸出的插件可以做什么的技術(shù)細(xì)節(jié),請參閱插件鉤子。如果一個不兼容的插件被用作特定于輸出的插件,那么 Rollup 會發(fā)出警告。
為了實踐那些依賴于輸出的插件,我們基于前面簡單的項目打包進(jìn)行相關(guān)的配置,先執(zhí)行命令行npm install --save-dev rollup-plugin-terser,
- // rollup.config.js
- import json from '@rollup/plugin-json';
- import { terser } from 'rollup-plugin-terser';
- export default {
- input: 'src/main.js',
- output: [
- {
- file: 'bundle.js',
- format: 'cjs'
- },
- {
- file: 'bundle.min.js',
- format: 'iife',
- name: 'version',
- plugins: [terser()]
- }
- ],
- plugins: [json()]
- };
此時打包得到:
- var version = (function () {
- 'use strict';
- var n = '1.0.0';
- return function () {
- console.log('version ' + n);
- };
- })();
前面我們看到可以將多個文件的代碼打包成一個文件,那么當(dāng)項目比較大的時候就需要對代碼進(jìn)行分割。對于代碼分割,有時候 Rollup 會自動將代碼分割成塊,比如動態(tài)加載或多個入口點,還有一種方法可以通過 output.manualChunks 選項顯式告訴 Rollup 哪些模塊要分割成單獨的塊。
- // src/main.js
- export default function () {
- import('./foo.js').then(({ default: foo }) => console.log(foo));
- }
Rollup將會使用動態(tài)導(dǎo)入去創(chuàng)建一個分割的chunk文件,只在需要的時候進(jìn)行加載。為了rollup能夠知道哪里是第二個chunk塊,可以通過設(shè)置--file設(shè)定規(guī)范,通過--dir創(chuàng)建導(dǎo)出目錄。
- $ rollup src/main.js -f cjs -d dist
接下來將會創(chuàng)建一個包含兩個文件main.js和chunk-[hash].js的目錄dist,此處的[hash]是一個hash字符串。你可以設(shè)置你自己想要的文件命令模式,通過 output.chunkFileNames和output.entryFileNames 進(jìn)行配置。
- //→ main.js:
- 'use strict';
- function main() {
- Promise.resolve(require('./chunk-b8774ea3.js')).then(({ default: foo }) => console.log(foo));
- }
- module.exports = main;
- //→ chunk-b8774ea3.js:
- ('use strict');
- var foo = 'hello world!';
- exports.default = foo;
Rollup的優(yōu)勢是:
- 輸出結(jié)果更加扁平,執(zhí)行效率更高
- 自動移除未引用代碼
- 打包結(jié)果依然完全可讀
Rollup的缺點是:
- 加載非ESM的第三方模塊比較復(fù)雜
- 因為模塊最終會都被打包到全局中,所以無法實現(xiàn)HMR
- 瀏覽器環(huán)境中,代碼拆分功能必須使用Require.js這樣的AMD庫
寫在最后
對于Webpack大而全,Rollup小而美,我的選擇基本原則是應(yīng)用開發(fā)優(yōu)先webpack,類庫或框架開發(fā)使用Rollup。這是因為在開發(fā)js庫時,webpack的繁瑣和打包后的文件體積太大,而rollup就是針對js庫和框架開發(fā)的,它只是生成代碼將我們的代碼轉(zhuǎn)為目標(biāo)js。如果你想了解更多關(guān)于rollup的相關(guān)知識,可以閱讀(官方文檔)。