常用npm、Yarn、Browserify、Gulp、Grunt、Webpack 等工具,但不清楚他們有什麼區別?手頭的項目環境過於複雜,各類配置已經超出了可控的範圍,以致於怕出問題都不敢更改它?恭喜你,你遇到了前端工程化。前端
當咱們對一個工程進行設計並把它拆分紅各個組件和模塊時,咱們是在作工程化;當咱們用 Webpack 構建項目,配置好各個環境的打包配置時,咱們是在作工程化;當咱們爲項目添加了 ESLint,並在每次提交以前自動檢查代碼質量時,咱們是在作工程化。node
若是要用一句話來歸納,在個人理解中前端工程化是把前端開發工做帶入到更加系統和規範體系的一系列過程。這個過程會包括源代碼的預編譯、模塊處理、代碼壓縮等構建方面的工做。工程化會盡量保證開發者的開發體驗更加友好,保證源代碼的質量以及依賴的完整性。工程化也會盡量高效地將構建完成後的代碼送達給客戶端,來追求更加良好的用戶體驗。全部這些都屬於工程化。npm
若是一個開發者想作現代 JavaScirpt 應用,那麼他就有理由瞭解前端工程化,由於經過工程化能夠提升代碼質量,下降開發成本。前端工程化
會有人質疑說如今的前端開發是否是過於複雜了?一個沒有用過 npm 和 Webpack 的開發者想寫一個 Hello World 可能都須要學習和配置半天。在之前沒有所謂「工程化」的時候,還不是照樣寫代碼和發佈代碼來實現用戶需求。緩存
其實咱們能夠打個比方,把發送給客戶端的頁面理解成呈獻給用戶的一道菜。前端開發者是廚師,而工程化可使開發者遵循正確的作菜流程——洗菜、切菜、炒菜,而不是將生的東西直接放到用戶面前。前端框架
在 Web 技術剛開始的時候,尚未前端工程化這樣一個東西。人們只是簡單地把 HTML、CSS 和 JavaScript 直接混在一塊兒丟到用戶。而就如人類對於食物的追求在不斷進步同樣,雖然在最初級的階段需求只是能填飽肚子,但慢慢地人們開始追求食物的質量。對於前端來講也是同樣,用戶的需求從最開始簡單的頁面在向複雜的應用發展。前端須要作的事情更多,同時也要追求更友好的用戶體驗。前端工程師
對於廚師來講,要想作出高質量的食物須要順手的工具以及正確的烹飪方法。對於開發者來講,要想輸出高質量的代碼也一樣須要工程化來輔助。做爲一個前端工程師,光會寫代碼是不夠的,最多隻是提供了好的原材料。要想將代碼轉化爲高質量的產出提供給用戶,必需要了解工程化。框架
另外,工程化也是爲開發者服務的。經過預編譯語言、模塊熱加載等技術能夠提高開發效率,而利用自動化測試、lint 工具等能夠保證代碼的功能和質量。工程化能夠有效下降開發成本,誰不想省下埋頭 debug 的時間去作更有趣的事呢。異步
前端工程化不是憑空出現的,必定是爲了解決當時的一些問題而出現的,讓咱們先簡單回溯一下前端開發的歷史。async
曾經的 Web 開發不一樣於如今,頁面功能比較簡單。開發者想添加一段邏輯,最直接的作法就是在 HTML 中插入一個 script 標籤,而後直接在裏面書寫代碼。聽上去彷佛十分的原始,但在當時確實不少都是這樣作的,並且在業務需求簡單的時候這也確實是最直接了當的實現方式。可是這種原始的作法也必然有它的缺點,主要有如下兩個方面:
全局做用域的污染。因爲在每一個 script 標籤下頂層做用域即全局做用域,直接進行變量和函數聲明會形成全局命名空間污染。假如一個頁面有多個 script 標籤,它們之間頗有可能發生命名衝突。
代碼重用性差。在一個多頁面應用的場景下,常常會有一些邏輯是這些頁面之間共有的,此時咱們不得不將這些代碼複製粘貼到各個頁面中。而當此處邏輯改動的時候咱們也須要去更新全部頁面的代碼,形成不少額外的成本。
後來逐漸有一些針對這些問題的解決辦法。首先,能夠將 HTML 中內聯的 JavaScript 提取出來成爲單獨的 JavaScript 文件。好比說一些頁面公有的邏輯能夠放在相似 common.js 中來被各個頁面引用,這能夠解決各個頁面之間重用的問題。至於全局做用域污染的問題,則可使用當即執行函數表達式將它包起來( IIFE ),只把接口暴露到全局上。
看上去問題已經獲得瞭解決,然而隨着頁面邏輯的複雜度增長開發者又面臨了新的問題:
頁面 JavaScript 文件的引用順序。因爲 HTML 頁面引用和處理 JavaScript 文件只能是順序的(不考慮 async 等),所以頁面的 JavaScript 之間依賴關係也必須是順序的。而咱們知道一個大型工程內部的模塊依賴關係一般是樹狀的(好比 index.js 依賴 a、b、c 三個模塊,而 a、b、c 又有各自的依賴),簡單的順序依賴關係沒法知足需求。例如在 jQuery 最流行的時期,jQuery 自己以及其相關的插件之間有着各類各樣的依賴關係,有些庫可能自身包含 jQuery,不一樣的插件可能須要不一樣的 jQuery 版本,這些問題都不是簡單的順序依賴關係能夠解決的。
頁面引用的 JavaScript 文件的長度與數量如何權衡。隨着頁面邏輯的增長,工程中的 JavaScript 文件愈來愈長,也愈來愈難以維護。一個頁面的單個 JavaScript 文件可能有數千行甚至上萬行。而若是按照功能來把頁面邏輯切割成一個個小的 JavaScript 文件,則最終會走到另外一個極端——頁面請求過多。咱們知道每一個 HTTP 請求都是須要鏈接時間的,對於小模塊而言每個都要單獨創建鏈接總歸得不償失,必然會致使頁面渲染速度的降低。
上面所說的這些問題都屬於前端開發的「原始時期」,那時候尚未工程化這種說法。然而逐漸暴露出來的問題已經讓人們以爲不能再簡單粗暴地採用如此原始的開發方式,模塊化是第一步。
一個設計良好的系統應該是模塊化的。一個最簡單的緣由,在一個模塊化的系統中,當外界的需求亦或環境變化的時候,開發者能夠更快地將問題定位到相應的模塊,而沒必要面對糾纏在一塊兒的邏輯不知如何下手。模塊化可使系統具有更強的可維護性。
被封裝良好的模塊應該具有特定且單一的功能,對外界只提供接口,而將具體實現封裝在內部。Webpack 中有一個核心的理念——」一切皆模塊」,即 HTML、JavaScript、CSS、圖片等等都是模塊,在後面的文章中會展開講。
雖然模塊化很重要,可是 JavaScript 誕生的時候並不具有模塊這一特性,這主要是由於早期 Web 中的腳本大都比較簡單,在設計之初只是爲了實現 Web 上一些簡單的功能。一直到 CommonJS 以及 AMD 的出現,爲前端定義了模塊的標準。也有了實現這些模塊化的庫,好比 RequireJS 以及 Browserify。可讓開發者將本身工程中的代碼按模塊進行劃分,模塊之間也再也不僅僅是簡單的順序依賴關係。
另外在將代碼提供給客戶端以前,開發者能夠經過 Browserify、Webpack 這些工具將工程代碼進行打包,把全部依賴模塊打包爲單一的 JavaScript 文件。這樣一來,對於開發者而言開發體驗更加友好,由於開發中每次須要關注的僅僅是單個模塊,而不是堆放在一塊兒的上千行 JavaScript 文件;而對於客戶端來講則只用接受單一的打包產物,解決了文件數量過多致使 HTTP 請求耗時長的問題。解決模塊之間的依賴,並根據依賴樹進行打包,是工程化解決的最基本的問題之一。
上面說的只是 JavaScript 的模塊化,那麼咱們很天然地就想到 CSS 的模塊化。然而因 CSS 自己 @import
的性能問題,通常都是要經過 SASS、LESS 等預編譯語言去實現其模塊化。
例如在 SASS 中,經過 @import
語句咱們能夠導入其它模塊。SASS 的編譯器會處理模塊之間的依賴,並最終將代碼打包在一塊兒生成 CSS。
在實際開發中,咱們不少時候都會使用預編譯語言來進行編碼工做,而後通過 Webpack 等工具的構建將其編譯爲實際頁面中的代碼。使用預編譯語言的主要目的是爲了實現 HTML、CSS、JavaScript 不具有的特性。好比說上面提到的最多見的 SASS,它是 CSS 的預編譯語言,經過它開發者可使用模塊、定義變量、編寫嵌套規則等等來提升開發效率。
除了 CSS 的預編譯語言,HTML 對應的有 HAML,JavaScript 對應的有 Coffee 等等。整體而言這些預編譯語言的目的就是使開發體驗更友好,開發者能夠更高效地編寫和維護代碼。
固然如今預編譯已經不只僅是這些,咱們還可使用 Babel 預編譯 JavaScript 來實現新的 ES 特性,以及使用 TypeScript 去作類型檢查等,在預編譯這裏還能夠有更多的想象力。
和 Java、C++ 這些語言不一樣,JavaScript 沒有強大的標準庫。許多經常使用的功能,好比日期處理、URL 處理、異步流程控制等每每都須要手工去編寫,而採用外部已有的開源框架庫或許是節約成本的最好辦法。
Bower 做爲包管理器最先進入人們的視野,大部門前端框架庫也都提供了經過 Bower 安裝的方式。經過它你能夠獲取項目須要的依賴,而且經過打包工具和業務代碼打包到一塊兒。
雖然如今當咱們說包管理器可能首先想到的是 npm,但它最開始其實主要是爲 Node.js 服務的,而人們逐漸意識到它也能夠用於前端而且真正擔當起前端包管理器的大任也就是近兩年的時間。在現階段來講 npm 已經超越 Bower 成爲開發者首選的包管理器,而 Yarn 做爲 Facebook 出品的新生代也不過是管理 node_modules 的另外一個工具罷了,與 npm 並無什麼本質上的區別。在後續的相關文章中會更詳細地介紹包管理器常見的問題和處理辦法。
隨着工程化的發展,交給構建過程的任務也愈來愈多,而且在不一樣環境下須要對任務進行區分。好比對於一個前端工程來講,除了須要預編譯各類類型的文件、資源打包以外,本地環境下還要生成 source-map、配置模塊熱加載等等便於調試代碼;而到了生產環境下則要對資源進行壓縮,生成版本號等等。
所以對於開發者來講須要將這些任務統一塊兒來進行管理,也就有了 Gulp 和 Grunt 等構建流程管理工具。這類工具的出現使得構建變得更加傻瓜化,經過項目中的一些配置,開發者可使用簡單的一行命令啓動本地開發環境或者構建和發佈整個工程。
相比於 Gulp 和 Grunt,Webpack 出現的更晚。它和前二者的核心定位其實不太同樣,Webpack 自己只是做爲一個模塊打包工具的姿態出現的,可是利用一些相關工具和插件咱們也能夠完成整個工程的構建。Webpack 的「一切皆模塊」以及「按需加載」兩大特性使得它更好地服務於工程化。在後面的文章中會有不少關於 Webpack 的部分,包括 Webpack 的打包原理及優化、從零搭建起一個開發環境等,會詳細講解 Webpack 的使用以及其最新的特性。
如今當咱們討論工程化,效率和優化是出現得愈來愈頻繁的詞。當把工程化的各類功能都實如今咱們的工程中以後,卻發現完整地構建一遍須要好幾分鐘甚至更長。工程自己的愈加龐大以及愈來愈多的構建任務使得耗時愈來愈久,此時是否是又懷念起了在 HTML 裏寫內聯腳本的日子。
除了構建速度的問題,推給用戶端的資源體積過大也是問題。須要針對項目的特色採用按需加載、異步加載、長效緩存等等策略。
做者: