webpack打包文件分析
打包后:
(function(modules){
// The module cache
var installedModules = {};
// The require function
function __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 exports
return __webpack_require__(__webpack_require__.s = "./src/index.js"); //入口文件
...
})({
// 模块对象 key值为源文件路径
});
webpack_require
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (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 function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return 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 的类型为 Module
if (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.js
exports.name = 'tom';
exports.age = '18';
// index.js
const 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.js
export default name = 'tom';
export const age = '18';
// index.js
const 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];//代码块ID
var 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 {
//在代码块缓存中放置Promise
var 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"],并且赋给jsonpArray
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
//绑定push函数为oldJsonpFunction
var 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赋给parentJsonpFunction
var 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(),这个函数还是挺好理解的。