前言
Bundle or Bundleless?自 2015 年 ESM 標準釋出後,路線之爭就開始逐步升溫。轉眼間,時間已來到 2021 年。如果白酒的車你錯過了,那麼不妨看看 Bundleless,或許它就是前端圈的下一位「茅臺」。
前端構建當下的問題不得不說,曾經把自己定位為「打包器」的 Webpack,如今已形成強大的構建生態,儼然一統江湖。但前端構建的道路還遠沒有走到最後。隨著業務的發展,前端工程的複雜度越來越高,構建方面的也開始暴露出新的問題。
構建時間逐步拉長相信許多前端同學剛入行時,都經歷過「重新整理一下全都有」的幸福時光:寫幾個 HTML 標籤,寫幾句內嵌程式碼,瀏覽器中就會呈現出美妙的 UI。而如今,業務工程越來越複雜,程式碼量連年增長,構建的時間也越來越長。曾經「秒級構建」的前端,終究躋身「分鐘級構建」的圈子了。
前端工程構建時間的拉長,自然使得前端開發者在日常業務工作中的狀態。
模組標準引領方向如果我們縱觀前端領域的發展,就可以看到標準是如何推動各大瀏覽器建設,整個前端生態又是如何發生的變化。
2002 年,AJAX 推出,此後前端承擔的工作越來越多。彼時,瀏覽器廠各行其是,因此相容性是當時的主要問題。於是 2006 年,jQuery 的出現進一步帶動了前端的發展。
2009 - 2011 年,CommonJS、AMD、UMD 相繼為 JS 帶來了模組規範。同一時期,部分遵循 CommonJS 的 Node 為 JS 帶來了執行環境,為前端工程化的解鎖奠定基礎:
模組載入工具開始湧現,如 RequireJS、SeaJS 等包管理工具,如 npm、spm 等輕量的打包器開始出現,如 Browserify任務工具開始出現,如 GulpAngular、React、Vue 等的相繼火爆,也推動了前端的又一波浪潮:它們的發展提高了前端在業務中的表達能力,並向更高程度的工程化提出訴求。
2015 年,HTTP/2.0 推出,同時 JS 迎來了自己的模組標準 ESM:ES2015 一發布,Babel 就讓開發者們用上了 ES Module,真香。於是此後的幾年,Webpack & Babel 幾乎成了前端工程化的代名詞,甚至讓人以為,前端工程化已成定局。
2018 年,Chrome、Safari、Firefox 相繼完成了對 ESM 的支援。但得益於 Webpack 生態對 CommonJS、AMD、UMD 的支援,開發者們對 ESM 的享用更多是在編碼階段和一定程度的 Tree-shaking,在構建層面並沒有直接的得利。
總結當下時間點,出現了新的契機:其一,「工程體積的日益增長」與「亟待提升的構建效能」之間的矛盾;其二,「先進的前端模組標準」與「落後的前端模組規範」之間的矛盾。
Bundleless 為什麼是答案Bundleless 說到底,就是指無打包構建,與我們當下流行的打包構建相對,而打包器則是我們前端開發者用於將 JS 模組打包成單一的、可在瀏覽器內執行的檔案的工具。
為什麼過去需要打包這一問題在社群也有非常多的總結,概況來講,主要包括以下理由:
HTTP/1.1 各瀏覽器有並行連線限制瀏覽器不支援模組系統(如 CommonJS 包不能直接在瀏覽器執行)程式碼依賴關係與順序管理HTTP/1.1 各瀏覽器預設並行連線數
瀏覽器 |
Firefox 3+ |
Opera 12 |
Safari 5 |
IE 7 |
IE 10 |
Edge | Chrome |
並行連線 |
6 |
6 |
6 |
2 |
8 |
6 |
6 |
近幾年時間,標準的確立、瀏覽器大廠和前端生態的跟進,使得「不打包」成為可能:
HTTP/2.0 多路並用各大瀏覽器逐一支援 ESM越來越多的 npm 包擁抱 ESM(儘管很多包的依賴並不是)我們可以發現:
透過打包來減少網路請求數量從而提高效能的最佳化手段理論上在 HTTP/2.0 下會變得不再必要;ESM 標準的推廣和各大瀏覽器的支援:讓模組程式碼可以直接在瀏覽器中執行原生的解決了程式碼依賴和複用的問題會進一步推動越來越多的 npm 包支援 ESM,甚至會出現新的包管理或分發方式Bundleless vs Bundle模組載入的對比如果是打包式構建,在模組載入時,實際上載入的是若干模組的集合。這種方式的優點是以少量的請求連線數完成 JS 指令碼的下載。如果是無打包式構建,模組的載入則是基於原生模組方案,直接獲取具體的模組指令碼。
本地開發構建的對比如果是打包式構建,無論是專案啟動還是檔案變更,都需要完整地走一遍打包過程。以 Webpack 為例,我們就會經歷依賴分析、程式碼轉譯和打包的過程,哪怕我們只是簡單地修改了一行文案。當然,Split chunk 會在一定程度上緩解這一問題,但力度仍然偏大。
而無打包式構建,在啟動過程中基本只是啟動服務(當然不同的 Bundleless 方案可能還會做些其他的工作),而不用對業務程式碼進行依賴分析、打包,ESM 會幫助我們在瀏覽器中完成依賴的分析。當檔案發生變更時,本地開發服務只是提供了檔案的對映,只需要重新轉譯對應的檔案,並重新替換即可。
結論以上,我們可以知道:
打包過程的必要性已降低擁抱 ESM 是未來趨勢社群在領域內的工作概覽前端構建並不只是構建工具的問題。事實上,「構建」和「分發」共同組成了前端工程的構建,只不過通常情況下,我們是透過 npm install 將三方包下載下來,並打包到構建結果中實現的。
構建可以分為兩種型別。
一種是基於服務的構建方式,通常服務於實際生產。我們可以再細分成本地服務構建和遠端服務構建。本地服務構建就是我們常規的操作,目前基本已經被 Webpack 統治,是 Bundle 方案的代表;Snowpack、Vite、Web Dev Server 則是目前非常火的 Bundleless 方案,近一年的時間裡勢頭迅猛。遠端服務構建則是依託雲能力的玩法,把構建過程放在服務端完成,從而把本地的開發流程搬到 Web 上,並給出與本地服務構建基本一致的體驗。
另一種是基於瀏覽器的構建方式,通常面向 Demo 的快速搭建或預覽方案。Codesandbox、StackBlitz、CodePen 和 Riddle 是業內較出色的方案,整體是在瀏覽器端實現程式碼的編譯、打包、構建和執行。當然,具體到各個方案的細節,通常對服務還是有一定依賴。
目前來看,100% 在瀏覽器端進行打包、構建,現階段並不是最理想的方案。隨著 Bundleless 的發展,瀏覽器 Bundleless 和包分發平臺的結合會得到進一步的發展,並逐步影響前端工程的架構。
Snowpack原理這一部分在 Snowpack 的文件上有一定的講解。整體來說,Snowpack 儘可能利用了現有前端生態的工具,對三方包打包來壓縮依賴鏈,對業務程式碼走無構建路線,以此提供 Bundleless 體系下的開發體驗。值得一提的是,Snowpack 的構建速度很快,這得益於內建打包工具 esbuild 的發展。
Snowpack vs Webpack我們不妨將其與 Webpack 進行一個對比。
啟動時間,如上文所說,Webpack 會完整打包整個專案,因此隨著專案體積的增長,啟動時間也會越發漫長;而 Snowpack 主要是啟動本地的服務,對於 Snowpack 來說,儘管初次啟動時會分析三方依賴,並透過 Rollup 將其進行打包,但是打包結果會快取在 node_modules/.cache/snowpack/development 目錄下,後續就可以享受到飛一樣的啟動。
構建時間,對於 Webpack 而言,構建時長會隨著專案體積整體以線性方式增長;而 Snowpack 的模式則是 O(1) 的複雜度(當然這裡也有點小噱頭)。
快取能力,可以說 Webpack 的快取利用率尚有最佳化空間。儘管我們可以透過 Split Chunk,合理的劃分打包方式,但如果我們只是改了一句文案,那麼使用者側仍然會重新獲取對應 Chunk 資源。Snowpack 的快取利用率近乎完美。業務程式碼檔案發生變更,直接替代產出資源,其他全部可返回快取;三方依賴包,如 react.js,則直接更新該包,其他全部可用快取。不過,儘管生產環境最佳化可以做 Tree-shaking,但是業務程式碼本身,Snowpack 並不會做處理(只是以 ESM 來對待),即使使用 Snowpack 生態的 Webpack 外掛來做生產環境的構建也是如此,所以是近乎完美,這是相對於理想的 DCE(dead code elimination) 而言的。Webpack 在生產環境會把沒有使用到的程式碼 Tree-shaking 掉,不可謂不強大。
至於除錯體驗,因為 Webpack 需要打包,因此在除錯的時候我們依賴 SourceMap 來幫助我們看到原始碼。但對於 Snowpack 而言(實際上 Bundleless 模式都是如此),我們並不強依賴於 SourceMap,如果轉譯後的程式碼閱讀無礙(ES6 其實還好嘛),就可以直接進行單檔案除錯。
不過即使 Snowpack 有千番好,整個 Bundleless 生態還不足以取代 Webpack。Webpack 終究是一代神器,只是我們明白 Bundleless 也確實代表了未來。