<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端構(gòu)建這十年

          共 8439字,需瀏覽 17分鐘

           ·

          2021-09-28 13:37

          作者 |  李十三
          https://segmentfault.com/a/1190000040496020


          ◆  寫在前面

          前端模塊化/構(gòu)建工具從最開始的基于瀏覽器運(yùn)行時(shí)加載的 RequireJs/Sea.js 到將所有資源組裝依賴打包 webpack/rollup/parcelbundle類模塊化構(gòu)建工具,再到現(xiàn)在的bundleless基于瀏覽器原生 ES 模塊的 snowpack/vite,前端的模塊化/構(gòu)建工具發(fā)展到現(xiàn)在已經(jīng)快 10 年了。

          本文主要回顧 10 年間,前端模塊化/構(gòu)建工具的發(fā)展歷程及其實(shí)現(xiàn)原理。

          看完本文你可以學(xué)到以下知識(shí):

          • 模塊化規(guī)范方案

          • 前端構(gòu)建工具演變,對(duì)前端構(gòu)建有一個(gè)系統(tǒng)性認(rèn)識(shí)

          • 各個(gè)工具誕生歷程及所解決的問題

          • webpack/parcel/vite 的構(gòu)建流程及原理分析

          (因涉及一些歷史、趨勢(shì),本文觀點(diǎn)僅代表個(gè)人主觀看法)


          ◆  基于瀏覽器的模塊化

          · CommonJS

          一切的開始要從CommonJS規(guī)范說起。

          CommonJS 本來叫ServerJs,其目標(biāo)本來是為瀏覽器之外javascript代碼制定規(guī)范,在那時(shí)NodeJs還沒有出生,有一些零散的應(yīng)用于服務(wù)端的JavaScript代碼,但是沒有完整的生態(tài)。

          之后就是 NodeJs 從 CommonJS 社區(qū)的規(guī)范中吸取經(jīng)驗(yàn)創(chuàng)建了本身的模塊系統(tǒng)。

          · RequireJs 和 AMD

          CommonJs 是一套同步模塊導(dǎo)入規(guī)范,但是在瀏覽器上還沒法實(shí)現(xiàn)同步加載,這一套規(guī)范在瀏覽器上明顯行不通,所以基于瀏覽器的異步模塊 AMD(Asynchronous Module Definition)規(guī)范誕生。

          define(id?, dependencies?, factory);


          AMD規(guī)范采用依賴前置,先把需要用到的依賴提前寫在 dependencies 數(shù)組里,在所有依賴下載完成后再調(diào)用factory回調(diào),通過傳參來獲取模塊,同時(shí)也支持require("beta")的方式來獲取模塊,但實(shí)際上這個(gè)require只是語(yǔ)法糖,模塊并非在require的時(shí)候?qū)耄歉懊嬲f的一樣在調(diào)用factory回調(diào)之前就被執(zhí)行,關(guān)于依賴前置和執(zhí)行時(shí)機(jī)這點(diǎn)在當(dāng)時(shí)有很大的爭(zhēng)議,被 CommonJs社區(qū)所不容。

          在當(dāng)時(shí)瀏覽器上應(yīng)用CommonJs還有另外一個(gè)流派 module/2.0, 其中有BravoJS的 Modules/2.0-draft 規(guī)范和 FlyScript的 Modules/Wrappings規(guī)范。

          代碼實(shí)現(xiàn)大致如下:


          奈何RequireJs如日中天,根本爭(zhēng)不過。

          關(guān)于這段的內(nèi)容可以看玉伯的 前端模塊化開發(fā)那點(diǎn)歷史。

          · Sea.js 和 CMD

          在不斷給 RequireJs 提建議,但不斷不被采納后,玉伯結(jié)合RequireJsmodule/2.0規(guī)范寫出了基于 CMD(Common Module Definition)規(guī)范的Sea.js

          define(factory);


          在 CMD 規(guī)范中,一個(gè)模塊就是一個(gè)文件。模塊只有在被require才會(huì)被執(zhí)行。
          相比于 AMD 規(guī)范,CMD 更加簡(jiǎn)潔,而且也更加易于兼容 
          CommonJS 和 Node.js 的 Modules 規(guī)范。

          · 總結(jié)

          RequireJsSea.js都是利用動(dòng)態(tài)創(chuàng)建script來異步加載 js 模塊的。

          在作者還是前端小白使用這兩個(gè)庫(kù)的時(shí)候就很好奇它是怎么在函數(shù)調(diào)用之前就獲取到其中的依賴的,后來看了源碼后恍然大悟,沒想到就是簡(jiǎn)單的函數(shù) toString 方法


          通過對(duì)factory回調(diào)toString拿到函數(shù)的代碼字符串,然后通過正則匹配獲取require函數(shù)里面的字符串依賴

          這也是為什么二者都不允許require更換名稱或者變量賦值,也不允許依賴字符串使用變量,只能使用字符串字面量的原因

          規(guī)范之爭(zhēng)在當(dāng)時(shí)還是相當(dāng)混亂的,先有CommonJs社區(qū),然后有了 AMD/CMD 規(guī)范和 NodeJs 的 module 規(guī)范,但是當(dāng)那些CommonJs的實(shí)現(xiàn)庫(kù)逐漸沒落,并隨著NodeJs越來越火,我們口中所說的CommonJs 好像就只有 NodeJs所代表的modules了。


          ◆  bundle 類的構(gòu)建工具

          · Grunt

          隨著NodeJs的逐漸流行,基于NodeJs的自動(dòng)化構(gòu)建工具Grunt誕生

          Grunt可以幫我們自動(dòng)化處理需要反復(fù)重復(fù)的任務(wù),例如壓縮(minification)、編譯、單元測(cè)試、linting 等,還有強(qiáng)大的插件生態(tài)。

          Grunt采用配置化的思想:

          基于 nodejs 的一系列自動(dòng)化工具的出現(xiàn),也標(biāo)志著前端進(jìn)入了新的時(shí)代。

          · browserify

          browserify致力于在瀏覽器端使用CommonJs,他使用跟 NodeJs 一樣的模塊化語(yǔ)法,然后將所有依賴文件編譯到一個(gè)bundle文件,在瀏覽器通過<script>標(biāo)簽使用的,并且支持 npm 庫(kù)。


          當(dāng)時(shí)RequireJs(r.js)雖然也有了 node 端的 api 可以編譯AMD語(yǔ)法輸出到單個(gè)文件,但主流的還是使用瀏覽器端的RequireJs

          AMD / RequireJS:


          CommonJS:

          相比于 AMD 規(guī)范為瀏覽器做出的妥協(xié),在服務(wù)端的預(yù)編譯方面CommonJs的語(yǔ)法更加友好。

          常用的搭配就是 browserify + Grunt,使用Gruntbrowserify插件來構(gòu)建模塊化代碼,并對(duì)代碼進(jìn)行壓縮轉(zhuǎn)換等處理。

          · UMD

          現(xiàn)在有了RequireJs,也有了browserify但是這兩個(gè)用的是不同的模塊化規(guī)范,所以有了 UMD - 通用模塊規(guī)范,UMD 規(guī)范就是為了兼容AMDCommonJS規(guī)范。就是以下這坨東西:


          · Gulp

          上面說到Grunt是基于配置的,配置化的上手難度較高,需要了解每個(gè)配置的參數(shù),當(dāng)配置復(fù)雜度上升的時(shí)候,代碼看起來比較混亂。
          gulp 基于代碼配置和對(duì) Node.js 流的應(yīng)用使得構(gòu)建更簡(jiǎn)單、更直觀。可以配置更加復(fù)雜的任務(wù)。

          以上是一個(gè)配置browserify的例子,可以看出來非常簡(jiǎn)潔直觀。

          · webpack

          在說webpack之前,先放一下阮一峰老師的吐槽


          webpack1支持CommonJsAMD模塊化系統(tǒng),優(yōu)化依賴關(guān)系,支持分包,支持多種類型 script、image、file、css/less/sass/stylus、mocha/eslint/jshint 的打包,豐富的插件體系。

          以上的 3 個(gè)庫(kù) Grunt/Gulp/browserify 都是偏向于工具,而 webpack將以上功能都集成到一起,相比于工具它的功能大而全。

          webpack的概念更偏向于工程化,但是在當(dāng)時(shí)并沒有馬上火起來,因?yàn)楫?dāng)時(shí)的前端開發(fā)并沒有太復(fù)雜,有一些 mvc 框架但都是曇花一現(xiàn),前端的技術(shù)棧在 requireJs/sea.js、grunt/gulp、browserify、webpack 這幾個(gè)工具之間抉擇。

          webpack真正的火起來是在2015/2016,隨著ES2015ES6)發(fā)布,不止帶來了新語(yǔ)法,也帶來了屬于前端的模塊規(guī)范ES modulevue/react/Angular三大框架打得火熱,webpack2 發(fā)布:支持ES modulebabeltypescript,jsx,Angular 2 組件和 vue 組件,webpack搭配react/vue/Angular成為最佳選擇,至此前端開發(fā)離不開webpackwebpack真正成為前端工程化的核心。

          webpack的其他功能就不在這里贅述。

          · 原理

          webpack主要的三個(gè)模塊就是,后兩個(gè)也是我們經(jīng)常配置的:

          • 核心流程

          • loader

          • plugins

          webpack依賴于Tapable做事件分發(fā),內(nèi)部有大量的hooks鉤子,在Compilercompilation 核心流程中通過鉤子分發(fā)事件,在plugins中注冊(cè)鉤子,實(shí)際代碼全都由不同的內(nèi)置 plugins 來執(zhí)行,而 loader 在中間負(fù)責(zé)轉(zhuǎn)換代碼接受一個(gè)源碼處理后返回處理結(jié)果content string -> result string

          因?yàn)殂^子太多了,webpack 源碼看起來十分的繞,簡(jiǎn)單說一下大致流程:

          1. 通過命令行和 webpack.config.js 來獲取參數(shù)

          2. 創(chuàng)建compiler對(duì)象,初始化plugins

          3. 開始編譯階段,addEntry添加入口資源

          4. addModule 創(chuàng)建模塊

          5. runLoaders 執(zhí)行 loader

          6. 依賴收集,js 通過acorn解析為 AST,然后查找依賴,并重復(fù) 4 步

          7. 構(gòu)建完依賴樹后,進(jìn)入生成階段,調(diào)用compilation.seal

          8. 經(jīng)過一系列的optimize優(yōu)化依賴,生成 chunks,寫入文件

          webpack的優(yōu)點(diǎn)就不用說了,現(xiàn)在說一下 2 個(gè)缺點(diǎn):

          • 配置復(fù)雜

          • 大型項(xiàng)目構(gòu)建慢

          配置復(fù)雜這一塊一直是webpack被吐槽的一點(diǎn),主要還是過重的插件系統(tǒng),復(fù)雜的插件配置,插件文檔也不清晰,更新過快插件沒跟上或者文檔沒跟上等問題。

          比如現(xiàn)在 webpack 已經(jīng)到 5 了網(wǎng)上一搜全都是 webpack3 的文章,往往是新增一個(gè)功能,按照文檔配置完后,誒有報(bào)錯(cuò),網(wǎng)上一頓查,這里拷貝一段,那里拷貝一段,又來幾個(gè)報(bào)錯(cuò),又經(jīng)過一頓搞后終于可以運(yùn)行。

          后來針對(duì)這個(gè)問題,衍生出了前端腳手架,react出了create-react-appvue出了vue-cli,腳手架內(nèi)置了webpack開發(fā)中的常用配置,達(dá)到了 0 配置,開發(fā)者無需關(guān)心 webpack 的復(fù)雜配置。

          · rollup

          2015 年,前端的ES module發(fā)布后,rollup應(yīng)聲而出。

          rollup編譯ES6模塊,提出了Tree-shaking,根據(jù)ES module靜態(tài)語(yǔ)法特性,刪除未被實(shí)際使用的代碼,支持導(dǎo)出多種規(guī)范語(yǔ)法,并且導(dǎo)出的代碼非常簡(jiǎn)潔,如果看過 vue 的dist 目錄代碼就知道導(dǎo)出的 vue 代碼完全不影響閱讀。

          rollup的插件系統(tǒng)支持:babelCommonJstersertypescript等功能。

          相比于browserifyCommonJsrollup專注于ES module
          相比于
          webpack大而全的前端工程化,rollup專注于純javascript,大多被用作打包tool工具或library庫(kù)。

          react、vue 等庫(kù)都使用rollup打包項(xiàng)目,并且下面說到的vite也依賴rollup用作生產(chǎn)環(huán)境打包 js。

          · Tree-shaking


          以上代碼最終打包后 b 的聲明就會(huì)被刪除掉。

          這依賴ES module的靜態(tài)語(yǔ)法,在編譯階段就可以確定模塊的導(dǎo)入導(dǎo)出有哪些變量。

          CommonJs 因?yàn)槭腔谶\(yùn)行時(shí)的模塊導(dǎo)入,其導(dǎo)出的是一個(gè)整體,并且require(variable)內(nèi)容可以為變量,所以在ast編譯階段沒辦法識(shí)別為被使用的依賴。

          webpack4中也開始支持tree-shaking,但是因?yàn)闅v史原因,有太多的基于CommonJS代碼,需要額外的配置。

          · parcel

          上面提到過webpack的兩個(gè)缺點(diǎn),而parcel的誕生就是為了解決這兩個(gè)缺點(diǎn),parcel 主打極速零配置

          打包工具時(shí)間
          browserify22.98s
          webpack20.71s
          parcel9.98s
          parcel - with cache2.64s

          以上是 parcel 官方的一個(gè)數(shù)據(jù),基于一個(gè)合理大小的應(yīng)用,包含 1726 個(gè)模塊,6.5M 未壓縮大小。在一臺(tái)有 4 個(gè)物理核心 CPU 的 2016 MacBook Pro 上構(gòu)建。

          parcel 使用 worker 進(jìn)程去啟用多核編譯,并且使用文件緩存。

          parcel 支持 0 配置,內(nèi)置了 html、babel、typescript、less、sass、vue等功能,無需配置,并且不同于webpack只能將 js 文件作為入口,在 parcel 中萬物皆資源,所以 html 文件 css 文件都可以作為入口來打包。

          所以不需要webpack的復(fù)雜配置,只需要一個(gè)parcel index.html命令就可以直接起一個(gè)自帶熱更新的server來開發(fā)vue/react項(xiàng)目。

          parcel 也有它的缺點(diǎn):

          • 0 配置的代價(jià),0 配置是好,但是如果想要配置一些復(fù)雜的配置就很麻煩。

          • 生態(tài),相比于webpack比較小眾,如果遇到錯(cuò)誤查找解決方案比較麻煩。

          · 原理

          1. commander 獲取命令

          2. 啟動(dòng) server 服務(wù),啟動(dòng) watch監(jiān)聽文件,啟動(dòng) WebSocket 服務(wù)用于 hmr,啟動(dòng)多線程

          3. 如果是第一次啟動(dòng),針對(duì)入口文件開始編譯

          4. 根據(jù)擴(kuò)展名生成對(duì)應(yīng)asset資源,例如jsAssetcssAssetvueAsset,如果parcel識(shí)別 less 文件后項(xiàng)目?jī)?nèi)如果沒有 less 庫(kù)會(huì)自動(dòng)安裝

          5. 讀取緩存,如果有緩存跳到第 7 步

          6. 多線程編譯文件,調(diào)用 asset 內(nèi)方法parse -> ast -> 收集依賴 -> transform(轉(zhuǎn)換代碼) -> generate(生成代碼),在這個(gè)過程中收集到依賴,編譯完結(jié)果寫入緩存

          7. 編譯依賴文件,重復(fù)第 4 步開始

          8. createBundleTree 創(chuàng)建依賴樹,替換 hash 等,package打包生成最終代碼

          9. 當(dāng)watch文件發(fā)生變化,重復(fù)第 4 步,并將結(jié)果 7 通過WebSocket發(fā)送到瀏覽器,進(jìn)行熱更新。

          一個(gè)完整的模塊化打包工具就以上功能和流程。


          ◆  基于瀏覽器 ES 模塊的構(gòu)建工具

          browserifywebpackrollupparcel這些工具的思想都是遞歸循環(huán)依賴,然后組裝成依賴樹,優(yōu)化完依賴樹后生成代碼。
          但是這樣做的缺點(diǎn)就是慢,需要遍歷完所有依賴,即使 
          parcel 利用了多核,webpack 也支持多線程,在打包大型項(xiàng)目的時(shí)候依然慢可能會(huì)用上幾分鐘,存在性能瓶頸。

          所以基于瀏覽器原生 ESM 的運(yùn)行時(shí)打包工具出現(xiàn):



          僅打包屏幕中用到的資源,而不用打包整個(gè)項(xiàng)目,開發(fā)時(shí)的體驗(yàn)相比于 bundle類的工具只能用極速來形容。
          (實(shí)際生產(chǎn)環(huán)境打包依然會(huì)構(gòu)建依賴方式打包)

          · snowpack 和 vite

          因?yàn)?nbsp;snowpack 和 vite 比較類似,都是bundleless所以一起拿來說,區(qū)別可以看一下 vite 和 snowpack 區(qū)別,這里就不贅述了。

          bundleless類運(yùn)行時(shí)打包工具的啟動(dòng)速度是毫秒級(jí)的,因?yàn)椴恍枰虬魏蝺?nèi)容,只需要起兩個(gè)server,一個(gè)用于頁(yè)面加載,另一個(gè)用于HMRWebSocket,當(dāng)瀏覽器發(fā)出原生的ES module請(qǐng)求,server收到請(qǐng)求只需編譯當(dāng)前文件后返回給瀏覽器不需要管依賴。

          bundleless工具在生產(chǎn)環(huán)境打包的時(shí)候依然bundle構(gòu)建所以依賴視圖的方式,vite 是利用 rollup 打包生產(chǎn)環(huán)境的 js 的。

          原理拿 vite 舉例:

          vite在啟動(dòng)服務(wù)器后,會(huì)預(yù)先以所有 html 為入口,使用 esbuild 編譯一遍,把所有的 node_modules 下的依賴編譯并緩存起來,例如vue緩存為單個(gè)文件。

          當(dāng)打開在瀏覽器中輸入鏈接,渲染index.html文件的時(shí)候,利用瀏覽器自帶的ES module來請(qǐng)求文件。


          vite 收到一個(gè)src/main.js的 http 文件請(qǐng)求,使用esbuild開始編譯main.js,這里不進(jìn)行main.js里面的依賴編譯。

          瀏覽器獲取到并編譯main.js后,再次發(fā)出 2 個(gè)請(qǐng)求,一個(gè)是 vue 的請(qǐng)求,因?yàn)榍懊嬉呀?jīng)說了 vue 被預(yù)先緩存下來,直接返回緩存給瀏覽器,另一個(gè)是App.vue文件,這個(gè)需要@vitejs/plugin-vue來編譯,編譯完成后返回結(jié)果給瀏覽器(@vitejs/plugin-vue會(huì)在腳手架創(chuàng)建模板的時(shí)候自動(dòng)配置)。

          因?yàn)槭腔跒g覽器的ES module,所以編譯過程中需要把一些 CommonJsUMD 的模塊都轉(zhuǎn)成 ESM

          Vite 同時(shí)利用 HTTP 頭來加速整個(gè)頁(yè)面的重新加載(再次讓瀏覽器為我們做更多事情):源碼模塊的請(qǐng)求會(huì)根據(jù) 304 Not Modified 進(jìn)行協(xié)商緩存,而依賴模塊請(qǐng)求則會(huì)通過 Cache-Control: max-age=31536000,immutable 進(jìn)行強(qiáng)緩存,因此一旦被緩存它們將不需要再次請(qǐng)求,即使緩存失效只要服務(wù)沒有被殺死,編譯結(jié)果依然保存在程序內(nèi)存中也會(huì)很快返回。

          上面多次提到了esbuildesbuild使用 go 語(yǔ)言編寫,所以在 i/o 和運(yùn)算運(yùn)行速度上比解釋性語(yǔ)言 NodeJs 快得多,esbuild 號(hào)稱速度是 node 寫的其他工具的 10~100 倍。

          ES module 依賴運(yùn)行時(shí)編譯的概念 + esbuild + 緩存 讓 vite 的速度遠(yuǎn)遠(yuǎn)甩開其他構(gòu)建工具。

          · 總結(jié)

          簡(jiǎn)單的匯總:

          • 前端運(yùn)行時(shí)模塊化

            • RequireJs AMD 規(guī)范

            • sea.js CMD 規(guī)范

          • 自動(dòng)化工具

            • Grunt 基于配置

            • Gulp 基于代碼和文件流

          • 模塊化

            • browserify 基于CommonJs規(guī)范只負(fù)責(zé)模塊化

            • rollup 基于ES moduletree shaking優(yōu)化代碼,支持多種規(guī)范導(dǎo)出,可通過插件集成壓縮、編譯、commonjs 語(yǔ)法 等功能

          • 工程化

            • webpack 大而全的模塊化構(gòu)建工具

            • parcel 極速 0 配置的模塊化構(gòu)建工具

            • snowpack/vite ESM運(yùn)行時(shí)模塊化構(gòu)建工具

          這 10 年,前端的構(gòu)建工具隨著 nodejs 的逐漸成熟衍生出一系列的工具,除了文中列舉的還有一些其他的工具,或者基于這些工具二次封裝,在nodejs出現(xiàn)之前前端也不是沒有構(gòu)建工具雖然很少,只能說nodejs的出現(xiàn)讓更多人可以參與進(jìn)來,尤其是前端可以使用本身熟悉的語(yǔ)言參與到開發(fā)工具使用工具中,npm 上至今已經(jīng)有 17 萬個(gè)包,周下載量 300 億。

          在這個(gè)過程中也有些模塊化歷史遺留問題,我們現(xiàn)在還在使用著 UMD 規(guī)范庫(kù)來兼容這 AMD 規(guī)范,npm 的包大都是基于CommonJs,不得不兼容ESMCommonJs

          webpack統(tǒng)治前端已經(jīng) 5 年,人們提到開發(fā)項(xiàng)目只會(huì)想到 webpack,而下一個(gè) 5 年會(huì)由誰來替代?snowpack/vite嗎,當(dāng)打包速度達(dá)到 0 秒后,未來有沒有可能出現(xiàn)新一代的構(gòu)建工具?下一個(gè) 10 年前端又會(huì)有什么變化?

          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉乱伦 | 麻豆三级片在线播放 | 欧美青青视频手机在线 | 亚洲成人av在线观看 | 国产高清区|