壓縮代碼
瀏覽器從服務(wù)器訪問網(wǎng)頁時獲取的JavaScript、CSS資源都是文本形式的,文件越大網(wǎng)頁加載時間越長。 為了提升網(wǎng)頁加速速度和減少網(wǎng)絡(luò)傳輸流量,可以對這些資源進行壓縮。 壓縮的方法除了可以通過GZIP算法對文件壓縮外,還可以對文本本身進行壓縮。
對文本本身進行壓縮的作用除了有提升網(wǎng)頁加載速度的優(yōu)勢外,還具有混淆源碼的作用。由于壓縮后的代碼可讀性非常差,就算別人下載到了網(wǎng)頁的代碼,也大大增加了代碼分析和改造的難度。
壓縮JavaScript
目前最成熟的JavaScript代碼壓縮工具是UglifyJS,它會分析JavaScript代碼語法樹,理解代碼含義,從而能做到諸如去掉無效代碼、去掉日志輸出代碼、縮短變量名等優(yōu)化。
要在Webpack中接入UglifyJS需要通過插件的形式,目前有兩個成熟的插件,分別是:
- UglifyJsPlugin:通過封裝 UglifyJS 實現(xiàn)壓縮。
- ParallelUglifyPlugin:多進程并行處理壓縮。
UglifyJS提供了非常多的選擇用于配置在壓縮過程中采用哪些規(guī)則。 由于選項非常多,就挑出一些常用的拿出來詳細講解其應(yīng)用方式:
-
sourceMap:是否為壓縮后的代碼生成對應(yīng)的Source Map,默認為不生成,開啟后耗時會大大增加。一般不會把壓縮后的代碼的Source Map發(fā)送給網(wǎng)站用戶的瀏覽器,而是用于內(nèi)部開發(fā)人員調(diào)試線上代碼時使用。 -
beautify: 是否輸出可讀性較強的代碼,即會保留空格和制表符,默認為是,為了達到更好的壓縮效果,可以設(shè)置為false。 -
comments:是否保留代碼中的注釋,默認為保留,為了達到更好的壓縮效果,可以設(shè)置為false。 -
compress.warnings:是否在UglifyJs刪除沒有用到的代碼時輸出警告信息,默認為輸出,可以設(shè)置為false以關(guān)閉這些作用不大的警告。 -
drop_console:是否剔除代碼中所有的console語句,默認為不剔除。開啟后不僅可以提升代碼壓縮效果,也可以兼容不支持console語句IE瀏覽器。 -
collapse_vars:是否內(nèi)嵌定義了但是只用到一次的變量,例如把var x = 5; y = x轉(zhuǎn)換成y = 5,默認為不轉(zhuǎn)換。為了達到更好的壓縮效果,可以設(shè)置為true。 -
reduce_vars: 是否提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值,例如把x = 'Hello'; y = 'Hello'轉(zhuǎn)換成var a = 'Hello'; x = a; y = b,默認為不轉(zhuǎn)換。為了達到更好的壓縮效果,可以設(shè)置為true。
也就是說,在不影響代碼正確執(zhí)行的前提下,最優(yōu)化的代碼壓縮配置為如下:
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
module.exports = {
plugins: [
// 壓縮輸出的 JS 代碼
new UglifyJSPlugin({
compress: {
// 在UglifyJs刪除沒有用到的代碼時不輸出警告
warnings: false,
// 刪除所有的 `console` 語句,可以兼容ie瀏覽器
drop_console: true,
// 內(nèi)嵌定義了但是只用到一次的變量
collapse_vars: true,
// 提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值
reduce_vars: true,
},
output: {
// 最緊湊的輸出
beautify: false,
// 刪除所有的注釋
comments: false,
}
}),
],
};
除此之外Webpack還提供了一個更簡便的方法來接入UglifyJSPlugin,直接在啟動Webpack時帶上--optimize-minimize參數(shù),即webpack --optimize-minimize, 這樣Webpack會自動為你注入一個帶有默認配置的UglifyJSPlugin。
壓縮ES6
雖然當前大多數(shù)JavaScript引擎還不完全支持ES6中的新特性,但在一些特定的運行環(huán)境下已經(jīng)可以直接執(zhí)行ES6代碼了,例如最新版的Chrome、ReactNative的引擎JavaScriptCore。
運行ES6的代碼相比于轉(zhuǎn)換后的ES5代碼有如下優(yōu)點:
- 一樣的邏輯用ES6實現(xiàn)的代碼量比ES5更少。
- JavaScript引擎對ES6中的語法做了性能優(yōu)化,例如針對
const申明的變量有更快的讀取速度。
所以在運行環(huán)境允許的情況下,我們要盡可能的使用原生的ES6代碼去運行,而不是轉(zhuǎn)換后的ES5代碼。
在你用上面所講的壓縮方法去壓縮ES6代碼時,你會發(fā)現(xiàn)UglifyJS會報錯退出,原因是UglifyJS只認識ES5語法的代碼。 為了壓縮ES6代碼,需要使用專門針對ES6代碼的UglifyES。
UglifyES和UglifyJS來自同一個項目的不同分支,它們的配置項基本相同,只是接入Webpack時有所區(qū)別。 在給Webpack接入UglifyES時,不能使用內(nèi)置的UglifyJsPlugin,而是需要單獨安裝和使用最新版本的uglifyjs-webpack-plugin。安裝方法如下:
npm i -D uglifyjs-webpack-plugin@beta
Webpack 相關(guān)配置代碼如下:
const UglifyESPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyESPlugin({
// 多嵌套了一層
uglifyOptions: {
compress: {
// 在UglifyJs刪除沒有用到的代碼時不輸出警告
warnings: false,
// 刪除所有的 `console` 語句,可以兼容ie瀏覽器
drop_console: true,
// 內(nèi)嵌定義了但是只用到一次的變量
collapse_vars: true,
// 提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值
reduce_vars: true,
},
output: {
// 最緊湊的輸出
beautify: false,
// 刪除所有的注釋
comments: false,
}
}
})
]
}
同時,為了不讓babel-loader輸出ES5語法的代碼,需要去掉.babelrc配置文件中的babel-preset-env,但是其它的Babel插件,比如babel-preset-react還是要保留, 因為正是babel-preset-env負責(zé)把ES6代碼轉(zhuǎn)換為ES5代碼。
壓縮CSS
CSS代碼也可以像JavaScript那樣被壓縮,以達到提升加載速度和代碼混淆的作用。 目前比較成熟可靠的CSS壓縮工具是cssnano,基于PostCSS。
cssnano能理解CSS代碼的含義,而不僅僅是刪掉空格,例如:
-
margin: 10px 20px 10px 20px被壓縮成margin: 10px 20px -
color: #ff0000被壓縮成color:red
把cssnano接入到Webpack中也非常簡單,因為css-loader已經(jīng)將其內(nèi)置了,要開啟cssnano去壓縮代碼只需要開啟css-loader的minimize選項。 相關(guān)Webpack配置如下:
const path = require('path');
const {WebPlugin} = require('web-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css/,// 增加對 CSS 文件的支持
// 提取出 Chunk 中的 CSS 代碼到單獨的文件中
use: ExtractTextPlugin.extract({
// 通過 minimize 選項壓縮 CSS 代碼
use: ['css-loader?minimize']
}),
},
]
},
plugins: [
// 用 WebPlugin 生成對應(yīng)的 HTML 文件
new WebPlugin({
template: './template.html', // HTML 模版文件所在的文件路徑
filename: 'index.html' // 輸出的 HTML 的文件名稱
}),
new ExtractTextPlugin({
filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 文件名稱加上 Hash 值
}),
],
};
ParallelUglifyPlugin
用過UglifyJS的你一定會發(fā)現(xiàn)在構(gòu)建用于開發(fā)環(huán)境的代碼時很快就能完成,但在構(gòu)建用于線上的代碼時構(gòu)建一直卡在一個時間點遲遲沒有反應(yīng),其實卡住的這個時候就是在進行代碼壓縮。
由于壓縮JavaScript代碼需要先把代碼解析成用Object抽象表示的AST語法樹,再去應(yīng)用各種規(guī)則分析和處理AST,導(dǎo)致這個過程計算量巨大,耗時非常多。
當Webpack有多個JavaScript文件需要輸出和壓縮時,原本會使用UglifyJS去一個個挨著壓縮再輸出, 但是ParallelUglifyPlugin則會開啟多個子進程,把對多個文件的壓縮工作分配給多個子進程去完成,每個子進程其實還是通過UglifyJS去壓縮代碼,但是變成了并行執(zhí)行。 所以ParallelUglifyPlugin能更快的完成對多個文件的壓縮工作。
使用ParallelUglifyPlugin也非常簡單,把原來Webpack配置文件中內(nèi)置的UglifyJsPlugin去掉后,再替換成ParallelUglifyPlugin,相關(guān)代碼如下:
const path = require('path');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = {
plugins: [
// 使用 ParallelUglifyPlugin 并行壓縮輸出的 JS 代碼
new ParallelUglifyPlugin({
// 傳遞給 UglifyJS 的參數(shù)
uglifyJS: {
output: {
// 最緊湊的輸出
beautify: false,
// 刪除所有的注釋
comments: false,
},
compress: {
// 在UglifyJs刪除沒有用到的代碼時不輸出警告
warnings: false,
// 刪除所有的 `console` 語句,可以兼容ie瀏覽器
drop_console: true,
// 內(nèi)嵌定義了但是只用到一次的變量
collapse_vars: true,
// 提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值
reduce_vars: true,
}
},
}),
],
};
在通過new ParallelUglifyPlugin()實例化時,支持以下參數(shù):
-
test:使用正則去匹配哪些文件需要被ParallelUglifyPlugin壓縮,默認是/.js$/,也就是默認壓縮所有的.js文件。 -
include:使用正則去命中需要被ParallelUglifyPlugin壓縮的文件。默認為[]。 -
exclude:使用正則去命中不需要被ParallelUglifyPlugin壓縮的文件。默認為[]。 -
cacheDir:緩存壓縮后的結(jié)果,下次遇到一樣的輸入時直接從緩存中獲取壓縮后的結(jié)果并返回。cacheDir用于配置緩存存放的目錄路徑。默認不會緩存,想開啟緩存請設(shè)置一個目錄路徑。 -
workerCount:開啟幾個子進程去并發(fā)的執(zhí)行壓縮。默認是當前運行電腦的CPU核數(shù)減去1。 -
sourceMap:是否輸出Source Map,這會導(dǎo)致壓縮過程變慢。 -
uglifyJS:用于壓縮ES5代碼時的配置,Object類型,直接透傳給UglifyJS的參數(shù)。 -
uglifyES:用于壓縮ES6代碼時的配置,Object類型,直接透傳給UglifyES的參數(shù)。
其中的test、include、exclude與配置Loader時的思想和用法一樣。
UglifyES是UglifyJS的變種,專門用于壓縮ES6代碼,它們兩都出自于同一個項目,并且它們兩不能同時使用。
UglifyES一般用于給比較新的JavaScript 運行環(huán)境壓縮代碼,例如用于ReactNative的代碼運行在兼容性較好的JavaScriptCore引擎中,為了得到更好的性能和尺寸,采用UglifyES壓縮效果會更好。
ParallelUglifyPlugin同時內(nèi)置了UglifyJS和UglifyES,也就是說ParallelUglifyPlugin支持并行壓縮ES6代碼。
接入ParallelUglifyPlugin后,項目需要安裝新的依賴:
npm i -D webpack-parallel-uglify-plugin
安裝成功后,重新執(zhí)行構(gòu)建你會發(fā)現(xiàn)速度變快了許多。如果設(shè)置cacheDir開啟了緩存,在之后的構(gòu)建中會變的更快。