webpack打包文件分析
打包后:
(function(modules){// The module cachevar installedModules = {};// The require functionfunction __webpack_require__(moduleId){...}// expose the modules object (__webpack_modules__)__webpack_require__.m = modules;// expose the module cache__webpack_require__.c = installedModules;// Object.prototype.hasOwnProperty.call__webpack_require__.o = function (object, property) {return Object.prototype.hasOwnProperty.call(object, property);};// __webpack_public_path____webpack_require__.p = "";// 其他函数 d r n t// Load entry module and return exportsreturn __webpack_require__(__webpack_require__.s = "./src/index.js"); //入口文件...})({// 模块对象 key值为源文件路径});
webpack_require
// The require functionfunction __webpack_require__(moduleId) {// Check if module is in cacheif (installedModules[moduleId]) {return installedModules[moduleId].exports;}// Create a new module (and put it into the cache)var module = installedModules[moduleId] = {i: moduleId,l: false,exports: {}};// Execute the module functionmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);// Flag the module as loadedmodule.l = true;// Return the exports of the modulereturn module.exports;}
module、module.exports 的作用和 CommonJS 中的 module、module.exports 的作用是一样的
webpack_require 相当于 CommonJS 中的 require。
r 函数
表示此对象是一个ES6模块对象
// define __esModule on exports__webpack_require__.r = function (exports) {// 方便判断 exports 的类型为 Moduleif (typeof Symbol !== 'undefined' && Symbol.toStringTag) {Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });}Object.defineProperty(exports, '__esModule', { value: true });};
webpack_exports 添加一个 __esModule 为 true 的属性,表示这是一个 ES6 module。
d 函数
为 ES6模块定义getter的方法
// define getter function for harmony exports__webpack_require__.d = function (exports, name, getter) {// 判断是否有 name 属性if (!__webpack_require__.o(exports, name)) {Object.defineProperty(exports, name, { enumerable: true, get: getter });}};
n 函数
获取此对象的默认导出
webpack_require.n 分析该 export 对象是否是 ES6 module
- 如果是则返回 module[‘default’] 即 export default 对应的变量。
如果不是 ES6 module 则直接返回 module
// getDefaultExport function for compatibility with non-harmony modules__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;};
t 函数
把任意模块包装成ES6模块
- mode & 1: value is a module id, require it 表示传的是模块ID
- mode & 2: merge all properties of value into the ns 需要合并属性
- mode & 4: return value when already ns object 如果是 ES6 模块直接返回
- mode & 8|1: behave like require 等同于 require 方法 ```javascript // create a fake namespace object // mode & 1: value is a module id, require it // mode & 2: merge all properties of value into the ns // mode & 4: return value when already ns object // mode & 8|1: behave like require webpack_require.t = function (value, mode) { if (mode & 1) value = webpack_require(value); if (mode & 8) return value; if ((mode & 4) && typeof value === ‘object’ && value && value.esModule) return value; var ns = Object.create(null); webpackrequire.r(ns); Object.defineProperty(ns, ‘default’, { enumerable: true, value: value }); if (mode & 2 && typeof value != ‘string’) for (var key in value) webpackrequire.d(ns, key, function (key) { return value[key]; }.bind(null, key)); return ns; };
<a name="ezyl4"></a># harmony import/export> CJS = common.js> ESM = ES6 module<a name="bwECF"></a>## CJS加载CJS```javascript// c.jsexports.name = 'tom';exports.age = '18';// index.jsconst c = require('./c');console.log(c.name);console.log(c.age);
{"./src/c.js":/*! no static exports found */(function (module, exports) {exports.name = 'tom';exports.age = '18';}),"./src/index.js":/*! no static exports found */(function (module, exports, __webpack_require__) {const c = __webpack_require__(/*! ./c */ "./src/c.js");console.log(c.name);console.log(c.age);})}
可以看出,webpack对CJS的模块没有处理,通过实现的require方法加载,webpack是采用的 CJS 的模块方案
CJS加载ESM
// c.jsexport default name = 'tom';export const age = '18';// index.jsconst c = require('./c');console.log(c.name);console.log(c.age);console.log(c.default);
{"./src/c.js":/*! exports provided: default, age */(function (module, __webpack_exports__, __webpack_require__) {"use strict";__webpack_require__.r(__webpack_exports__);/* harmony export (binding) */__webpack_require__.d(__webpack_exports__, "age", function () { return age; });/* harmony default export */__webpack_exports__["default"] = (name = 'tom');const age = '18';}),"./src/index.js":(function (module, exports, __webpack_require__) {let e = __webpack_require__(/*! ./c */ "./src/e.js");console.log(e);console.log(e.default);console.log(e.age);})}
可以看出:
- ESM 的导出对象首先通过
r函数处理,标识为 __esModule - 给 ESM 的导出通过
d函数设置 getter(ESM输出的是引用),如果是 default 就直接赋值(将default赋值到模块上)ESM加载ESM
```javascript // a.js export default name = ‘tom’; export const age = ‘18’;
// index.js import name, { age } from ‘./a’;
console.log(name); console.log(age);
```javascript{"./src/a.js":/*! exports provided: default, age */(function (module, __webpack_exports__, __webpack_require__) {"use strict";__webpack_require__.r(__webpack_exports__);/* harmony export (binding) */__webpack_require__.d(__webpack_exports__, "age", function () { return age; });/* harmony default export */__webpack_exports__["default"] = (name = 'tom');const age = '18';}),"./src/index.js":/*! no exports provided */(function (module, __webpack_exports__, __webpack_require__) {"use strict";__webpack_require__.r(__webpack_exports__);/* harmony import */var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");console.log(_a__WEBPACK_IMPORTED_MODULE_0__["default"]);console.log(_a__WEBPACK_IMPORTED_MODULE_0__["age"]);})}
可以看出:
- 导入ESM使用
default时,webpack 会编译成module["default"]进行访问ESM加载CJS
```javascript // b.js module.exports = { home: ‘beijing’ }; module.exports.name = ‘tom’; module.exports.age = ‘18’;
// index.js import home, { name, age } from ‘./b’; console.log(name); console.log(age); console.log(home);
```javascript"./src/b.js":(function(module, exports) {module.exports = { home: 'beijing' };module.exports.name = 'tom';module.exports.age = '18';}}),"./src/index.js":(function(module, __webpack_exports__, __webpack_require__) {"use strict";__webpack_require__.r(__webpack_exports__);var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/b.js");var _b__WEBPACK_IMPORTED_MODULE_0___default =__webpack_require__.n(_b__WEBPACK_IMPORTED_MODULE_0__);console.log(_b__WEBPACK_IMPORTED_MODULE_0__["name"]);console.log(_b__WEBPACK_IMPORTED_MODULE_0__["age"]);console.log(_b__WEBPACK_IMPORTED_MODULE_0___default.a);})
可以看出:
- ESM 加载 CJS 时,如果用到默认导出,要通过
n函数获取
异步加载
改变index.js
test()import('./b') // 按需加载
则会打包成两个文件
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], {"./src/b.js":(function (module, exports) {module.exports = function test() {console.log('test')}})}]);
(function (modules) { // 启动函数//安装一个为了加载额外代码块的JSON回调函数function webpackJsonpCallback(data) {var chunkIds = data[0];//代码块IDvar moreModules = data[1];//更多的模块//向模块对象上增加更多的模块,然后把所有的chunkIds设置为已经加载并触发回调var moduleId, chunkId, i = 0, resolves = [];for (; i < chunkIds.length; i++) {chunkId = chunkIds[i];if (installedChunks[chunkId]) {resolves.push(installedChunks[chunkId][0]);}installedChunks[chunkId] = 0;//标识这个代码块为已经OK}for (moduleId in moreModules) {//把新拉下来的模块合并到模块对象上if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {modules[moduleId] = moreModules[moduleId];}}if (parentJsonpFunction) parentJsonpFunction(data);//如果有父JSONP函数就调用while (resolves.length) {resolves.shift()();//让所有的promise都OK}};// 模块缓存var installedModules = {};//用来存放加载完成或加载中的代码块对象// undefined = 代码块未加载, null = 代码块正在预加载或者预获取// Promise = 代码块更在加载中, 0 = 代码块已经加载var installedChunks = {"main": 0};//JSON加载的路径function jsonpScriptSrc(chunkId) {return __webpack_require__.p + "" + chunkId + ".bundle.js"}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__.e = function requireEnsure(chunkId) {var promises = [];//JSONP代码块加载var installedChunkData = installedChunks[chunkId];if (installedChunkData !== 0) { // 0的意思是已经安装// a Promise means "currently loading". 如果是一个Promise的话表示正在加载if (installedChunkData) {promises.push(installedChunkData[2]);//如果已经在加载中了,则添加Promise} else {//在代码块缓存中放置Promisevar promise = new Promise(function (resolve, reject) {installedChunkData = installedChunks[chunkId] = [resolve, reject];});promises.push(installedChunkData[2] = promise);// 开始加载代码块var script = document.createElement('script');var onScriptComplete;script.charset = 'utf-8';script.timeout = 120;//// HTMLElement 接口的 nonce 属性返回只使用一次的加密数字,被内容安全政策用来决定这次请求是否被允许处理。if (__webpack_require__.nc) {script.setAttribute("nonce", __webpack_require__.nc);}//设置源文件路径script.src = jsonpScriptSrc(chunkId);//在栈展开之前创建错误以获取有用的堆栈信息var error = new Error();onScriptComplete = function (event) {script.onerror = script.onload = null;clearTimeout(timeout);var chunk = installedChunks[chunkId];if (chunk !== 0) {if (chunk) {var errorType = event && (event.type === 'load' ? 'missing' : event.type);var realSrc = event && event.target && event.target.src;error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';error.name = 'ChunkLoadError';error.type = errorType;error.request = realSrc;chunk[1](error);}installedChunks[chunkId] = undefined;}};var timeout = setTimeout(function () {onScriptComplete({ type: 'timeout', target: script });}, 120000);script.onerror = script.onload = onScriptComplete;document.head.appendChild(script);}}return Promise.all(promises);};__webpack_require__.m = modules;__webpack_require__.c = installedModules;__webpack_require__.d = function (exports, name, getter) {if (!__webpack_require__.o(exports, name)) {Object.defineProperty(exports, name, { enumerable: true, get: getter });}};__webpack_require__.r = function (exports) {if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });}Object.defineProperty(exports, '__esModule', { value: true });};__webpack_require__.t = function (value, mode) {if (mode & 1) value = __webpack_require__(value);if (mode & 8) return value;if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;var ns = Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns, 'default', { enumerable: true, value: value });if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key]; }.bind(null, key));return ns;};__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_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };__webpack_require__.p = "";//异步加载中的错误处理函数__webpack_require__.oe = function (err) { console.error(err); throw err; };//刚开始的时候会把数组赋给window["webpackJsonp"],并且赋给jsonpArrayvar jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];//绑定push函数为oldJsonpFunctionvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);//狸猫换太子,把webpackJsonpCallback赋给了jsonpArray.push方法jsonpArray.push = webpackJsonpCallback;//把数组进行截取得到一个新的数组jsonpArray = jsonpArray.slice();//如果数组不为空,就把全部安装一次for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);//把oldJsonpFunction赋给parentJsonpFunctionvar parentJsonpFunction = oldJsonpFunction;return __webpack_require__(__webpack_require__.s = "./src/index.js");})({"./src/index.js":(function (module, exports, __webpack_require__) {var button = document.createElement("button");button.innerHTML = "点我";button.onclick = function () {__webpack_require__.e("title").then(__webpack_require__.t.bind(null, "./src/title.js", 7)).then(function (result) {console.log(result["default"]);});};document.body.appendChild(button);})});
- 定义了一个对象 installedChunks,作用是缓存动态模块。
- 定义了一个辅助函数 jsonpScriptSrc(),作用是根据模块 ID 生成 URL。
- 定义了两个新的核心函数 webpack_require.e() 和 webpackJsonpCallback()。
- 定义了一个全局变量 window[“webpackJsonp”] = [],它的作用是存储需要动态导入的模块。
重写 window[“webpackJsonp”] 数组的 push() 方法为 webpackJsonpCallback()。也就是说 window[“webpackJsonp”].push() 其实执行的是 webpackJsonpCallback()。
__webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))
e函数
先查看该模块 ID 对应缓存的值是否为 0,0 代表已经加载成功了,第一次取值为 undefined。
- 如果不为 0 并且不是 undefined 代表已经是加载中的状态。然后将这个加载中的 Promise 推入 promises 数组。
- 如果不为 0 并且是 undefined 就新建一个 Promise,用于加载需要动态导入的模块。
- 生成一个 script 标签,URL 使用 jsonpScriptSrc(chunkId) 生成,即需要动态导入模块的 URL。
- 为这个 script 标签设置一个 2 分钟的超时时间,并设置一个 onScriptComplete() 函数,用于处理超时错误。
- 然后添加到页面中 document.head.appendChild(script),开始加载模块。
- 返回 promises 数组。
当 JS 文件下载完成后,会自动执行文件内容。
也就是说下载完 0.js 后,会执行 window[“webpackJsonp”].push()。
由于 window[“webpackJsonp”].push() 已被重置为 webpackJsonpCallback() 函数。所以这一操作就是执行 webpackJsonpCallback()
对这个模块 ID 对应的 Promise 执行 resolve(),同时将缓存对象中的值置为 0,表示已经加载完成了。相比于 webpack_require.e(),这个函数还是挺好理解的。
