深入浅出 Webpack

 主页   资讯   文章   代码   电子书 

压缩代码

浏览器从服务器访问网页时获取的 JavaScript、CSS 资源都是文本形式的,文件越大网页加载时间越长。 为了提升网页加速速度和减少网络传输流量,可以对这些资源进行压缩。 压缩的方法除了可以通过 GZIP 算法对文件压缩外,还可以对文本本身进行压缩。

对文本本身进行压缩的作用除了有提升网页加载速度的优势外,还具有混淆源码的作用。 由于压缩后的代码可读性非常差,就算别人下载到了网页的代码,也大大增加了代码分析和改造的难度。

下面来一一介绍如何在 Webpack 中压缩代码。

压缩 JavaScript

目前最成熟的 JavaScript 代码压缩工具是 UglifyJS, 它会分析 JavaScript 代码语法树,理解代码含义,从而能做到诸如去掉无效代码、去掉日志输出代码、缩短变量名等优化。

要在 Webpack 中接入 UglifyJS 需要通过插件的形式,目前有两个成熟的插件,分别是:

  • UglifyJsPlugin:通过封装 UglifyJS 实现压缩。
  • ParallelUglifyPlugin:多进程并行处理压缩,4-4使用ParallelUglifyPlugin中有详细介绍。

由于 ParallelUglifyPlugin 在4-4使用ParallelUglifyPlugin中介绍过就不再复述, 这里重点介绍如何配置 UglifyJS 以达到最优化的压缩 JavaScript 代码。

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-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 值
    }),
  ],
};

本实例提供项目完整代码