参考连接: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)或者预编译全部模块到一个闭包中,提高你的代码在浏览器中的执行速度。