我認爲,「什麼是前端工程化」——這是一個很好的問題,但同時也是一個很是「務虛」的問題。前端
由於前端工程化是一個極度寬泛且宏大的概念,咱們很難去下一個定義,也沒法給出一個樣例來解釋。我試圖從工程(構建)工具對比和一個線上 bug 的處理來側面說明。vue
提到工程化(構建)工具,做爲經驗豐富的前端開發者,相信你能列舉出不一樣時代的表明:從 Browserify + Gulp 到 Parcel,從 Webpack 到 Rollup,甚至 @尤雨溪編寫的 Vite,相信你也並不陌生。沒錯,前端發展到如今,工程化工具琳琅滿目。但不少工具的實現和設計很是複雜,甚至出現了「面向 webpack 編程」的調侃。node
ToolingReport 是由 Chrome core team 核心成員以及業內著名開發者打造的構建工具比對平臺。這個平臺對比了 Webpack v四、Rollup v二、Parcel v二、Browserify + Gulp 在不一樣維度下的表現,以下圖所示:webpack
測評經過的 test 得分只是一個方面,實際狀況也和不一樣構建工具的設計目標有關。好比,Webpack 的構建主要依賴了插件和 loader,所以它的能力雖然強大,但配置信息較爲煩瑣。而 Parcel 的設計目標之一就是零配置,開箱即用,可是在功能的集成上相對有限。web
但從工程化的角度出發,咱們仍是從上面的分數分析,來看看這些分數評測的維度。這些分數來自如下 6 個維度的評測:vue-cli
和工程化主題相關的是:這 6 個維度究竟是什麼,爲何它們能做爲考量指標被選取爲評測參考標準?下面咱們逐一進行分析。npm
Code Splitting,即代碼分割。這意味着在構建打包時,可以將靜態資源拆分,所以在頁面加載時,實現最合理的按需加載策略。編程
實際上,Code Splitting 是一個很深的話題。好比:不一樣模塊間的代碼分割機制可否支持不一樣的上下文環境(Web worker 環境等特殊上下文狀況),如何實現對 Dynamic Import 語法特性的支持,應用配置多入口/單入口時是否支持重複模塊的抽取並打包,代碼模塊間是否支持 Living Bindings(若是被依賴的 module 中的值發生了變化,則會映射到全部依賴該值的模塊中)。前端工程化
總之,Code Splitting 直接決定了前端的靜態資源產出狀況,影響着項目應用的性能表現。是前端工程化這顆大樹的一個分支。瀏覽器
Hashing,即對打包資源進行版本信息映射。這個話題背後的重要技術點是最合理地利用緩存機制。咱們知道有效的緩存策略將直接影響頁面加載表現,決定用戶體驗。那麼對於前端工程化來講,爲了實現更合理的 hash 機制,工具就須要分析各類打包資源,導出模塊間依賴關係,依據依賴關係上下文決定產出包的哈希值。由於一個資源的變更,將會引發其依賴下游的關聯資源變更,所以工程工具進行打包的前提就是對各個模塊依賴關係進行分析,並根據依賴關係,支持開發者自行定義哈希策略。好比,Webpack 提供的不一樣類型 hash 的區別:hash/chunkhash/contenthash,這三種 hashing 策略你都瞭解嗎?爲何有這三種策略的設計呢?具體我就不展開了。
Output Module Formats,工程輸出的模塊化方式也須要更加靈活,好比開發者可配置 ESM、CommonJS 等規範的構建內容導出。
Transformations,前端工程化離不開編譯/轉義過程。好比對 JavaScript 代碼的壓縮、對無用代碼的刪除(DCE)等。這裏須要站在工程化視覺上注意的是,咱們在設計構建工具時,對於相似 JSX 的編譯、.vue 文件的編譯,不會內置到工具當中,而是利用 Babel 等社區能力,「無縫融合」到工程化流程裏。工程化工具只作份內的事情,其餘擴展能力經過插件化機制來完成,顯然是一個很是工程化的設計。
其餘 Importing Modules 以及 Non-JavaScript Resources 我很少說了,雖然這是評測工程化工具的幾個大方向,但每個都是前端工程化的重要主題。
這一部分,讓咱們以一篇文章《報告老闆,咱們的 H5 頁面在 iOS 11 系統上白屏了!》分析,我先簡單梳理和總結一下文章表達的內容,讀者看我總結便可:
...
擴展運算符;...
擴展運算符。如今問題找到了,或許直接將出現問題的公共庫代碼用 Babel 進行編譯降級就能夠了。在文中環境下,須要在 vue.config.js
中加入對問題公共庫 module-name/library-name
的 Babel 編譯流程:
transpileDependencies: [
'module-name/library-name' // 出現問題的那個庫
],
vue-cli 對 transpileDependencies 也有以下說明:
默認狀況下 babel-loader 會忽略全部
node_modules
中的文件。若是你想要經過 Babel 顯式轉譯一個依賴,能夠在這個選項中列出來。
按照上述操做,卻獲得了新的報錯:Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'
。
究其緣由,module-name/library-name
這個庫對外輸出的是 CommonJS 類型源碼,咱們對該庫進行編譯後,項目基礎設施中會經過 babel-transform-runtime 在編譯時增長 helper 代碼,而這些 helper 使用的是 import 引入。最終編譯結果出現了 ESM 包含 CommonJS 的狀況,是不會被 Webpack 處理的。
我再次分析下出現的新的問題:
爲了適配上述問題,Babel 設置了 sourceType
屬性,sourceType:unambiguous
表示 Babel 會根據文件上下文(好比是否含有 import/export)來決定是否按照 ESM 語法處理文件。
這時候就須要配置 Babel 內容了:
module.exports = {
... // 省略的配置
sourceType: 'unambiguous',
... // 省略的配置
}
可是這種作法在工程上並不推薦,上述更改方式對全部編譯文件都生效,但也增長了編譯成本(由於設置 sourceType:unambiguous
後,編譯時須要作的事情更多),還有個潛在問題:
Unambiguous can be quite useful in contexts where the type is unknown, but it can lead to false matches because it's perfectly valid to have a module file that does not use import/export statements.
翻譯過來,就是說並非全部的 ESM 模塊(這裏指使用 ESNext 特性的文件)都含有 import/export,所以即使某個待編譯文件屬於 ESM 模塊,也可能被 Babel 錯誤地判斷爲 CommonJS 模塊而引起誤判。
**基於這一點,一個更合適的作法是:**只對目標第三方庫 'module-name/library-name'
使用 sourceType:unambiguous
,這時 Babel overrides 屬性就派上用場了:
Allows users to provide an array of options that will be merged into the current configuration one at a time. This feature is best used alongside the "test"/"include"/"exclude" options to provide conditions for which an override should apply.
具體使用方式:
module.exports = {
... // 省略的配置
overrides: [
{ include: './node_modules/module-name/library-name/name.common.js', // 使用的第三方庫
sourceType: 'unambiguous'
}
],
... // 省略的配置
};
至此,這個「iOS 11 系統白屏」問題就算告一段落了(你有沒有被各類配置和設計搞得雲裏霧裏?)。
我整理了解決路線,以下圖所示:
咱們回過頭再來看這個問題,問題其實出如今一個公共庫上,於是前端生態的混亂和複雜也許是更本質的緣由,但這都轉嫁爲前端工程化的難點。
咱們進一步思考:
被動地發現問題、解決問題只會讓咱們被「牽着鼻子走」——這不是咱們的目的。感興趣的讀者能夠點贊,關注,我會很快輸出更多關於「前端工程化」的內容。
對於不少前端工程師來講,你可能配置過 Babel/Webpack,也可能看過一些關於 Babel/Webpack 插件或原理的文章。但我認爲,經過閱讀幾篇 Babel/Webpack 插件編寫甚至 AST 分析的文章並不能讓咱們真正掌握前端工程化。這也徹底徹底不是前端工程化的要義。
「配置工程師」只是咱們的起點。做爲前端開發者,你可能會被繁瑣的配置和工具所困擾,本身的終端脆弱無比,出現各類報錯。此時,你可能花費了一天的時間,經過 Google 找到了最終的配置解法;或者經過:
rm -rf node_modules + npm install + npm run dev
規避了問題。可是解決之道卻沒搞清楚,得過且過,從此依然被相似的困境襲擾。
當咱們對配置、工具、構建流程、架構設計、生產發佈等環節的各類挑戰和問題能有系統化的思考時,「前端工程化」天然也不會再是一個困惑。
其實很抱歉我沒法回答題主這個宏大的問題,我本身也受此困擾,僅以兩個小的細節方面拋磚引玉(閒時我也會持續輸出更多關於「前端工程化」的內容)。
總之,前端既收穫着快速發展,也迎接着批量劣汰;前端技術有着與生俱來的混亂,也有着與之抗衡的規範 —— 這都對前端工程化提出了更高的挑戰。