Webpack构建优化——压缩代码

压缩代码

浏览器从服务器访问网页时获取的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-loaderminimize选项。 相关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的参数。

其中的testincludeexclude与配置Loader时的思想和用法一样。
UglifyES是UglifyJS的变种,专门用于压缩ES6代码,它们两都出自于同一个项目,并且它们两不能同时使用。
UglifyES一般用于给比较新的JavaScript 运行环境压缩代码,例如用于ReactNative的代码运行在兼容性较好的JavaScriptCore引擎中,为了得到更好的性能和尺寸,采用UglifyES压缩效果会更好。
ParallelUglifyPlugin同时内置了UglifyJS和UglifyES,也就是说ParallelUglifyPlugin支持并行压缩ES6代码。

接入ParallelUglifyPlugin后,项目需要安装新的依赖:

npm i -D webpack-parallel-uglify-plugin

安装成功后,重新执行构建你会发现速度变快了许多。如果设置cacheDir开启了缓存,在之后的构建中会变的更快。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容