[译]Uglify vs Babel-minify vs Terser 一场代码压缩的pk

[译]Uglify vs. Babel-minify vs. Terser: 一场代码压缩的战役

原文连接-uglify-vs-babel-minify-vs-terser-a-mini-battle-royalecss

什么是缩小?

缩小(也是最小化)是从 解释的编程语言标记语言 的源代码中删除全部没必要要的字符而不改变其功能的过程。这些没必要要的字符一般包括前端

  • 空白字符
  • 换行符
  • 评论
  • 阻止分隔符

让咱们试着经过一个例子来理解这一点。下面的代码显示了一个示例 JavaScript 代码,用于建立数组并使用前20个整数值对其进行初始化:node

var array = []; for(var i = 0 ; i < 20 ; i ++){ array [ i ] = i ; }   

复制代码

如今,让咱们尝试手动缩小这些代码。下面的示例显示了咱们如何只使用一行代码来实现相同的功能:react

for(var a = [ i = 0 ]; ++ i < 20 ; a [ i ] = i );

复制代码

首先,咱们减小了 array 变量的名称(array to a),而后咱们将它移动到循环初始化构造中。咱们还将第 3 行的数组初始化移动到循环中。结果,字符数和文件大小显着减小。webpack

为什么要缩小?

如今咱们了解缩小是什么,很容易猜到咱们为何这么作。因为缩小缩小了源代码的大小,所以其在网络上的传输变得更有效。git

这对于 Web 和 移动应用程序 尤为有用,其中前端向后端发出http请求以获取文件,用户数据等资源。对于至关大的应用程序,如InstagramFacebook,前端一般安装在用户的设备上,然后端和数据库做为本地服务器或云中的多个实例存在。github

在诸如加载照片的典型用户操做中,前端向后端发出http请求,然后端又向数据库实例发出请求以获取所请求的资源。这涉及经过网络传输数据,而且该过程的效率与正在传输的数据的大小成正比。这正是缩小有用的地方。web

那怎么进行缩小呢?

在上一节中,咱们看到了如何手动缩小一个简单的代码。但这对于巨大的代码库来讲实际上不是一个可扩展的解决方案。多年来已经有各类构建工具来缩小 JavaScript 代码。接下来让咱们来看看最受欢迎的一些解决方案:数据库

UglifyJS

UglifyJS 的目标是缩小和压缩代码。让咱们继续使用如下命令安装它:npm

npm install uglify - js - g

复制代码

如今让咱们尝试在JavaScript模块上运行uglify。为此,我编写了一个示例模块,代码以下:sample.js

var print = "Hello world! Let's minify everything because, less is more"

var apple = [1,2,3,4,5]

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

    
复制代码

此代码基本上在循环内打印字符串和数组。咱们屡次复制for()循环以增长文件的大小,这样咱们就能够更好地看到Uglify的效果。

文件大小为1.2KB

UglifyJS选项

咱们能够看到,Uglify 有不少选项,其中大部分都是不言自明的。那么,让咱们继续尝试其中几个:

文件大小为944B,并经过重复打印字符串和数组值来执行

咱们在文件上使用了 -c(compress)和-m(mangle)选项并对其进行了修改。文件大小减小到944B,减小了大约22%。如今,让咱们看看文件内容,看看它是如何经过uglification更改的:-c -m sample.js

var print="Hello world! Let's minify everything because, less is more",apple=[1,2,3,4,5];for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log    (print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);for(i=0;i<5;i++)console.log(print),console.log(apple[i]);

复制代码

从上面的示例中,咱们能够看到输出的内容具备相同的代码,没有任何空格和换行符。

为了进一步了解Uglify的效果,让咱们用原型函数编写一个示例JS代码:

// comments 

function sample(helloworld) {
  this.hello = helloworld
}
sample.prototype.printvar = function()
{
  console.log(this.hello)
}


var hello = "hello world"
var s = new sample(hello)
s.printvar()
 
 
复制代码

如今,让咱们编译一下这段代码:

Uglified 代码是131B

请注意,我使用了Uglify (附带)选项。此选项将全部内容嵌入到一个大函数中,具备可配置的参数和值。为了理解这意味着什么,让咱们看一下输出的内容:-e

!function(){function o(o){this.hello=o}o.prototype.printvar=function(){console.log(this.hello)};new o("hello world").printvar()}();


复制代码

uglified输出中,咱们能够看到函数名称 sample 已经消失,而且被替换为 o。全部代码都包含在一个大函数中,这会以可读性为代价进一步减少代码的大小。

如今,让咱们看看另外一个流行的缩小器:babel-minify

babel-minify(又名Babili)

babel- minify,之前称为Babili,是一个实验项目,试图使用Babel的工具链(用于编译)以相似的方式作一些事情:缩小。它目前是 0.x,官方存储库不建议在生产中使用它。

当咱们已经拥有Uglify时,为何咱们须要这个工具?若是你注意到前面的例子,我没有使用最新版ECMAScript的语法。这是由于Uglify还不支持它 - 可是babel-minify能够。

这是由于它只是一组Babel插件,Babel已经了解了解析器Babylon的新语法。此外,当能够仅定位支持较新 ES 功能的浏览器时,您的代码大小能够更小,由于您没必要进行转换而后缩小它。

babel-minify 以前,咱们将运行Babel来转换ES6,而后运行Uglify来缩小代码。经过babel-minify,这个两步过程基本上变成了一个步骤。

babel-minify 是ES2015 +知道的,由于它是使用 Babel 工具链构建的。它被写成一组Babel插件,带有 babel-preset-minify 的消耗品。咱们来看一个例子。

让咱们使用如下命令在本地安装 BabelBabel 预设以转换ES6:

npm install - save - dev @babel / core @babel / cli
 npm install - save - dev babel - plugin - transform - es2015 - classes

复制代码

如今,让咱们用ES6语法编写一个:sample.js

//ES6 Syntax

class sample {
  constructor(helloworld) {
      this.hello = helloworld
  }
  printvar() {
    console.log(this.hello)
  }
}

var hello = "hello world"
var s = new sample(hello)
s.printvar()

复制代码

让咱们使用如下命令来转换此代码:

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

//ES6 Syntax
let sample = function () {
  function sample(helloworld) {
    _classCallCheck(this, sample);

    this.hello = helloworld;
  }

  _createClass(sample, [{
    key: "printvar",
    value: function printvar() {
      console.log(this.hello);
    }
  }]);

  return sample;
}();

var hello = "hello world";
var s = new sample(hello);
s.printvar();

复制代码

已转换代码的内容以下所示:

如您所见,ES6 类语法已转换为常规函数语法。如今,让咱们在这个内容上运行 Uglify 来缩小它:

uglifyjs sample-transpiled.js -c -m -e -o sample-transpiled-uglified.js

复制代码

如今,压缩的内容以下所示:

function _classCallCheck(e,l){if(!(e instanceof l))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,l){for(var n=0;n<l.length;n++){var r=l[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function _createClass(e,l,n){return l&&_defineProperties(e.prototype,l),n&&_defineProperties(e,n),e}let sample=function(){function e(l){_classCallCheck(this,e),this.hello=l}return _createClass(e,[{key:"printvar",value:function(){console.log(this.hello)}}]),e}();var hello="hello world",s=new sample(hello);s.printvar();

复制代码

若是咱们比较这些文件大小,sample.js is 227B, sample-transpiled.js is 1KB, and sample-transpiled-uglified.js is 609B. 很明显的发现,这不是最佳的处理过程,由于它会致使文件大小的增长。 为了解决这个问题,引入了 babel-minify。 如今,让咱们安装 babel-minify 并尝试转换和缩小代码。

npm install babel-minify --save-dev
复制代码

接下来咱们使用下面的命令来压缩相同的 sample.js

minify sample.js > sample-babili.js
复制代码

咱们能够看下执行事后的输出内容:

class sample{constructor(l){this.hello=l}printvar(){console.log(this.hello)}}var hello="hello world",s=new sample(hello);s.printvar();
复制代码

这个文件的大小是 135B, 将近压缩了40%,是一种更好的压缩代码的方式。它直接提升了经过网络传输它的效率,并能够在不少浏览器上面运行,由于 Babel 能够转换代码。还有各类插件可使用。

Terser

TerserES6+JavaScript 解析器 和 mangler/compressor 工具包, 它多是最有效的。Terser 建议您搭配 Rollup 打包使用,这样会产生更小的代码。

Rollup 是一个相似于 webpack 的 模块打包器,它是为了尽量高效地构建 JavaScript 库的平面可分发版而建立的,利用了ES2015模块的巧妙设计。ES6 模块最终仍是要由浏览器原生实现,但当前 Rollup 可使你提早体验。

虽然 Rollup 是一个不错的选择,但若是你使用的是 webpack> v4,默认状况下会使用 Terser。 能够经过切换布尔变量来启用 Terser,以下所示:

module.exports = {
  //...
  optimization: {
    minimize: false
  }
};
复制代码

安装 Terser

npm install terser -g
复制代码

Terser 命令行具备如下语法:

terser [input files] [options]
复制代码

Terser 能够采用多个输入文件。 建议您先传递输入文件,而后传递选项。 Terser 将按顺序解析输入文件并应用任何压缩选项。

这些文件在同一个全局范围内解析 - 也就是说,从文件到另外一个文件中声明的某个变量/函数的引用将被正确匹配。 若是未指定输入文件,则 Terser 将从STDIN 读取。

若是您但愿在输入文件以前传递选项,请使用双短划线将二者分开以防止输入文件用做选项参数:

terser --compress --mangle -- input.js
复制代码

如今让咱们尝试运行咱们的sample.js代码:

terser -c toplevel,sequences=false --mangle -- sample.js > sample-terser.js
复制代码

如下是此输出的内容

new class{constructor(l){this.hello=l}printvar(){console.log(this.hello)}}("hello world").printvar();
复制代码

咱们能够看到,到目前为止,咱们所看到的全部工具中的输出是迄今为止最好的。 文件大小为 102B,比原始 sample.js 大小减小了近 55%。 可使用 --help 选项找到 Terser 的其余命令行选项。

Terser 命令行选项。 在全部选项中,咱们最感兴趣的是 --compress--mangle,每一个选项都有本身的选项集。 --compress--mangle 的选项使您能够控制如何处理源代码以生成缩小的输出。

若是您注意到,咱们已经在第一个 Terser 示例中使用了 --compress 的顶层和序列选项。 例如,您能够将 true 传递给 --compressdrop_console 选项以从源代码中删除全部 console.* 函数,若是您不想破坏类名,则可使用 keep_classnames 选项。

有时,美化生成的输出可能颇有用。 您可使用 --beautify 选项执行此操做。 许多构建工具使用Terser - 在这里找到它们

让咱们尝试在源文件中使用 drop_console 选项来查看console.log()函数是否被删除:

terser --compress drop_console=true -- sample.js > sample-drop-console.js
复制代码

如今,让咱们看看源代码,sample.js的内容:

//ES6 Syntax

class sample {
  constructor(helloworld) {
      this.hello = helloworld
  }
  printvar() {
    console.log(this.hello)
  }
}

var hello = "hello world"
var s = new sample(hello)
s.printvar()
复制代码

而如今输出,sample-drop-console.js:

new class{constructor(r){this.hello=r}printvar(){}}("hello world").printvar();
复制代码

正如咱们所看到的,代码被进一步破坏和压缩,这个新文件的大小仅为 79B。 与不使用 drop_console选项时所看到的 55%相比,这减少了65%。 这样,咱们能够根据项目的要求使用选项来平衡可读性和性能。

性能比较

到目前为止,咱们共研究了三种最流行的压缩 js 和css 代码的工具。Babel 存储库比较基准测试结果提供了这些统计信息,能够帮助您为项目选择合适的压缩工具。

经过上面的图,咱们能够看拿到,Terser 在基于 React项目中表现最好。您还能够在查看其余 web 框架的表现结果。

为 React 项目配置 minifiers

在本节中,咱们将介绍为 React 应用程序配置 minifier的过程。 让咱们在这个例子中使用Terser来缩小React应用程序。 为了以简化的方式实现这一目标,咱们使用 webpackwebpack 是一个工具链,用于将全部文件捆绑到一个名为bundle.js的文件中,该文件能够高效加载。 让咱们使用如下命令安装 webpack

npm install webpack --save-dev
复制代码

咱们还须要安装一些Babel插件以便转换代码:

npm install babel-core babel-loader babel-preset-env babel-preset-react\babel-preset-stage-0 --save-dev
复制代码

接下来,让咱们安装Terser插件:

npm install terser-webpack-plugin --save-dev
复制代码

咱们还须要.svg.css的 loader,因此咱们也要安装它们:

npm install svg-inline-loader --save-dev
npm install css-loader --save-dev
复制代码

在这个阶段,咱们只须要配置 webpack.config.js 文件,具体配置能够参考下面:

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  mode: 'development',
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules)/,
      use: {
        loader: 'babel-loader',
        options:{
          presets: ['@babel/preset-react']
        }
      }
    },
    {
      test: /\.svg$/,
      loader: 'svg-inline-loader'
    },
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
    }]
  },
  optimization: {
    minimizer: [new TerserPlugin()],
  },
}

复制代码

从上面的代码中,咱们能够看到webpack的入口文件是src /中的 index.js,最终输出将做为bundle.js存储在dist /目录中。 优化字段将 TerserPlugin拉入缩小过程。 如今,让咱们运行webpack来静态构建咱们的应用程序以进行生产。

webpack的输出。

咱们能够看到 webpack 在全部文件上运行 loader 和插件,并构建了一个大小为 938KBbundle.js,而咱们的整个应用程序比这大得多。 这是webpack以及相关加载器和插件的真正威力。

最近推出了一些新的捆绑包。 其中,RollupParcel愈来愈受欢迎。 任何捆绑工具的基础配置和设置相似于 webpack。 您能够在此处找到 webpackRollupParcel之间的性能比较, 连接在此

结论

最后一点,让我经过展现npm趋势的片断来结束这篇文章。 咱们能够看到,Terser 在过去六个月中得到了极大的人气。 与其余缩小工具相比,这能够直接归因于其更好的性能。

感谢您阅读这篇文章。 我但愿你能学习到压缩代码的一些知识和对一些长期的疑惑点可以获得清晰地解决。

相关文章
相关标签/搜索