文章來源于公眾號:驚天碼盜 ,作者:雞湯小弟
前言
通俗的講:JS這門語言,在設(shè)計之處就沒有模塊體系,所以他會經(jīng)歷模塊化演變,直到2015年,TC39(一群瀏覽器廠商代表組成的委員會)發(fā)布了ES6規(guī)范,ES Modules才被世人所知,也就是ESM。而在模塊化演變過程中,模塊化本身暴露的問題也越來越明顯。項目中的模塊越來越多,在管理上造成了混亂,所以迫切需要一些工具解決各種模塊類型混亂的問題。webpack和vite等工具就是用來解決這些問題的。
從瀏覽器的發(fā)展,JS的流行,到模塊化探索,再到構(gòu)建工具的區(qū)別,這里面涉及的知識點很多,在這里簡單的列幾點:
- 瀏覽器的運行機制
- 模塊化的演變
- 模塊化工具的演變
- webpack的演變
- vite的誕生
- 前端開發(fā)者的選擇
瀏覽器與js
瀏覽器在渲染頁面前會解析 HTML文檔,通過 HTML 解析器輸出 DOM 元素和屬性。
然后構(gòu)建 DOM tree 和 CSSOM tree。這兩個模型構(gòu)建時是相互獨立的。
最后 CSSOM tree 和 DOM tree 合并成渲染樹,然后用于計算每個可見元素的布局,并輸出繪制流程,將像素渲染到屏幕上。
如果 DOM 或 CSSOM 被修改,需要重新構(gòu)建,以確定哪些像素需要在屏幕上進(jìn)行重新渲染,這里會涉及到兩個知識點:repeat
和reflow
。
而 JS 允許查詢和修改 DOM 和 CSSOM,同樣 JS 也會阻塞 DOM 和 CSSOM 的構(gòu)建和渲染,所以我們要把 JS 放在 HTML 的底部加載。
現(xiàn)在的瀏覽器的內(nèi)核大多分為兩部分:渲染引擎和JS引擎。
渲染引擎,負(fù)責(zé)對網(wǎng)頁語法的解釋并渲染網(wǎng)頁。我們常說的gecko引擎、"斯巴阿"(edge)、presto引擎、webkit、blink引擎等都是渲染引擎。
JS引擎,是專用于對JS進(jìn)行解釋、編譯和執(zhí)行,使網(wǎng)頁達(dá)到動態(tài)得到效果。常說的V8引擎就是JS引擎。
js與模塊化
在 js 模塊化進(jìn)程中,CommonJS 常用在服務(wù)端,AMD、CMD規(guī)范常用在客戶端。
起初,CommonJS專攻服務(wù)端,原名叫 ServerJS,后來,為了統(tǒng)一前后端而改名 CommonJs 。而就在社區(qū)討論下一版規(guī)范的時候,內(nèi)部發(fā)生了比較大的分歧,分裂出三個主張,漸漸的形成三個不同的派別:
- 在現(xiàn)有CommonJs的基礎(chǔ)上滿足瀏覽器的需要,把現(xiàn)有模塊化轉(zhuǎn)化為適合瀏覽器端即可。制定了*Modules/Transport*(http://wiki.commonjs.org/wiki/Modules/Transport)規(guī)范,browserify就是這樣一個工具,可以把nodejs的模塊編譯成瀏覽器可用的模塊。
- 還有一波人,認(rèn)為瀏覽器與服務(wù)器環(huán)境差別太大,不能沿用舊的標(biāo)準(zhǔn),而且瀏覽器必須異步加載代碼,那么模塊在定義的時候就必須指明所依賴的模塊,然后把本模塊的代碼寫在回調(diào)函數(shù)里。模塊的加載也是通過下載-回調(diào)這樣得到過程來進(jìn)行,這個思想也是\A******MD****的基礎(chǔ)(https://github.com/amdjs/amdjs-api/wiki/AMD)。
- 最后一波人,既不想丟掉舊的規(guī)范,也不想像AMD那樣重來。他們認(rèn)為CommonJs的一些理念是好的,比如通過require來聲明依賴,新的規(guī)范應(yīng)兼容這些,同時也認(rèn)為AMD也有可取之處,比如預(yù)加載等。最終他們制定來一個*Modules/Wrappings*規(guī)范(http://wiki.commonjs.org/wiki/Modules/Wrappings)。
RequireJs 的出現(xiàn),迅速被廣大開發(fā)者接受。但是 RequireJs 也有被吐槽的地方,*預(yù)先下載沒有什么爭議,預(yù)先執(zhí)行是否需要*?如果一個模塊依賴十個其他模塊,那么在本模塊的代碼執(zhí)行之前,要先把其他十個模塊的代碼都執(zhí)行一遍,不管這些模塊是不是馬上會被用到。這個性能消耗是不不容忽視的。
針對 RequireJs 的不優(yōu)雅的地方,國內(nèi)大牛玉伯寫出了 SeaJs。SeaJs 全面擁抱 Modules/Wrapping 規(guī)范,但也沒有完全按照 Modules/Wrapping 規(guī)范,seajs 并沒有使用 declare 來定義模塊,而是使用和 requirejs 一樣的define。依賴會被預(yù)先下載,在需要執(zhí)行的時候執(zhí)行。同樣 seajs 也實現(xiàn)了在需要執(zhí)行的時候下載這一功能,提供了 require.async API,并支持 CommonJs 和 RequireJs 關(guān)于模塊對外暴露 API 的方式。鑒于 seajs 融合了太多的東西,已經(jīng)無法說它遵循哪個規(guī)范了,所以玉伯干脆就自立門戶,起名曰CMD(Common Module Definition)規(guī)范。
以上所講的 CommonJs 、AMD、CMD 等等,只是社區(qū)制定而非官方。官方一看社區(qū)呼聲這么高,js模塊化終于在2015年發(fā)布ES6正式版。從此ESM誕生了。
ES6 模塊的設(shè)計思想是盡量的靜態(tài)化,使得編譯時就能確定模塊的依賴關(guān)系,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時確定這些東西。比如,CommonJS 模塊就是對象,輸入時必須查找對象屬性。
CommonJs 是運行時加載,ES6是編譯時加載。可能你對編譯和運行這兩個關(guān)鍵詞有點不理解。CommonJs 是先加載模塊,輸出一個對象,輸出后內(nèi)部不會再變化。而 ES6 是語言層面的改變,它 輸出的是一個引用,提到引用就想到了JS的數(shù)據(jù)類型-引用類型 。等執(zhí)行的時候才會取值輸出。
模塊化與構(gòu)建工具
模塊的加載和傳輸,我們首先能想到兩種極端的方式:
- 一種是每個模塊文件都單獨請求
- 另一種是把所有模塊打包成一個文件然后只請求一次
顯而易見,每個模塊都發(fā)起單獨的請求造成了請求次數(shù)過多,導(dǎo)致應(yīng)用啟動速度慢;一次請求加載所有模塊導(dǎo)致流量浪費、初始化過程慢。這兩種方式都不是好的解決方案,它們過于簡單粗暴。
分塊傳輸,按需進(jìn)行懶加載,在實際用到某些模塊的時候再增量更新,才是較為合理的模塊加載方案。
要實現(xiàn)模塊的按需加載,就需要一個對整個代碼庫中的模塊進(jìn)行靜態(tài)分析、編譯打包的過程。
但是我們的項目不單單只有js?。∮袌D片、樣式、字體、html模版等資源。在編譯的時候,要對整個代碼進(jìn)行靜態(tài)分析,分析出各個模塊的類型和它們依賴關(guān)系,然后將不同類型的模塊提交給適配的加載器來處理。所以構(gòu)建工具產(chǎn)生了。
關(guān)于構(gòu)建工具可以查看之前寫的一篇文章《關(guān)于前端構(gòu)建工具的大雜燴》。
webpack脫穎而出是因為它的理念:一切皆為模塊。
關(guān)于webpack還有一個故事,Tobias Koppers是 Webpack 倉庫創(chuàng)建者,Tobias 的網(wǎng)絡(luò)昵稱叫 sokra ,后面我們就叫 sokra,sokra 沒有寫過 web 頁面,這個就很有意思了,一個沒有寫過web頁面的人發(fā)明了當(dāng)代web開發(fā)的基石。
sokra 一開始是寫 Java 的, Java 里面有個很出名的技術(shù)叫GWT(Google Web Toolkit),GWT 是把 Java 代碼轉(zhuǎn)換成JavaScript,也就是讓后端來寫前端,本質(zhì)上也是在AST層面對代碼做一層轉(zhuǎn)換,Babel 也是干這件事的,但是 GWT 這門技術(shù)沒有流行起來,后面 Google 也不推廣了。
GWT里面有個feature叫「code splitting」,于是他當(dāng)時給用來做前端項目 Bundle 的 node.js 庫 modules-webmake 提了一個 issue,希望他們能實現(xiàn),「code splitting」就是Webpack現(xiàn)在提供的主要功能,也是當(dāng)代前端的基石。
大多技術(shù)的產(chǎn)生都離不開前人的經(jīng)驗,都是把原有的技術(shù),改巴改巴就成了一個新的技術(shù),所以說「 造輪子 」是技術(shù)發(fā)展不可避免的一環(huán)。
webpack與vite
webpack 與 vite 本質(zhì)上的區(qū)別就在于「 按需加載 」。
webpack 無論怎么提倡按需加載,在 ESM 面前都是假的按需加載。因為在 ESM 之前瀏覽器并不支持模塊化,上面我們聊的模塊化大多都是社區(qū)的產(chǎn)物,不是本身語言層面的支持。而 ESM 的誕生,標(biāo)志著js有了自己的模塊體系。
有一個很好的例子可以說明,在 script 標(biāo)簽中添加type=“modules”,引入 js 文件,在 js 文件中直接使用 import 導(dǎo)入,瀏覽器是可以正常運行的,在這里我們不需要依賴工具幫我們實現(xiàn)瀏覽器對模塊化的支持。
vite 內(nèi)部的實現(xiàn)是把其他資源都編譯成 js 文件,換句話就是,把圖片、樣式、字體、vue文件等等其他資源模塊,都轉(zhuǎn)化JS模塊,讓瀏覽器加載編譯。當(dāng)然你可能會想到 webpack 也會啊,這不正是技術(shù)的本性嗎。難道 vite 的誕生就不能借鑒其他構(gòu)建工具嗎。
當(dāng)然很多同學(xué)都會有個疑問:vite會取代webpack嗎?
不會,vite 的發(fā)展才剛剛起步,而瀏覽器的多樣性也導(dǎo)致 ESM 規(guī)范落地沒有那么快。webpack 的社區(qū)、生態(tài)已經(jīng)非常完善,這不是 vite 一個剛出生的小孩可以媲美的,但是 vite 的未來是一定會比 webpack 更加強大,這是科技發(fā)展所必然經(jīng)歷的,一代更比一代強。
以上就是W3Cschool編程獅
關(guān)于瀏覽器、ESM規(guī)范、模塊化、webpack和vite之間聯(lián)系?的相關(guān)介紹了,希望對大家有所幫助。