[譯]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 在過去六個月中得到了極大的人氣。 與其餘縮小工具相比,這能夠直接歸因於其更好的性能。

感謝您閱讀這篇文章。 我但願你能學習到壓縮代碼的一些知識和對一些長期的疑惑點可以獲得清晰地解決。

相關文章
相關標籤/搜索