压缩代码
浏览器从服务器访问网页时获取的JavaScript、CSS资源都是文本形式的,文件越大网页加载时间越长。 为了提升网页加速速度和减少网络传输流量,可以对这些资源进行压缩。 压缩的方法除了可以通过GZIP算法对文件压缩外,还可以对文本本身进行压缩。
对文本本身进行压缩的作用除了有提升网页加载速度的优势外,还具有混淆源码的作用。由于压缩后的代码可读性非常差,就算别人下载到了网页的代码,也大大增加了代码分析和改造的难度。
压缩JavaScript
目前最成熟的JavaScript代码压缩工具是UglifyJS,它会分析JavaScript代码语法树,理解代码含义,从而能做到诸如去掉无效代码、去掉日志输出代码、缩短变量名等优化。
要在Webpack中接入UglifyJS需要通过插件的形式,目前有两个成熟的插件,分别是:
- UglifyJsPlugin:通过封装 UglifyJS 实现压缩。
- ParallelUglifyPlugin:多进程并行处理压缩。
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
,默认为不转换。为了达到更好的压缩效果,可以设置为true
。 -
reduce_vars
: 是否提取出出现多次但是没有定义成变量去引用的静态值,例如把x = 'Hello'; y = 'Hello'
转换成var a = 'Hello'; x = a; y = b
,默认为不转换。为了达到更好的压缩效果,可以设置为true
。
也就是说,在不影响代码正确执行的前提下,最优化的代码压缩配置为如下:
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,直接在启动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
把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 值
}),
],
};
ParallelUglifyPlugin
用过UglifyJS的你一定会发现在构建用于开发环境的代码时很快就能完成,但在构建用于线上的代码时构建一直卡在一个时间点迟迟没有反应,其实卡住的这个时候就是在进行代码压缩。
由于压缩JavaScript代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程计算量巨大,耗时非常多。
当Webpack有多个JavaScript文件需要输出和压缩时,原本会使用UglifyJS去一个个挨着压缩再输出, 但是ParallelUglifyPlugin则会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过UglifyJS去压缩代码,但是变成了并行执行。 所以ParallelUglifyPlugin能更快的完成对多个文件的压缩工作。
使用ParallelUglifyPlugin也非常简单,把原来Webpack配置文件中内置的UglifyJsPlugin去掉后,再替换成ParallelUglifyPlugin,相关代码如下:
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 的参数
uglifyJS: {
output: {
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
},
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: false,
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
},
}),
],
};
在通过new ParallelUglifyPlugin()
实例化时,支持以下参数:
-
test
:使用正则去匹配哪些文件需要被ParallelUglifyPlugin压缩,默认是/.js$/
,也就是默认压缩所有的.js
文件。 -
include
:使用正则去命中需要被ParallelUglifyPlugin压缩的文件。默认为[]
。 -
exclude
:使用正则去命中不需要被ParallelUglifyPlugin压缩的文件。默认为[]
。 -
cacheDir
:缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回。cacheDir
用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。 -
workerCount
:开启几个子进程去并发的执行压缩。默认是当前运行电脑的CPU核数减去1。 -
sourceMap
:是否输出Source Map,这会导致压缩过程变慢。 -
uglifyJS
:用于压缩ES5代码时的配置,Object类型,直接透传给UglifyJS的参数。 -
uglifyES
:用于压缩ES6代码时的配置,Object类型,直接透传给UglifyES的参数。
其中的test
、include
、exclude
与配置Loader时的思想和用法一样。
UglifyES是UglifyJS的变种,专门用于压缩ES6代码,它们两都出自于同一个项目,并且它们两不能同时使用。
UglifyES一般用于给比较新的JavaScript 运行环境压缩代码,例如用于ReactNative的代码运行在兼容性较好的JavaScriptCore引擎中,为了得到更好的性能和尺寸,采用UglifyES压缩效果会更好。
ParallelUglifyPlugin同时内置了UglifyJS和UglifyES,也就是说ParallelUglifyPlugin支持并行压缩ES6代码。
接入ParallelUglifyPlugin后,项目需要安装新的依赖:
npm i -D webpack-parallel-uglify-plugin
安装成功后,重新执行构建你会发现速度变快了许多。如果设置cacheDir
开启了缓存,在之后的构建中会变的更快。