前端工程化
前端工程化的概念在近些年來逐漸成爲主流構建大型web應用不可或缺的一部分,在此我通過以下這三方面總結一下自己的理解。
-
爲什麼需要前端工程化。
-
前端工程化的演化。
-
怎麼實現前端工程化。
爲什麼需要工程化
隨着近些年來前端技術的不斷髮展,越來越多複雜的業務放在了前端,前端不再是以前幾個HTML + CSS + javascript就能解決的了。業務複雜了,需要維護的代碼量就自然多了,如此一來,前端代碼的可靠性,可維護性,可拓展性,以及前端web應用的性能,開發效率等等各方面就成了不得不考慮的問題。
於是我們就產生了前端工程化這個概念,來解決這些問題。現階段的前端工程化,需要考慮到各個方面,包括但不限於以下這幾點:
提升開發效率
-
webpack-dev-server 熱加載
以前,我們的日常前端開發的流程是這樣的: 修改代碼 -> 切換IDE到瀏覽器 -> 刷新瀏覽器查看效果(有時候還需要清除緩存) -> 修改代碼 ....。
這套流程,尤其是刷新瀏覽器這個過程,無疑是相當低效繁瑣枯燥的。 而webpack-dev-server 替我們解決了這個問題,它有兩種模式,兩種模式,一種是 watch 模式,功能是你修改代碼,自動幫你刷新頁面,無需手動刷新;另一種更加強大,基於 websocket 全雙工通信技術,直接無刷新幫你把修改的代碼替換掉。 從而極大程度上提高了開發效率。
-
數據mock
在後端接口還沒提供的時候,前後端制定好共同的接口協議,開發時前端可以使用mock模擬數據,與後端徹底分離,並行開發。面向接口編程,儘可能減少前後端溝通成本。
優化性能
-
代碼合併壓縮,混淆加密
-
減少小圖片請求
webpack中url-loader:loader: 'url-loader?limit=8192'
,使得小於8kb的圖片使用data:image base64 編碼內聯,減少圖片請求量
-
部署靜態文件緩存管理
使用webpack的內置的chunkhash功能,可以給生成的js文件添加hash後綴,標識文件版本。
提高代碼質量
-
模塊化
主要指 js 代碼的模塊化。以前的前端開發並沒有模塊化這個概念,這給維護大型項目帶來了極大的困難。發展到現在的前端有很多模塊化的方法可供選擇,如seajs ,requirejs, webpack 等。 模塊化能很大程度上提高了代碼的可維護性。
-
CSS 預處理
通過sass,less 等css 預處理器,可以實現 css 文件的拆分,顆粒化,實現css可複用。而且通過autoprefixer或postcss 還可以讓 css 樣式對老舊瀏覽器向下兼容。
此外,通過使用 css-modules 能夠避免css全局污染的問題,極大提高css代碼的可控性,不需要設定一堆命名空間與命名規範來限制。
-
ES6 + babel 編譯
javascript本身設計存在一定程度上的缺陷,例如“沒有模塊化”,“沒有塊級作用域”,“全局變量污染”,“回調地獄”等等之類的問題,爲了改善這些缺陷,計算機協會在2015年推出了ECMAScript 6 標準(今年已經ES8 已經發布了),使用ES6的語法除了能有效減少代碼量之外,還引入了塊級作用域,模塊化,類的語法糖,promise以及一些新的API,很大程度上填了以前javascript的遺留下的坑,以及提高了代碼質量。
不過即便過了兩年,ES6也並沒有被市面的主流瀏覽器完全支持,所以我們還需用 babel 將ES6 編譯成ES5,再將一些不支持的API polyfill 處理。
-
eslint 代碼檢查
一直一來,代碼風格都是一場無休止的爭論,每個人都有自己的代碼風格習慣,而這些習慣無非就是tab還是空格,換不換行,加不加空格等等之類的瑣事,與其通過制定規範去強行限制開發者的編寫習慣,不如從工具層面徹底解決代碼風格的問題。eslint可以自動處理一些代碼風格的問題,直接將代碼通過指定的規則格式化,使代碼整體風格統一。
更進一步,eslint 還可以禁止代碼的一些可能造成不良影響的行爲(例如eval,未定義變量),使其拋出錯誤。降低代碼產生bug的可能性。
-
單元測試
集成單元測試,提高代碼可靠性。前端較爲流行的單元測試 mocha,qunit 等
-
UI 自動化測試
UI 自動化測試是 軟件通過模擬瀏覽器,對頁面進行UI操作,判斷是否產生預想的UI效果。目前較爲流行的UI自動化測試套件主要是 基於phantomjs的 nightmare
-
web組件化
web組件化是通過自定義標籤,從UI層面對代碼的拆分,提高前端代碼的可複用性。儘管w3c已經初步對web組件化制定了規範, 但目前瀏覽器對web 組件化的支持慘不忍睹,無法通過原生的方法來實現web組件,但目前流行的前端框架,如vue,angular,react都有提供自己的web組件化,從而提高代碼可複用性。
前端工程化的發展
<script>
直接引入加載
在沒有引入模塊化的概念之前,前端往往需要手動處理js文件的依賴關係,例如;bootstartp 依賴 jquery,就需要在引入bootstrap之前引入jquery
<script src="src/jquery.min.js" ></script>
<script src="src/bootstrap.min.js" ></script>
如果引入js文件順序錯了則會報錯。 乍一看似乎沒什麼難度呀,是人都能分清是吧。那麼請看下面這種情況:
有 a.js, b.js, c.js, d.js, e.js 五個文件,其中
-
a 依賴 b和e,
-
b 依賴 d和e,
-
c 依賴 a和d,
-
d 依賴 e,
-
e 無依賴。
那麼根據以上關係,請按正確順序引入js文件(黑人問號???)。當然,事實上也並不難區分其優先級,逐級遞推就很快可以推斷出引入順序爲 e,d,b,a,c
。
毫無疑問,對於稍微複雜點的web工程,存在複雜依賴情況是極有可能發生的,並且把時間耗費在管理依賴關係上也不值當。
所以就誕生了前端模塊化
模塊化標準(AMD,CMD,ES6 Module)
經歷了混亂加載的黑歷史,我們終於迎來了js的模塊化,忽如一夜春風來,一夜之間冒出一堆模塊化標準。
其中具有代表性的模塊加載器分別是是遵循AMD(Asynchronous Module Definition)規範的RequireJS ,還有淘寶玉伯開源的 遵循CMD(Common Module Definition)規範的 SeaJS。 兩者除了遵循規範不一樣之外,封裝模塊有差別之外,都各有所長,而且對舊版本瀏覽器的支持都相當完美。
當然除了這兩個,還有各類其他開發者開發的模塊加載器,當真是一番羣魔亂舞百家爭鳴的盛世呀。在此就不一一細述了。
下面有請我們的主角出廠: ES6 Module。
ES6 Module 是新一代javascript標準 ECMAScript 6 的新增特性,其語法和Python相似,比較簡潔易用。另外,相比於其他模塊加載器,ES6 Module 是語法級別的實現,其靜態代碼分析相比於其他框架會更快更高效,方便做代碼檢測。
// import 基本語法
import React from 'react'; //等價於 var React = require("react");
import { stat, exists, readFile } from 'fs';
// 等價於
// var fs = require('fs');
// var stat = fs.stat, exists = fs.exists, readFile = fs.readFile;
而且,且不論其API優劣,其語法與前面說的模塊化有什麼區別的,ES6 Module最大優點是顯而易見的: 它是官方標準,而不是其他妖豔賤貨第三方開發的框架/庫。跟着有名分的原配混,毫無疑問是有前途更穩定的吧。
當然,缺點也是很明顯的,不同於RequireJS,SeaJS 向下兼容到極致(ie6+),ES6 Module 的兼容性還未覆蓋絕大部分瀏覽器,支持ES6 Module的瀏覽器寥寥無幾,雖然可以通過babel進行語法轉譯,不過兼容性畢竟是硬傷,唯有時間能治癒。
自動化構建工具(gulp,grunt)
從描述可知,前端工程化需要做的事情,單憑人力一個一個去處理基本沒有可能完成,那麼,我們就需要學會使用工具,畢竟程序猿和猿之間最大的區別就是會不會使用工具。
grunt 和 gulp 就是自動化構建工具。我們通過安裝對應的node_module,根據gulp/grunt 的API編寫相對應的任務(如:css預處理,代碼合併壓縮,代碼校驗檢查等任務,js代碼轉譯),那麼就可以生成我們想要的結果,完成前端工作流管理,極大程度地提高效率。其作用其實就相當於makefile 的make 操作,將手工操作自動化,其任務編寫格式如下。
// gulp scss預處理任務
gulp.task('styles', function() {
return gulp.src('src/styles/main.scss')
.pipe(sass({ style: 'expanded' }))
.pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
.pipe(gulp.dest('dist/assets/css'))
.pipe(rename({suffix: '.min'}))
.pipe(minifycss())
.pipe(gulp.dest('dist/assets/css'))
.pipe(notify({ message: 'Styles task complete' }));
});
模塊化打包器(webpack)
前面說了那麼多SeaJS,RequireJS的模塊化 ,又有gulp ,grunt的自動化處理,想必都有點覺得這前端工程化的技術棧也太繁瑣了吧。
那麼現在,你可以統統不用管啦,讓我們推出終極解決方案:Webpack。
相比於seajs / requirejs 需要在瀏覽器引入 sea.js 、require.js 的模塊解析器文件,瀏覽器才能識別其定義的模塊。 webpack不需要在瀏覽器中加載解釋器,而是直接在本地將模塊化文件(無論是AMD,CMD規範還是ES6 Module)編譯成瀏覽器可識別的js文件。
另外,相對於gulp/grunt 的批處理工作流功能,webpack 也可以通過 loader、plugin的形式對所有文件進行處理,來實現類似的功能。
其主要工作方式是: 整個項目存在一個或多個入口js文件,通過這個入口找到項目的所有依賴文件,通過loader,plugin進行處理後,打包生成對應的文件,輸出到指定的output目錄中。可以說是集模塊化與工作流於一身的工具。
當然,webpack也並非銀彈。工具沒有好壞,只有適合與否。即便是webpack也並非適用於所有場合。
webpack 的最大特點是一切皆爲模塊,一切全包,最適和應用在SPA一站式應用場景。只有簡單幾個頁面的情況下使用 webpack 反而可能會增加不必要的配置成本,反而直接用gulp或者其他工具處理代碼壓縮,css 預處理之類的工作會更加快捷易用。
另外,除了最主流的 webpack 之外,同性質的模塊化打包器還有 browserIfy,以及百度的 fis ,由於對這兩者瞭解不多,就不一一比較了。
使用 webpack 實現工程化
廢話少說,talk is easy , show me the code,我們來看看webpack是怎麼工作的。以下是一個配置了webpack-dev-server
的本地開發webpack配置文件。 具體可訪問 github 地址 查看完整信息
// webpack.dev.config.js
let path = require('path'),
webpack = require('webpack');
let resolve = path.resolve;
let webRootDir = resolve(__dirname, '../');
module.exports = {
entry: { // 入口文件,打包通過入口,找到所有依賴的模塊,打包輸出
main: resolve(webRootDir, './src/main.js'),
},
output: {
path: resolve(webRootDir, './build'), // 輸出路徑
publicPath: '/build/', // 公共資源路徑
filename: '[name].js' // 輸出文件名字,此處輸出main.js, babel-polyfill.js , 視情況可以配置[name].[chunkhash].js添加文件hash, 管理緩存
},
module: {
rules: [ //模塊化的loader,有對應的loader,該文件才能作爲模塊被webpack識別
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/