rollup輸出的6種格式詳解
學(xué)習(xí)本文 ??
之前找工作的時候因為在簡歷上寫“熟練使用 rollup.js
進行構(gòu)建”,但回答不出 rollup
能打包出哪幾種格式,我差點被面試官鄙視。你將:
- 弄明白vite和rollup輸出的幾種格式,了解它們之間的關(guān)系和使用場景。
- 獲得一份最簡單的
rollup.js
打包demo
一份。 - 這份
demo
還為每個打包結(jié)果準(zhǔn)備了一個可執(zhí)行demo of demo
為什么要學(xué)這個?
伴隨著 vite.js
越來越火,它也變成了工作生產(chǎn)和面試問答時被經(jīng)常提及的問題。
眾所周知,vite.js
構(gòu)建的核心是 rollup.js
。
那為什么在學(xué)習(xí)它們之前,有必要先學(xué)習(xí) js
模塊化規(guī)范?
因為我們需要理解它們最終的產(chǎn)物是什么,是如何運作的。
“崇拜魔法”是編程學(xué)習(xí)者的大忌,“好奇心”才是我們成長與進步的階梯。
讓我們解開神秘的面紗,看看那些打包后的代碼,都是些“什么玩意兒”!
DEMO與示例構(gòu)建
請配合 demo
食用,你會收獲更多!
我為本文構(gòu)建了一個簡單的 demo
實例,源碼就放在 github
上。
demo
地址: github.com/zhangshichu…demo
的代碼結(jié)構(gòu)如下:
├── answer.js ├── index.js ├── out ├── package.json ├── rollup.config.js └── yarn.lock
其中 index.js
和 answer.js
屬于業(yè)務(wù)代碼,是需要被打包的對象。
在 index.js
中依賴了 answer.js
。
如下:
// answer.js export default 42 // index.js import answer from "./answer"; import { repeat } from 'lodash' // 定義一個無用變量, 測試 tree-shaking const unusedVar = 'May the 4th' export const printAnswer = () => { // 打印 console.log(`the answer is ${answer}`) // 測試 lodash 的能力,打印42個1 console.log(repeat('1', answer)) }
rollup.config.js
是 rollup
的配置文件,在此文件中我們分別指定其輸出 amd
, cjs
, esm
, iife
, umd
, system
六種格式的文件。
輸出的文件會被打包到 out
文件夾下。
當(dāng)我們執(zhí)行 yarn build
或者 npm build
之后,會在 out
下產(chǎn)生如下格式的代碼:
├── out │ ├── amd │ │ └── bundle.js │ ├── cjs │ │ └── bundle.js │ ├── ems │ │ └── bundle.js │ ├── iife │ │ └── bundle.js │ ├── system │ │ └── bundle.js │ └── umd │ └── bundle.js
為了弄清楚每種格式的 bundle.js
文件是如何運作的,我專門為它們訂制添加了一些附屬的小 demo
。
接下來,就讓我們一種格式一種格式地學(xué)習(xí)和分析吧。
一、IIFE 自執(zhí)行函數(shù)
IIFE 的全稱是 “immediately invoked function expression”。
1.1 打包結(jié)果分析
讓我們先看看本 demo
的 iife
格式打出來的包長什么樣。
對上述代碼做一些簡化:
var Test = (function (exports, lodash) { 'use strict'; // 自帶嚴(yán)格模式,避免一些奇怪的兼容性問題 /** * 下面折行無用代碼被 tree-shaking 掉了 * const unusedVar = 'May the 4th' * */ var answer = 42; // 業(yè)務(wù)中被單一引用的模塊,被直接抹平了 const printAnswer = () => { console.log(`the answer is ${answer}`); console.log(lodash.repeat('1', answer)); }; exports.printAnswer = printAnswer; // 把要export的屬性掛在到exports上 return exports; })({}, $); // exports是第一個入?yún)?,依賴的jquery是第二個入?yún)?
IIFE
是前端模塊化早期的產(chǎn)物,它的核心思路是:
- 構(gòu)建一個匿名函數(shù)
- 立刻執(zhí)行這個匿名函數(shù),對外部的依賴通過入?yún)⒌男问絺魅?/li>
- 返回該模塊的輸出
1.2 如何運行
IIFE
的運行其實很容易,如果它沒有其他依賴,只需要去引入文件,然后在 window
上取相應(yīng)的變量即可。
如:
<script src="http://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script> // jquery 就是典型的自執(zhí)行函數(shù)模式,當(dāng)你引入后,他就會掛在到 window.$ 上 window.$ // 這樣就能取到 jquery 了 </script>
但是如果你像本 demo
中那樣依賴了其他的模塊,那你就必須保證以下兩點才能正常運行:
- 此包所依賴的包,已在此包之前完成加載。
- 前置依賴的包,和
IIFE
只執(zhí)行入?yún)⒌淖兞棵且恢碌摹?/li>
以本 demo
的 IIFE
構(gòu)建結(jié)果為例:
- 它前置依賴了
lodash
,因此需要在它加載之前完成lodash
的加載。 - 此
IIFE
的第二個入?yún)⑹?lodash
,作為前置條件,我們需要讓window.lodash
也指向lodash
。
因此,運行時,代碼如下:
<head> <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script> <script>window.lodash = window._</script> <script src="./bundle.js"></script> </head> <body> <script> window.Test.printAnswer(); </script> </body>
1.3 優(yōu)缺點
優(yōu)點:
- 通過閉包營造了一個“私有”命名空間,防止影響全局,并防止被從外部修改私有變量。
- 簡單易懂
- 對代碼體積的影響不大
缺點:
- 輸出的變量可能影響全局變量;引入依賴包時依賴全局變量。
- 需要使用者自行維護
script
標(biāo)簽的加載順序。
優(yōu)點就不細(xì)說了,缺點詳細(xì)解釋一下。
缺點一:輸出的變量可能影響全局變量;引入依賴包時依賴全局變量。
前半句:輸出的變量可能影響全局變量; 其實很好理解,以上面 demo
的輸出為例: window.Test
就已經(jīng)被影響了。
這種明顯的副作用在程序中其實是有隱患的。
后半句:引入依賴包時依賴全局變量; 我們?yōu)榱俗?demo
正常運行,因此加了一行代碼讓 window.lodash
也指向 lodash
,但它確實是太脆弱了。
<!-- 沒有這一行,demo就無法正常運行 --> <script>window.lodash = window._</script>
你瞧,IIFE
的執(zhí)行對環(huán)境的依賴是苛刻的,除非它完全不依賴外部包。(Jquery: 正是在下!)
雖然 IIFE
的缺點很多,但并不妨礙它在 Jquery
時代極大地推動了 web
開發(fā)的進程,因為它確實解決了 js
本身存在的很多問題。
那么?后續(xù)是否還有 更為優(yōu)秀 的前端模塊化方案問世呢?
當(dāng)然有,往下看吧。
二、CommonJS
2.1 分析打包結(jié)果
先看看 CommonJs
打包的結(jié)果:
簡化一下,就長這樣了:
'use strict'; var lodash = require('lodash'); var answer = 42; const printAnswer = () => { // 打印 console.log(`the answer is ${answer}`); // 測試 lodash 的能力,打印42個1 console.log(lodash.repeat('1', answer)); }; exports.printAnswer = printAnswer;
以上格式,就是 CommonJS
規(guī)范的寫法。
// CommonJS 通過一個全局 require 方法進行模塊的引入 var lodash = require('lodash'); // CommonJS 進行模塊內(nèi)方法輸出的方式 module.exports.printAnswer = printAnswer; // 上面寫法也等價于: exports.printAnswer = printAnswer; // 因為 exports 變量等價于 module.exports
為了解決 node.js
在模塊化上的缺失, 2009年10月 CommonJS
規(guī)范首次被提出。
注意這個關(guān)鍵詞: node.js。
是的,CommonJS
并不是在瀏覽器環(huán)境運行的規(guī)范,而是在 node.js
環(huán)境下運行的。
2.2 如何運行
因此,我寫了一個 run.js
腳本。 如下:
// run.js const Test = require('./bundle.js') Test.printAnswer()
然后,執(zhí)行以下命令:
# 執(zhí)行腳本 node ./out/cjs/run.js # 輸出1: > the answer is 42 # 輸出2: > 111111111111111111111111111111111111111111
可以看出,node.js
環(huán)境是天然支持 CommonJS
的。
2.3 優(yōu)缺點
優(yōu)點
完善的模塊化方案,完美解決了 IIFE
的各種缺點。
缺點
不支持瀏覽器環(huán)境,因為這種同步的引入方式可能導(dǎo)致瀏覽器假死。
因此,前端界迫切地需要一種能在瀏覽器環(huán)境完美運行,完善的模塊化方案。
三、AMD 和 requirejs !
AMD,YES!
2011年, amdjs-api
在業(yè)內(nèi)被正式提出。
3.1 打包結(jié)果分析
amd 格式的打包結(jié)果如下:
可以看到,核心內(nèi)容是一個全局方法 define
。
define
方法有三個入?yún)?,分別是:
"Test"
, 模塊名稱[
exports,
lodash]
分別表示模塊的輸出和外部依賴- 一個以
exports
和lodash
作為入?yún)⒌姆椒?,代表模塊的實際內(nèi)容。
相比于 IIFE
和 CommonJs
而言,AMD
的寫法無疑是復(fù)雜且別扭的。
但它卻實實在在是解決了 IIFE
和 CommonJS
所面臨的問題,對“瀏覽器里完善的JS模塊方法” 提供了一套完善的方案。
尤其是 amd
標(biāo)準(zhǔn)的實現(xiàn)方案:requirejs
。
requirejs
所實現(xiàn)的 AMD
不僅解決了 CommonJS
在瀏覽器端的不適,通過異步的方式進行模塊加載實現(xiàn)了不會導(dǎo)致假死的能力;更是完全彌補了 IIFE
存在的各類缺陷。
requirejs
在使用時,一般情況下是以下四步法:
- 在瀏覽器內(nèi)引入
require.js
- 通過
requirejs.config
方法定義全局的依賴 - 通過
requirejs.define
注冊模塊 - 通過
requirejs()
完成模塊引入。
3.2 如何運行
在 out/amd
打包目錄下的 index.html
里,按如下方式編排代碼:
<head> <!-- 1. 引入 require.js --> <script src="./require.js"></script> <!-- 2. 定義全局依賴 --> <script> window.requirejs.config({ paths: { "lodash": "https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min" } }); </script> <!-- 3. 定義模塊 --> <script src="./bundle.js"></script> </head> <body> <script> // 4. 開銷模塊 window.requirejs( ['Test'], function (test) { test.printAnswer() } ); </script> </body>
打開瀏覽器,我們可以正常地看到]控制臺里被打印出來的 42
和 42個1
了。
3.3 優(yōu)缺點
優(yōu)點
- 解決了
CommonJS
的缺點 - 解決了
IIFE
的缺點 - 一套完備的瀏覽器里
js
文件模塊化方案
缺點
- 代碼組織形式別扭,可讀性差
但好在我們擁有了各類打包工具,瀏覽器內(nèi)的代碼可讀性再差也并不影響我們寫出可讀性ok的代碼。
現(xiàn)在,我們擁有了面向 node.js
的 CommonJs
和 面向瀏覽器的 AMD
兩套標(biāo)準(zhǔn)。
如果我希望我寫出的代碼能同時被瀏覽器和nodejs識別,我應(yīng)該怎么做呢?
四、UMD 偉大的整合
它沒有做什么突破性的創(chuàng)造,但它是集大成者。
4.1 打包分析
umd
格式構(gòu)建出來的代碼的可讀性進一步降低了。
我相信任何正常人看到下面這段代碼都會感到一陣頭大:
是的,整整一大段代碼,只是在處理兼容性問題,判斷當(dāng)前應(yīng)該使用 amd
亦或是 CommonJS
。
因此 umd
的代碼和實現(xiàn)不在此進行過多分析,它所做的無非便是讓同一段代碼兼容了 amd
和 CommonJS
。
4.2 如何運行?
- 在瀏覽器端,它的運行方式和
amd
完全一致,可以完全參考3.2
節(jié)的demo
。 - 在node.js端,它則和
CommonJS
的運行方式完全一致,在此就不贅述了。
4.3 優(yōu)缺點
優(yōu)點
抹平了一個包在 AMD
和 CommonJS
里的差異
缺點
會為了兼容產(chǎn)生大量不好理解的代碼。(理解難度與包體積)
雖然在社區(qū)的不斷努力下,CommonJS
、 AMD
、 UMD
都給業(yè)界交出了自己的答卷。
但很顯然,它們都是不得已的選擇。
瀏覽器應(yīng)該有自己的加載標(biāo)準(zhǔn)。
ES6
草案里,雖然描述了模塊應(yīng)該如何被加載,但它沒有 “加載程序的規(guī)范”。
五、SystemJs
因此 WHATWG(Web Hypertext Application Technology Working Group) 即網(wǎng)頁超文本應(yīng)用技術(shù)工作小組,提出了一套更有遠(yuǎn)見的規(guī)范:whatwg/loader。
也就是 JavaScript Loader Standard
(JS 加載標(biāo)準(zhǔn))。
本規(guī)范描述了從 JavaScript 宿主環(huán)境中加載 JavaScript 模塊的行為。它還提供了用于攔截模塊加載過程和自定義加載行為的 api。
基于此規(guī)范,SystemJS
誕生了。
SystemJS
是目前 whatwg/loader
規(guī)范的最佳實踐者。
可以看出來,system
的打包結(jié)果其實和 amd
類似,提供了全局的對象 System
,并提供了注冊的方式和統(tǒng)一的寫法。
就單純的從打包結(jié)果上,其實看不出它相比對 AMD + require.js
有什么優(yōu)勢,難道只是寫法上存在差異?
并不止于此!
相比于 require.js
,SystemJS
的 System.import('module/name')
方式允許你更為“懶”地加載模塊,這意味著你無需每次都加載一大堆的 bundle
,用戶只需要為他能看見的頁面開銷帶寬。
另外,正因為 SystemJS
是面向 whatwg/loader
規(guī)范實踐的,因此它是面向未來的模塊依賴方式。
抱歉,這個的 demo 我也沒玩明白,就不誤導(dǎo)大家了。希望有明白的大佬可以幫忙完善下demo。
六、ESM
ECMAScript modules, 也叫 ESM, Javascript 模塊化官方標(biāo)準(zhǔn)格式。
6.1 打包分析
在 ESM
被提出來之前,JavaScript 一直沒有真正意義上的模塊(module)體系。
它的規(guī)范是通過 export
命令顯式指定輸出的代碼,再通過 import
命令輸入。
// 導(dǎo)入模塊 import { foo } from 'bar'; // 導(dǎo)出命令 export { zap };
這也是我們?nèi)粘i_發(fā)中最為熟悉的寫法。
因此,esm
格式打出來的包,可讀性確實非常棒:
和閱讀我們平時所寫的業(yè)務(wù)代碼完全沒有區(qū)別。(rollup
依然沒忘記做 tree-shaking
)
6.2 如何運行
祝賀你,是這個時代的前端開發(fā)。
部分現(xiàn)代瀏覽器已經(jīng)開始實裝 <script type="module>
了,因此在瀏覽器上直接使用 esm
已成為現(xiàn)實。
但運行起來扔需要做一些前置步驟。
- 在js-modules目錄下起一個本地靜態(tài)服務(wù)
# 在js-modules目錄下起一個本地靜態(tài)服務(wù) cd js-modules && http-server
- 把
esm/bundle.js
文件的第一行修改為:
import repeat from '../../node_modules/lodash-es/repeat.js'; // 因為默認(rèn)的lodash并不是輸出的 esm 格式,因此為了demo我們需要做一些特殊處理
- 在瀏覽器打開頁面(假設(shè)端口是8080),則打開:http://127.0.0.1:8080/out/esm/index.html
這樣一來,代碼就能成功運行,控制臺就可以成功打印 42 和 42個1 了。
總結(jié):分別適合在什么場景使用?
- IIFE: 適合部分場景作為SDK進行使用,尤其是需要把自己掛到
window
上的場景。 - CommonJS: 僅node.js使用的庫。
- AMD: 只需要在瀏覽器端使用的場景。
- UMD: 既可能在瀏覽器端也可能在node.js里使用的場景。
- SystemJs: 和UMD類似。目前較出名的
Angular
用的就是它。 - ESM: 1. 還會被引用、二次編譯的場景(如組件庫等);2.瀏覽器調(diào)試場景如
vite.js
的開發(fā)時。3.對瀏覽器兼容性非常寬松的場景。
以上就是rollup輸出的6種格式詳解的詳細(xì)內(nèi)容,更多關(guān)于rollup輸出6種格式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
讓chatGPT教你如何使用taro創(chuàng)建mbox
這篇文章主要為大家介紹了讓chatGPT教你如何使用taro創(chuàng)建mbox實現(xiàn)實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03微信小程序 es6-promise.js封裝請求與處理異步進程
這篇文章主要介紹了微信小程序 es6-promise.js封裝請求與處理異步進程的相關(guān)資料,需要的朋友可以參考下2017-06-06微信小程序 后臺https域名綁定和免費的https證書申請詳解
這篇文章主要介紹了微信小程序 后臺https域名綁定和免費的https證書申請詳解的相關(guān)資料,需要的朋友可以參考下2016-11-11業(yè)務(wù)層hooks封裝useSessionStorage實例詳解
這篇文章主要為大家介紹了業(yè)務(wù)層hooks封裝useSessionStorage實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08使用compose函數(shù)優(yōu)化代碼提高可讀性及擴展性
這篇文章主要為大家介紹了使用compose函數(shù)提高代碼可讀性及擴展性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06微信小程序 wx.uploadFile在安卓手機上面the same task is working問題解決
這篇文章主要介紹了微信小程序 wx.uploadFile在安卓手機上面the same task is working問題解決的相關(guān)資料,需要的朋友可以參考下2016-12-12