參考連接:webpack打包分析javascript
源文件java
// index.js const util = require('./util'); console.log(util.hello()) // util.js function hello () { return 'hello' } function bye () { return 'bye' } module.exports = { hello, bye }
/* 1 */
對應的是index.js,/* 2 */
對應的是util.js,/* 0 */
是執行1的主模塊文件,能夠看到模塊函數有三個參數module
、exports
、__webpack_reuire__
,這些都是在當即執行函數內部傳遞的
__webpack_require__
函數及其屬性,最後一句話return __webpack_require__((__webpack_require__.s = 0));
表示執行moduleId爲0的模塊
__webpack_require__
函數中緩存已經調用的模塊,初始化exports等參數並將這些參數傳入module的執行中webpack
var installedModules = {}; // 緩存已引入的模塊 function __webpack_require__(moduleId) { if (installedModules[moduleId]) { //直接返回引入模塊 return installedModules[moduleId].exports; } //初始化模塊 var module = (installedModules[moduleId] = { i: moduleId, l: false, exports: {} }); // 執行對應的模塊 modules[moduleId].call( module.exports, module, module.exports, __webpack_require__ ); // 表示模塊已經加載 module.l = true; return module.exports; }
// 掛載模塊 __webpack_require__.m = modules; // 掛載緩存 __webpack_require__.c = installedModules; // 實際是Object.prototype.hasOwnProperty __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; // 定義對象取值的函數 __webpack_require__.d = function(exports, name, getter) { if (!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { configurable: false, enumerable: true, get: getter }); } }; // 判斷是否爲es模塊,若是是則默認返回module['default'],不然返回module __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module["default"]; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, "a", getter); return getter; };
- webpack打包文件是一個當即執行函數IIFE - 模塊文件在外包裹了一層函數,以數組的形式做爲參數傳入當即執行函數中 - IIFE內部有緩存已執行函數的機制,第二次執行直接返回exports - IIFE 最後執行id爲0的模塊`__webpack_require__((__webpack_require__.s = 0));`將整個modules執行串聯起來
// // index.js import addCounter from './util' addCounter() //util.js let counter = 3; export default function addCounter () { return counter++ }
// bundle.js /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util__ = __webpack_require__(2); // // index.js Object(__WEBPACK_IMPORTED_MODULE_0__util__["a" /* default */])() /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (immutable) */ __webpack_exports__["a"] = addCounter; //util.js let counter = 3; function addCounter () { return counter++ }
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
標記當前引入import
文件爲es6模塊。__webpack_require__.n
當__esModule
爲true是,__webpack_require__.d(getter, "a", getter);
獲取a的是module.default的值,上面打包文件中Object(__WEBPACK_IMPORTED_MODULE_0__util__["a" /* default */])()
體現了這一點,保證 counter 變量取的是值的引用。es6值引用源文件git
// // index.js import { counter, addCounter} from './util' console.log(counter); addCounter() console.log(counter); //util.js export let counter = 3; export function addCounter () { return counter++ }
// bundle.js /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util__ = __webpack_require__(2); // // index.js console.log(__WEBPACK_IMPORTED_MODULE_0__util__["b" /* counter */]); // 3 Object(__WEBPACK_IMPORTED_MODULE_0__util__["a" /* addCounter */])() console.log(__WEBPACK_IMPORTED_MODULE_0__util__["b" /* counter */]); // 4 /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return counter; }); /* harmony export (immutable) */ __webpack_exports__["a"] = addCounter; //util.js let counter = 3; function addCounter () { return counter++ } /***/ })
/* 2 */
中counter(「b」)的定義使用了__webpack_require__.d方法,也就是直接賦值在exports的屬性上,import時獲取的也就是引用值了cjs值引用源文件es6
// index.js const {counter, addCounter} = require('./util'); console.log(counter); addCounter() console.log(counter); //util.js let counter = 3; function addCounter () { counter = counter+1 } module.exports = { counter, addCounter }
// bundle.js /* 1 */ /***/ (function(module, exports, __webpack_require__) { // index.js const {counter, addCounter} = __webpack_require__(2); console.log(counter); // 3 addCounter() console.log(counter); //3 /***/ }), /* 2 */ /***/ (function(module, exports) { let counter = 3; function addCounter () { counter = counter+1 } module.exports = { counter, addCounter }
//index.js import { bye } from './bye' import hello from './hello' bye(); hello(); //hello function hello() { console.log('hello') } module.exports = hello; //bye.js function bye() { console.log('bye') } module.exports = { bye };
// bundle.js /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__bye__ = __webpack_require__(2); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__bye___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__bye__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__hello__ = __webpack_require__(3); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__hello___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1__hello__); Object(__WEBPACK_IMPORTED_MODULE_0__bye__["bye"])(); __WEBPACK_IMPORTED_MODULE_1__hello___default()(); /***/ }), /* 2 */ /***/ (function(module, exports) { function bye() { console.log('bye') } module.exports = { bye }; /***/ }), /* 3 */ /***/ (function(module, exports) { function hello() { console.log('hello') } module.exports = hello; /***/ }) /******/ ]);
__esModule
標記,注意到調用時候的不用Object(__WEBPACK_IMPORTED_MODULE_0__bye__["bye"])()
引入bye是以一個對象的形式引入的。__WEBPACK_IMPORTED_MODULE_1__hello___default()();
在處理cjs時每一個模塊有一個defalut值,若是是cjs的狀況會返回module的getter,而源文件引入Hello是default值,直接執行返回的getter//index.js const { bye } = require('./bye') const hello = require('./hello') bye(); hello.default(); //hello.js function hello() { console.log('hello') } export default hello; //bye.js export function bye() { console.log('bye') }
// bundle.js /* 1 */ /***/ (function(module, exports, __webpack_require__) { const { bye } = __webpack_require__(2) const hello = __webpack_require__(3) bye(); hello.default(); /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony export (immutable) */ __webpack_exports__["bye"] = bye; function bye() { console.log('bye') } /***/ }), /* 3 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); function hello() { console.log('hello') } /* harmony default export */ __webpack_exports__["default"] = (hello); /***/ }) /******/ ]);
export function bye編譯成
__webpack_exports__["bye"] = bye;export defalut編譯成
__webpack_exports__["default"] = (hello)`;因此在默認引入hello調用時要手動調用default方法連接:Webpack之treeShaking;tree shakinggithub
最簡單最直接的分離代碼方式,可是會有重複的問題,好比說引入lodashweb
import {each} from 'lodash'
,仍是會引入整個lodash庫,目前最好的解決辦法是按fp引入好比:import each from 'lodash/fp/each';
分別在js/index,js/bye中引入lodash,像下面的配置就會存在重複的each方法,若是不是按fp的方式引入lodash,則至關於引入了兩個lodash大小的文件是對資源的浪費。json
module.exports = { entry: { index: './src/index.js', bye: './src/bye.js' }, output: { filename: 'js/[name].js' },
因此咱們在webpack4以前使用CommonsChunkPlugin來抽取公共組件,webpack4開始使用optimization
選項來作簡化配置數組
//webapck4以前 new webpack.optimize.CommonsChunkPlugin({ name: 'commons', // (公共 chunk(commnon chunk) 的名稱) filename: 'commons.js', // (公共chunk 的文件名) minChunks: 2, // (模塊必須被2個 入口chunk 共享) })
webpack 提供了兩種方式進行動態導入,符合 ECMAScript 提案的import()
和webpack特定的方法require.ensure()
promise
import() 調用會在內部用到 promises。若是在舊有版本瀏覽器中使用 import(),記得使用 一個 polyfill 庫(例如 es6-promise 或 promise-polyfill),來 shim Promise。
簡單的例子:在index.js中動態引入hello.js,打包後會生成一個0.js和index.js
//index,js import('./hello').then(hello => { hello.default(); })
先來看index.js中[modules]引入的模塊,能夠發現引入的動態引入的hello.js文件並不存在index.js,實際是在新生成的0.js
文件中,這裏先按下不提,/* 1 */
中最重要的兩個函數__webpack_require__.e
和__webpack_require__.bind
成爲動態import的關鍵
[ /* 0 */, /* 1 */ /***/ (function (module, exports, __webpack_require__) { __webpack_require__.e/* import() */(0/* duplicate */).then(__webpack_require__.bind(null, 0)) .then(hello => { hello.default(); }) /***/ }) /******/]
分析__webpack_require__.e(0)
/******/ __webpack_require__.e = function requireEnsure(chunkId) { /******/ var installedChunkData = installedChunks[chunkId]; /******/ if (installedChunkData === 0) { /******/ return new Promise(function (resolve) { resolve(); }); /******/ } /******/ /******/ // a Promise means "currently loading". /******/ if (installedChunkData) { /******/ return installedChunkData[2]; /******/ } /******/ /******/ var promise = new Promise(function (resolve, reject) { /******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; /******/ }); /******/ installedChunkData[2] = promise; /******/ /******/ // start chunk loading /******/ var head = document.getElementsByTagName('head')[0]; /******/ var script = document.createElement('script'); /******/ script.type = "text/javascript"; /******/ script.charset = 'utf-8'; /******/ script.async = true; /******/ script.timeout = 120000; /******/ /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } /******/ script.src = __webpack_require__.p + "js/" + chunkId + ".js"; /******/ var timeout = setTimeout(onScriptComplete, 120000); /******/ script.onerror = script.onload = onScriptComplete; /******/ function onScriptComplete() { /******/ // avoid mem leaks in IE. /******/ script.onerror = script.onload = null; /******/ clearTimeout(timeout); /******/ var chunk = installedChunks[chunkId]; /******/ if (chunk !== 0) { /******/ if (chunk) { /******/ chunk[1](new Error('Loading chunk ' + chunkId + ' failed.')); /******/ } /******/ installedChunks[chunkId] = undefined; /******/ } /******/ }; /******/ head.appendChild(script); /******/ /******/ return promise; /******/ };
installedChunks
對象用來標記模塊是否加載過,當installedChunks[chunkId]爲0時表示已經加載成功installedChunkData
保存新建的promise,用於判斷模塊是否處於加載中狀態通過script標籤的方法,代碼會引入0.js
webpackJsonp([0],[ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); function hello() { console.log('hello') } /* harmony default export */ __webpack_exports__["default"] = (hello); /***/ }) ]);
webpackJsonp
方法,分析下這個方法var parentJsonpFunction = window["webpackJsonp"]; /******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { /******/ // add "moreModules" to the modules object, /******/ // then flag all "chunkIds" as loaded and fire callback /******/ var moduleId, chunkId, i = 0, resolves = [], result; /******/ for (; i < chunkIds.length; i++) { /******/ chunkId = chunkIds[i]; /******/ if (installedChunks[chunkId]) { /******/ resolves.push(installedChunks[chunkId][0]); /******/ } /******/ installedChunks[chunkId] = 0; /******/ } /******/ for (moduleId in moreModules) { /******/ if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { /******/ modules[moduleId] = moreModules[moduleId]; /******/ } /******/ } /******/ if (parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules); /******/ while (resolves.length) { /******/ resolves.shift()(); /******/ } /******/ /******/ };
__webpack_require__.bind(null, 0)
,前面咱們分析過__webpack_require__
方法,主要做用是緩存已經調用的模塊,初始化exports等參數並將這些參數傳入module的執行中。後一個promise鏈then方法真正執行hello方法。DCE(dead code elimination)保證代碼結果不變的前提下,去除無用代碼
DCE{ 優勢:減小程序體積、執行時間 Dead Code{ 程序中沒有執行的代碼(不可能進入的分支、return以後的語句) 致使dead variable的代碼(寫入變量後再也不讀取的代碼) } }
harmony import
被使用過的export標記爲harmony export [type]
mode=development
時都認爲是被使用的,不會出現unused harmony export
詳情 harmony export [FuncName]
,其中[FuncName]爲export的方法名稱//math.js export function square(x) { return x * x; } export function cube(x) { return x * x * x; } //index.js import { cube } from './math.js'; function component() { var element = document.createElement('pre'); element.innerHTML = [ 'Hello webpack!', '5 cubed is equal to ' + cube(5) ].join('\n\n'); return element; } document.body.appendChild(component());
//bundle.js (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* unused harmony export square */ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return cube; }); function square(x) { return x * x; } function cube(x) { return x * x * x; } /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0); function component() { var element = document.createElement('pre'); element.innerHTML = [ 'Hello webpack!', '5 cubed is equal to ' + Object(_math_js__WEBPACK_IMPORTED_MODULE_0__[/* cube */ "a"])(5) ].join('\n\n'); return element; } document.body.appendChild(component()); /***/ }) /******/ ]);
tree shaking對於類是總體標記爲導出的,代表webpack tree shaking只處理頂層內容,類和對象內部都不會再被分別處理。由於類調用屬性方法有不少狀況,好比util[Math.random() > 0.5 ? 'hello' : 'bye']這種判斷型的字符串方式調用
// index.js import Util from './util' let util = new Util() let result1 = util.hello() console.log(result1) // util.js export default class Util { hello() { return 'hello' } bye() { return 'bye' } } // util.js export default class Util { hello() { return 'hello' } bye() { return 'bye' } }
//bundle.js (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Util; }); // util.js class Util { hello() { return 'hello' } bye() { return 'bye' } } /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0); // index.js let util = new _util__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"]() let result1 = util.hello() console.log(result1) /***/ }) /******/ ]);
在執行某個方法或者文件時會對全局其餘內容產生影響的代碼。在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個 export 或多個 export。舉例說明,例如 polyfill,在各種prototype假如方法,它影響全局做用域,而且一般不提供 export。
//index.js import Util from './util' console.log('Util unused') //util.js console.log('This is Util class') export default class Util { hello () { return 'hello' } bye () { return 'bye' } } Array.prototype.hello = () => 'hello'
//bundle.js function(e, t, o) { e.exports = o(1); }, function(e, t, o) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); o(2); console.log("Util unused"); }, function(e, t, o) { "use strict"; console.log("This is Util class"); Array.prototype.hello = () => "hello"; }
unused harmony export default
,在壓縮時去處class Util的引入,可是先後的兩句代碼依舊保留。由於編譯器不知道這兩句代碼時什麼做用,爲了保證代碼執行效果不變,默認指定全部代碼都有反作用。//index.js import {hello, bye} from './util' let result1 = hello() let result2 = bye() console.log(result1) // util.js export function hello () { return 'hello' } export function bye () { return 'bye' }
//bundle.js function(e, t, n) { e.exports = n(1); }, function(e, t, n) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); var r = n(2); let o = Object(r.b)(); Object(r.a)(); console.log(o); }, function(e, t, n) { "use strict"; (t.b = function() { return "hello"; }), (t.a = function() { return "bye"; }); }
一、 pure_funcs配置,在webpack.config.js中增長沒有反作用的函數名
// index.js import {hello, bye} from './util' let result1 = hello() let a = 1 let b = 2 let result2 = Math.floor(a / b) console.log(result1)
plugins: [ new UglifyJSPlugin({ uglifyOptions: { compress: { pure_funcs: ['Math.floor'] } } }) ],
//bundle.js 未使用pure_funcs function(e, t, n) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); var r = n(2); let o = Object(r.a)(); Math.floor(0.5); console.log(o); }, //bundle.js pure_funcs function(e, t, n) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); var r = n(2); let o = Object(r.a)(); console.log(o); },
二、 webpack4 package.json裏面配置sideEffects
{ "name": "your-project", "sideEffects": false } { "name": "your-project", "sideEffects": [ "./src/some-side-effectful-file.js" ] }
過去 webpack 打包時的一個取捨是將 bundle 中各個模塊單獨打包成閉包。這些打包函數使你的 JavaScript 在瀏覽器中處理的更慢。相比之下,一些工具像 Closure Compiler 和 RollupJS 能夠提高(hoist)或者預編譯全部模塊到一個閉包中,提高你的代碼在瀏覽器中的執行速度。