Tree-shaking 文檔例子編譯後結果和實際不同?

根據webpack 官方 v.4.43.0 的說法是

tree shaking 是一個術語,通常用於描述移除 JavaScript 上下文中的未引用代碼(dead-code)。它依賴於 ES2015 模塊語法的 靜態結構 特性,例如 import 和 export。這個術語和概念實際上是由 ES2015 模塊打包工具 rollup 普及起來的。

那總體流程簡述如下:

分析程序流,ES Module 的靜態結構,編譯時判斷到底都加載了哪些模塊,標識未被使用的模塊和變量,最後啓用 uglify 混淆並刪除未被使用的代碼。

例子

筆者傳到了 git 倉庫裏,主要是有兩個文件

// math.js
export function square(x) {
  return x * x;
}

export function cube(x) {
  return val * val * val;
}
// index.js
import { cube } from './math.js';

function component() {
  // var element = document.createElement("div");
  var element = document.createElement('pre');
  // element.innerHTML = _.join(["Hello", "webpack"], " ");
  element.innerHTML = [
    'Hello webpack!',
    '5 cubed is equal to ' + cube(5)
  ].join('\n\n');

  return element;
}

document.body.appendChild(component());

官網說法

官網說道在 mode: development 的模式下編譯,會出現 /* unused harmony export square */ 標識

dist/bundle.js (around lines 90 - 100)

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
  'use strict';
  /* unused harmony export square */
  /* harmony export (immutable) */ __webpack_exports__['a'] = cube;
  function square(x) {
    return x * x;
  }

  function cube(x) {
    return x * x * x;
  }
});

最後在 mode: prodution 時啓用uglify 壓縮混淆代碼時,官網給的例子只是說道會去掉沒有引入的 square 函數。但能看到引入的 cube 函數。

顯然,現在整個 bundle 都已經被 minify(壓縮) 和 mangle(混淆破壞),但是如果仔細觀察,則不會看到引入 square 函數,但能看到 cube 函數的混淆破壞版本(function r(e){return eee}n.a=r)

真實實驗結果

然而我自己的實驗結果有些差別!復現的例子筆者已經傳到了我的 Git 倉庫

在 mode: development 下編譯實際產生的代碼是帶着 eval 函數的。並且也沒有 /* unused harmony export square */ 的標識。

/***/ "./src/math.js":
/*!*********************!*\
  !*** ./src/math.js ***!
  \*********************/
/*! exports provided: square, cube */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"square\", function() { return square; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"cube\", function() { return cube; });\n// import { toPrimitive } from './util';\r\n\r\nfunction square(x) {\r\n  return x * x;\r\n}\r\n\r\nfunction cube(x) {\r\n//   let val = toPrimitive(x);\r\n  return val * val * val;\r\n}\r\n\n\n//# sourceURL=webpack:///./src/math.js?");

/***/ }),

並且當 mode 爲 prodution 時,編譯的結果他不僅去掉了 square,甚至連整個依賴都被直接合並進了主函數裏(注意下文的代碼裏調用依賴函數的地方直接變成了 val * val * val,牛逼啊)。而這又是怎麼回事呢?

.......省略 webpack 基礎代碼50+行
([
  function (e, t, n) {
    "use strict";
    var r;
    n.r(t),
      document.body.appendChild(
        (((r = document.createElement("pre")).innerHTML = [
          "Hello webpack!",
          "5 cubed is equal to " + val * val * val,
        ].join("\n\n")),
        r)
      );
  },
]);

爲此我提了一個 issue 去詢問,得到答覆說是文檔太老了有待更新。

總之我還實驗了 util.js -> math.js -> index.js 的三層引用,發現它還是能最後被直接合併入 index 的代碼塊中,而不是外部的函數。

我猜測這個 webpack 4.x 的機制,我猜測是打包時直接能把所有依賴的並且是靜態 import 的函數直接合併入引入的地方。期待能有大佬來解釋

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章