之前介紹過webpack3的新特性,里面提到webpack2支持了ES6的import和export,不需要將ES6的模塊先轉成CommonJS模塊,然后再進行打包處理。正基於此,webpack2引入了tree-shaking技術,能夠在模塊的層面上做到打包后的代碼只包含被引用並被執行的模塊,而不被引用或不被執行的模塊被刪除掉,以起到減包的效果。
webpack的tree-shaking案例
下面結合實際代碼來解釋webpack2是如何實現tree-shaking的,示例代碼可到github進行下載。
示例代碼結構如圖:src中index.js為入口文件,module.js是測試的模塊文件,dist中是產出的文件。
根據webpack官網的提示,webpack支持tree-shaking,需要修改配置文件,指定babel處理js文件時不要將ES6模塊轉成CommonJS模塊,具體做法就是:
在.babelrc設置babel-preset-es2015的modules為fasle,表示不對ES6模塊進行處理。
// .babelrc文件 { "presets": [ ["es2015", { "modules": false }] ], "comments": false }
在webpack.config.js中設置babel-preset-es2015的modules為fasle,表示不對ES6模塊進行處理。
// webpack.config.js ... rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ ["es2015", { "modules": false }] ] } } ] ...
然后在module.js文件中創建三個模塊sayHello,sayBye,sayHi,並在index.js引用sayHello,sayHi;
// module.js export const sayHello = name => `Hello ${name}!`; export const sayBye = name => `Bye ${name}!`; export const sayHi = name => `Hi ${name}!`;
// index.js import { sayHello } from './module'; import { sayHi } from './module'; const element = document.createElement('h1'); element.innerHTML = sayHello('World') + sayHi('my friend'); document.body.appendChild(element);
然后在當前目錄執行 webpack 命令后,產出bundle.js的代碼如下
/* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return sayHello; }); /* unused harmony export sayBye */ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return sayHi; }); var sayHello = function sayHello(name) { return "Hello " + name + "!"; }; var sayBye = function sayBye(name) { return "Bye " + name + "!"; }; var sayHi = function sayHi(name) { return "Hi " + name + "!"; }; /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__module__ = __webpack_require__(0); var element = document.createElement('h1'); element.innerHTML = Object(__WEBPACK_IMPORTED_MODULE_0__module__["a" /* sayHello */])('World') + Object(__WEBPACK_IMPORTED_MODULE_0__module__["b" /* sayHi */])(' to meet you'); document.body.appendChild(element); /***/ })
從上面可以知道,sayBye模塊被打上了unused harmony export標簽,sayHello和sayHi被設置為__webpack_exports__的屬性,在入口文件中通過讀取__webpack_exports__的屬性取出。
bundle.js文件雖然對多余的模塊進行了標記,但是並沒有刪除,這是因為webpack還沒有執行壓縮混淆操作,可以通過webpack -p命令對產出進行壓縮處理,這時候會把打了unused harmony export 標簽的模塊刪除掉。
webpack的tree-shaking的局限性
(1)只能是靜態聲明和引用的ES6模塊,不能是動態引入和聲明的;
在打包階段對冗余代碼進行刪除,就需要webpack需要在打包階段確定模塊文件的內部結構,而ES模塊的引用和輸出必須出現在文件結構的第一級('import' and 'export' may only appear at the top level),否則會報錯。
// webpack編譯時會報錯 if (condition) { import module1 from './module1'; } else { import module2 from './module2'; }
而CommonJS模塊支持動態結構的,所以不能對CommonJS模塊進行tree-shaking處理。
(2)只能處理模塊級別,不能處理函數級別的冗余;
因為webpack的tree-shaking是基於模塊間的依賴關系,所以並不能對模塊內部自身的無用代碼進行刪除。
(3)只能處理JS相關冗余代碼,不能處理CSS冗余代碼。
目前webpack只對JS文件的依賴進行了處理,CSS的冗余並沒有給出很好的工具。最近聽了一個講座,提到了webpack-css-treeshaking-plugin,該插件基於AST對CSS冗余代碼進行了很好的處理。