對文本本身進行壓縮的作用除了有提升網頁加載速度的優勢外,還具有混淆源碼的作用。 由於壓縮后的代碼可讀性非常差,就算別人下載到了網頁的代碼,也大大增加了代碼分析和改造的難度。
下面來一一介紹如何在 Webpack 中壓縮代碼。
壓縮 JavaScript
目前最成熟的 JavaScript 代碼壓縮工具是 UglifyJS , 它會分析 JavaScript 代碼語法樹,理解代碼含義,從而能做到諸如去掉無效代碼、去掉日志輸出代碼、縮短變量名等優化。
要在 Webpack 中接入 UglifyJS 需要通過插件的形式,目前有兩個成熟的插件,分別是:
- UglifyJsPlugin :通過封裝 UglifyJS 實現壓縮。
- ParallelUglifyPlugin :多進程並行處理壓縮,4-4使用ParallelUglifyPlugin 中有詳細介紹。
由於 ParallelUglifyPlugin 在 4-4使用ParallelUglifyPlugin 中介紹過就不再復述, 這里重點介紹如何配置 UglifyJS 以達到最優的壓縮效果。
UglifyJS 提供了非常多的選擇用於配置在壓縮過程中采用哪些規則,所有的選項說明可以在 其官方文檔 上看到。 由於選項非常多,就挑出一些常用的拿出來詳細講解其應用方式:
- sourceMap :是否為壓縮后的代碼生成對應的 Source Map,默認為不生成,開啟后耗時會大大增加。一般不會把壓縮后的代碼的 Source Map 發送給網站用戶的瀏覽器,而是用於內部開發人員調試線上代碼時使用。
- beautify : 是否輸出可讀性較強的代碼,即會保留空格和制表符,默認為是,為了達到更好的壓縮效果,可以設置為
false
。 - comments :是否保留代碼中的注釋,默認為保留,為了達到更好的壓縮效果,可以設置為
false
。 - compress.warnings :是否在 UglifyJs 刪除沒有用到的代碼時輸出警告信息,默認為輸出,可以設置為
false
以關閉這些作用不大的警告。 - drop_console :是否剔除代碼中所有的 console 語句,默認為不剔除。開啟后不僅可以提升代碼壓縮效果,也可以兼容不支持
console
語句 IE 瀏覽器。 - collapse_vars :是否內嵌定義了但是只用到一次的變量,例如把
var x = 5; y = x
轉換成y = 5
,默認為不轉換。為了達到更好的壓縮效果,可以設置為false
。 - reduce_vars : 是否提取出出現多次但是沒有定義成變量去引用的靜態值,例如把 x = 'Hello'; y = 'Hello' 轉換成 var a = 'Hello'; x = a; y = b, 默認為不轉換。為了達到更好的壓縮效果,可以設置為 false 。
也就是說,在不影響代碼正確執行的前提下,最優化的代碼壓縮配置為如下:
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); module.exports = { plugins: [ // 壓縮輸出的 JS 代碼 new UglifyJSPlugin({ compress: { // 在UglifyJs刪除沒有用到的代碼時不輸出警告 warnings: false, // 刪除所有的 `console` 語句,可以兼容ie瀏覽器 drop_console: true, // 內嵌定義了但是只用到一次的變量 collapse_vars: true, // 提取出出現多次但是沒有定義成變量去引用的靜態值 reduce_vars: true, }, output: { // 最緊湊的輸出 beautify: false, // 刪除所有的注釋 comments: false, } }), ], };
從以上配置中可以看出 Webpack 內置了 UglifyJsPlugin,需要指出的是 UglifyJsPlugin 當前采用的是 UglifyJS2 而不是老的 UglifyJS1, 這兩個版本的 UglifyJS 在配置上有所區別,看文檔時注意版本。
除此之外 Webpack 還提供了一個更簡便的方法來接入 UglifyJSPlugin,直接在啟動 Webpack 時帶上 --optimize-minimize 參數,即 webpack --optimize-minimize , 這樣 Webpack 會自動為你注入一個帶有默認配置的 UglifyJSPlugin。本實例提供項目完整代碼
壓縮 ES6
雖然當前大多數 JavaScript 引擎還不完全支持 ES6 中的新特性,但在一些特定的運行環境下已經可以直接執行 ES6 代碼了,例如最新版的 Chrome、ReactNative 的引擎 JavaScriptCore。
運行 ES6 的代碼相比於轉換后的 ES5 代碼有如下優點:
- 一樣的邏輯用 ES6 實現的代碼量比 ES5 更少。
- JavaScript 引擎對 ES6 中的語法做了性能優化,例如針對 const 申明的變量有更快的讀取速度。
所以在運行環境允許的情況下,我們要盡可能的使用原生的 ES6 代碼去運行,而不是轉換后的 ES5 代碼。
在你用上面所講的壓縮方法去壓縮 ES6 代碼時,你會發現 UglifyJS 會報錯退出,原因是 UglifyJS 只認識 ES5 語法的代碼。 為了壓縮 ES6 代碼,需要使用專門針對 ES6 代碼的 UglifyES。
UglifyES 和 UglifyJS 來自同一個項目的不同分支,它們的配置項基本相同,只是接入 Webpack 時有所區別。 在給 Webpack 接入 UglifyES 時,不能使用內置的 UglifyJsPlugin,而是需要單獨安裝和使用最新版本的 uglifyjs-webpack-plugin。 安裝方法如下:
npm i -D uglifyjs-webpack-plugin@beta
Webpack 相關配置代碼如下:
const UglifyESPlugin = require('uglifyjs-webpack-plugin') module.exports = { plugins: [ new UglifyESPlugin({ // 多嵌套了一層 uglifyOptions: { compress: { // 在UglifyJs刪除沒有用到的代碼時不輸出警告 warnings: false, // 刪除所有的 `console` 語句,可以兼容ie瀏覽器 drop_console: true, // 內嵌定義了但是只用到一次的變量 collapse_vars: true, // 提取出出現多次但是沒有定義成變量去引用的靜態值 reduce_vars: true, }, output: { // 最緊湊的輸出 beautify: false, // 刪除所有的注釋 comments: false, } } }) ] }
同時,為了不讓 babel-loader 輸出 ES5 語法的代碼,需要去掉 . babelrc
配置文件中的 babel-preset-env ,但是其它的 Babel 插件,比如 babel-preset-react 還是要保留, 因為正是 babel-preset-env 負責把 ES6 代碼轉換為 ES5 代碼。本實例提供項目完整代碼
壓縮 CSS
CSS 代碼也可以像 JavaScript 那樣被壓縮,以達到提升加載速度和代碼混淆的作用。 目前比較成熟可靠的 CSS 壓縮工具是 cssnano,基於 PostCSS。
cssnano 能理解 CSS 代碼的含義,而不僅僅是刪掉空格,例如:
margin: 10px 20px 10px 20px 被壓縮成 margin: 10px 20px
color: #ff0000 被壓縮成 color:red
還有很多壓縮規則可以去其官網查看,通常壓縮率能達到 60%。
把 cssnano 接入到 Webpack 中也非常簡單,因為 css-loader 已經將其內置了,要開啟 cssnano 去壓縮代碼只需要開啟 css-loader 的 minimize 選項。 相關 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 生成對應的 HTML 文件 new WebPlugin({ template: './template.html', // HTML 模版文件所在的文件路徑 filename: 'index.html' // 輸出的 HTML 的文件名稱 }), new ExtractTextPlugin({ filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 文件名稱加上 Hash 值 }), ], };
本實例提供項目完整代碼