# Javascript 代码压缩

近几年 React,Vue 等构造单页的应用,使得前端的快速发展,项目也变得越发复杂。前端构建的静态资源也越来越庞大,这些资源中 Javascript 文件占据 80% 以上,如何压缩变得至为重要。

文件更小的体积有以下有个益处:

  • 用户体验上,加载速度更快;
  • 服务器压力上,更小的带宽、更少的服务器费用;

当下对于前端的构建,大抵都是应用 webpack 来对代码进行压缩优化。了解代码压缩内部是如何实现的,这对于我们在控制台对生产环境代码的调试有一定助益,这也是我们开发者对技术精进的精神体现。

# 查看资源体积

对于我们所编写的代码,在操作系统中其实就是一个文件。根据文件系统中的 stat 信息我们可以查看该文件的大小。

stat package.json

# 16777220 8653536504 -rw-r--r-- 1 rzy staff 0 383 "Mar 14 09:49:52 2020" "Mar 10 22:57:45 2020" "Mar 10 22:57:45 2020" "Mar 10 22:45:32 2020" 4096 8 0 package.json

哇哦,虽然提供的信息很全,但是我只是想要查看文件体积大小而已,有没有更直观的方式呢?

wc -c package.json

# 383 package.json

wc-c

事实也是 383 字节。

# 压缩思路设计

# 合并声明以及布尔值简化

// 压缩前
const x = 1;
const y = 2;

// 压缩后
const x = 1, y = 2;

布尔值简化如下:

// 压缩前
!a && !b && !c

// 压缩后
!(a||b||c)

这个示例需要解析 AST。

# 压缩变量名

// 压缩前
function sum(firstNum, secondNum) {
  return firstNum + secondNum;
}

// 压缩变量名
function sum(x, y) {
  return x + y;
}

// 再压缩,去空格
function s(x,y){return x+y}
  • firstNumsecondNum 在函数的作用域中,在作用域外不会引用它,此时可以让它们的变量名称更短;
  • 如果函数 sum 在一个模块中,且不被导出,也可以把函数名也缩短;
  • 当完成代码压缩 (compress) 时,代码的混淆 (mangle) 也一并完成。但此时缩短变量的命名也需要 AST 支持,不至于在作用域中造成命名冲突。

# 去除多余字符

// 压缩前:求和
function sum (x, y) {
  return x + y;
}

去除空格,换行及注释后

function s(x,y){return x+y}

替换掉多余字符后会有一些问题,比如多行代码压缩到一行时要注意行尾分号。这就需要 AST 来解决。那 AST 到底是什么?

# AST

抽象语法树,是 js 代码解析后的最小词法单元,通过 Parser 完成。

# 场景

  • eslint: 校验你的代码风格
  • babel: 编译代码到 ES 低版本
  • taro/mpvue: 多端运行的小程序框架
  • GraphQL: 解析客户端查询

不同的解析器会生成不同的 AST,其中 babel 使用的解析器 babylon,而 uglify 在代码压缩中使用到的解析器是 UglifyJS,是不是很熟悉。

可以自己在 AST Explorer 感受下,如下图:

AST Explorer

# 压缩过程

压缩过程

其实 babel 和 eslint 的流程也是一样。

# 名库堂 UglifyJS

一个代码压缩混淆的库 UglifyJS2在线演示 Demo。webpack 中内置的代码压缩插件就是使用了它。

UglifyJS

真正使用它来压缩代码时,你只需要面向配置编程即可。可参考 UglifyJS 官方文档

{
  ecma: undefined,
  warnings: false,
  parse: {},
  compress: {},
  mangle: true,
  module: false,
  output: null,
  toplevel: false,
  nameCache: null,
  ie8: false,
  keep_classnames: undefined,
  keep_fnames: false,
  safari10: false,
}

# webpack 压缩代码

知道代码压缩过程,就可以应用到生产环境中去了。webpack 中与性能优化相关的都可以在 optimization 中找到,TerserPlugin 是一个底层基于 uglifyjs 的用来压缩 JS 的插件。

const TerserPlugin = require('tenser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          ecma: undefined,
          warnings: false,
          parse: {},
          compress: {},
          mangle: true, // Note `mangle.properties` is `false` by default.
          module: false,
          output: null,
          toplevel: false,
          nameCache: null,
          ie8: false,
          keep_classnames: undefined,
          keep_fnames: false,
          safari10: false,
        },
      }),
    ],
  },
};

阅读原文或评论