前端工程本質(zhì)上是軟件工程的一種。軟件工程化關(guān)注的是性能、穩(wěn)定性、可用性、可維護性等方面,注重基本的開發(fā)效率、運行效率的同時,思考維護效率。一切以這些為目標的工作都是"前端工程化"。工程化是一種思想而不是某種技術(shù)。舉例說明:
要蓋一棟大樓,假如我們不進行工程化的考量那就是一上來掂起瓦刀、磚塊就開干,直到把大樓壘起來,這樣做往往意味著中間會出現(xiàn)錯誤,要推倒重來或是蓋好以后結(jié)構(gòu)有問題但又不知道出現(xiàn)在哪誰的責任甚至會在某一天轟然倒塌,那我們?nèi)绻霉こ袒乃枷肴プ?,就會先畫圖紙、確定結(jié)構(gòu)、確定用料和預(yù)算以及工期,另外需要用到什么工種多少人等等,我們會先打地基再建框架再填充墻體這樣最后建立起來的高樓才是穩(wěn)固的合規(guī)的,什么地方出了問題我們也能找到源頭和負責人。
前端,是一種GUI軟件
我們知道前端技術(shù)的主要應(yīng)用場景并非只是高大上的基礎(chǔ)庫/框架,拽炫酷的宣傳頁面,或者屌炸天的小游戲等這些一兩個文件的小項目,更具商業(yè)價值的則是復雜的Web應(yīng)用,它們功能完善,界面繁多,為用戶提供了完整的產(chǎn)品體驗。(可能是新聞聚合網(wǎng)站,可能是在線購物平臺,可能是社交網(wǎng)絡(luò),可能是金融信貸應(yīng)用,可能是音樂互動社區(qū),也可能是視頻上傳與分享平臺……)
從本質(zhì)上講,所有Web應(yīng)用,都是一種運行在網(wǎng)頁瀏覽器中的軟件,這些軟件的圖形用戶界面(Graphical User Interface,簡稱GUI)即為前端。
如此復雜的Web應(yīng)用,動輒幾十上百人共同開發(fā)維護,其前端界面通常也頗具規(guī)模,工程量不亞于一般的傳統(tǒng)GUI軟件:

前端工程化需要考慮哪些因素?
1. 模塊化
簡單來說,模塊化就是將一個大文件拆分成相互依賴的小文件,再進行統(tǒng)一的拼裝和加載。(方便了多人協(xié)作)。
分而治之是軟件工程中的重要思想,是復雜系統(tǒng)開發(fā)和維護的基石,這點放在前端開發(fā)中同樣適用。模塊化是目前前端最流行的分治手段。
模塊化開發(fā)的最大價值應(yīng)該是分治!
不管你將來是否要復用某段代碼,你都有充分的理由將其分治為一個模塊。
- JS模塊化方案
AMD/CommonJS/UMD/ES6 Module等等。
CommonJS的核心思想是把一個文件當做一個模塊,要在哪里使用這個模塊,就在哪里require這個模塊,然后require方法開始加載這個模塊并且執(zhí)行其中的代碼,最后會返回你指定的export對象。
module.export = function() {
hello: function() {
alert("你好");
}
}
var a = require('./xxx/a.js');
a.hello(); // ==> 彈窗“你好”
CommonJS 加載模塊是同步的,所以只有加載完成才能執(zhí)行后面的操作,不能非阻塞的并行加載多個模塊。
AMD(異步模塊定義,Asynchronous Module Definition),特點是可以實現(xiàn)異步加載模塊,等所有模塊都加載并且解釋執(zhí)行完成后,才會執(zhí)行接下來的代碼。
// 通過AMD載入模塊
// define(
// module_id /*可選*/,
// [dependencies] 可選,
// definition function /*回調(diào) 用來初始化模塊或?qū)ο蟮暮瘮?shù)*/
// );
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
console.log(myModule.hello());
//會先并行加載所有的模塊a b 并執(zhí)行其中模塊的代碼后,在執(zhí)行逐步執(zhí)行下面的 console
require("a");
console.log("a required");
require("b");
console.log("b required");
console.log("all modules have been required");
});
在一些同時需要AMD和CommonJS功能的項目中,你需要使用另一種規(guī)范:Universal Module Definition(通用模塊定義規(guī)范)。UMD創(chuàng)造了一種同時使用兩種規(guī)范的方法,并且也支持全局變量定義。所以UMD的模塊可以同時在客戶端和服務(wù)端使用。
幸運的是在JS的最新規(guī)范ECMAScript 6 (ES6)中,引入了模塊功能。
ES6 的模塊功能汲取了CommonJS 和 AMD 的優(yōu)點,擁有簡潔的語法并支持異步加載,并且還有其他諸多更好的支持(例如導入是實時只讀的。(CommonJS 只是相當于把導出的代碼復制過來))。
// CommonJS代碼
// lib/counter.js
var counter = 1;
function increment() {
counter++;
}
function decrement() {
counter--;
}
module.exports = {
counter: counter,
increment: increment,
decrement: decrement
};
// src/main.js
var counter = require('../../lib/counter');
counter.increment();
console.log(counter.counter); // 1
// 使用 es6 modules 通過 import 語句導入
// lib/counter.js
export let counter = 1;
export function increment() {
counter++;
}
export function decrement() {
counter--;
}
// src/main.js
import * as counter from '../../counter';
console.log(counter.counter); // 1
counter.increment();
console.log(counter.counter); // 2
- CSS模塊化方案
在less、sass、stylus等預(yù)處理器的import/mixin特性支持下實現(xiàn)、css modules。
雖然SASS、LESS、Stylus等預(yù)處理器實現(xiàn)了CSS的文件拆分,但沒有解決CSS模塊化的一個重要問題:選擇器的全局污染問題;
CSS in JS是徹底拋棄CSS,使用JS或JSON來寫樣式。這種方法很激進,不能利用現(xiàn)有的CSS技術(shù),而且處理偽類等問題比較困難;
CSS Modules 原理:使用JS 來管理樣式模塊,它能夠最大化地結(jié)合CSS生態(tài)和JS模塊化能力,通過在每個 class 名后帶一個獨一無二 hash 值,這樣就不有存在全局命名沖突的問題了。
webpack 自帶的 css-loader 組件,自帶了 CSS Modules,通過簡單的配置即可使用。
{
test: /\.css$/,
loader: "css?modules&localIdentName=[name]__[local]--[hash:base64:5]"
}
2. 組件化
前端作為一種GUI軟件,光有JS/CSS的模塊化還不夠,對于UI組件的分治也有著同樣迫切的需求。分治的確是非常重要的工程優(yōu)化手段。

頁面上的每個 獨立的 可視/可交互區(qū)域視為一個組件;
==每個組件對應(yīng)一個工程目錄==,組件所需的各種資源都在這個目錄下就近維護;
由于組件具有獨立性,因此組件與組件之間可以 自由組合;
頁面只不過是組件的容器,負責組合組件形成功能完整的界面;
當不需要某個組件,或者想要替換組件時,可以整個目錄刪除/替換。
由于系統(tǒng)功能被分治到獨立的模塊或組件中,粒度比較精細,組織形式松散,開發(fā)者之間不會產(chǎn)生開發(fā)時序的依賴,大幅提升并行的開發(fā)效率,理論上允許隨時加入新成員認領(lǐng)組件開發(fā)或維護工作,也更容易支持多個團隊共同維護一個大型站點的開發(fā)。
3. “智能”加載靜態(tài)資源(性能優(yōu)化)
模塊化/組件化開發(fā)之后,我們最終要解決的,就是模塊/組件加載的技術(shù)問題。然而前端與客戶端GUI軟件有一個很大的不同:前端是一種遠程部署,運行時增量下載的GUI軟件。
如果用戶第一次訪問頁面就強制其加載全站靜態(tài)資源再展示,相信會有很多用戶因為失去耐心而流失。根據(jù)“增量”的原則,我們應(yīng)該精心規(guī)劃每個頁面的資源加載策略,使得用戶無論訪問哪個頁面都能按需加載頁面所需資源,沒訪問過的無需加載,訪問過的可以緩存復用,最終帶來流暢的應(yīng)用體驗。
這正是Web應(yīng)用“免安裝”的魅力所在。
由“增量”原則引申出的前端優(yōu)化技巧幾乎成為了性能優(yōu)化的核心。
有加載相關(guān)的按需加載、延遲加載、預(yù)加載、請求合并等策略;
有緩存相關(guān)的瀏覽器緩存利用,緩存更新、緩存共享、非覆蓋式發(fā)布等方案;
還有復雜的BigRender、BigPipe、Quickling、PageCache等技術(shù)。
這些優(yōu)化方案無不圍繞著如何將增量原則做到極致而展開。
- 一種靜態(tài)網(wǎng)頁資源管理和優(yōu)化技術(shù)。
靜態(tài)資源管理系統(tǒng) = 資源表 + 資源加載框架
資源表是一份數(shù)據(jù)文件(比如JSON),是項目中所有靜態(tài)資源(主要是JS和CSS)的構(gòu)建信息記錄,通過構(gòu)建工具掃描項目源碼生成,是一種k-v結(jié)構(gòu)的數(shù)據(jù),以每個資源的id為key,記錄了資源的類別、部署路徑、依賴關(guān)系、打包合并等內(nèi)容。
{
"res" : {
"widget/a/a.css" : "/widget/a/a_1688c82.css",
"widget/a/a.js" : "/widget/a/a_ac3123s.js",
"widget/b/b.css" : "/widget/b/b_52923ed.css",
"widget/b/b.js" : "/widget/b/b_a5cd123.js",
"widget/c/c.css" : "/widget/c/c_03cab13.css",
"widget/c/c.js" : "/widget/c/c_bf0ae3f.js",
"jquery.js" : "/jquery_9151577.js",
"bootstrap.css" : "/bootstrap_f5ba12d.css",
"bootstrap.js" : "/bootstrap_a0b3ef9.js"
},
"pkg" : {
"p0" : {
"url" : "/pkg/lib_cef213d.js",
"has" : [ "jquery.js", "bootstrap.js" ]
},
"p1" : {
"url" : "/pkg/lib_afec33f.css",
"has" : [ "bootstrap.css" ]
},
"p2" : {
"url" : "/pkg/widgets_22feac1.js",
"has" : [
"widget/a/a.js",
"widget/b/b.js",
"widget/c/c.js"
]
},
"p3" : {
"url" : "/pkg/widgets_af23ce5.css",
"has" : [
"widget/a/a.css",
"widget/b/b.css",
"widget/c/c.css"
]
}
}
}
在查表的時候,如果一個靜態(tài)資源有pkg字段(用來記錄web應(yīng)用中一個頁面加載過的靜態(tài)資源,當下個頁面用到這個資源就無需加載了,有效利用緩存),那么就去加載pkg字段所指向的打包文件,否則加載資源本身。
4. 規(guī)范化
規(guī)范化其實是工程化中很重要的一個部分,項目初期規(guī)范制定的好壞會直接影響到后期的開發(fā)質(zhì)量。
- 目錄結(jié)構(gòu)的制定
- 編碼規(guī)范
- 前后端接口規(guī)范
- 文檔規(guī)范
- 組件管理
- Git分支管理
- Commit描述規(guī)范
- 定期CodeReview
- 視覺圖標規(guī)范
...
5. 自動化
任何簡單機械的重復勞動都應(yīng)該讓機器去完成。
- 圖標合并
- 持續(xù)集成
- 自動化構(gòu)建
- 自動化部署
- 自動化測試
References
使用 AMD、CommonJS 及 ES Harmony 編寫模塊化的 JavaScript
前端工程與性能優(yōu)化
前端工程——基礎(chǔ)篇