本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-02-26
// main.js
const show = require('./show.js');
show('Webpack');
// =====================================
// show.js
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
module.exports = show;
// =====================================
// webpack.config.js
const path = require('path');
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
}
};
生成的bundle.js
如下:
// webpackBootstrap 启动函数
// modules 即存放所有模块的数组,数组中的每个元素都是一个函数
(function(modules) {
// 作用是将己经加载过的模块缓存在内存中,提升性能
var installedModules = {};
// 加载模块的函数,与Node中的require作用一致
// 因为浏览器环境不支持require语句
// moduleId 为要加载模块在数组中的index
function __webpack_require__(moduleId) {
// 如果需要加载的模块己经被加载过,就直接从缓存中返回
if(installedModules[moduleId]) {
// exports 模块的导出的值
// 当第一次加载的时候会定义
return installedModules[moduleId].exports;
}
// 如果缓存中不存在需要加载的模块,就新建一个模块,并将它存在缓存中
var module = installedModules[moduleId] = {
// 模块在数组中的index
i: moduleId,
// 代表 该模块是否己经加载完毕 标识。第一次加载的模块,默认未完成为false。
l: false,
// 该模块的导出值
exports: {}
};
// 从modules 中获取index 为moduleId 的模块对应的函数
// 再调用这个函数,同时将函数需要的参数传入
/**
* @params module 本次加载的模块
* @params module.exports 本次模块的导出值
* @params __webpack_require__ 加载函数即现在定义的这个函数本身(可能该模块中还需要加载别的模块)
* */
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 将这个模块标记为己加载 返回这个模块的导出值
module.l = true;
return module.exports;
}
// 暴露所有的模块,即传进来的模块数组
__webpack_require__.m = modules;
// 暴露模块缓存输出
__webpack_require__.c = installedModules;
// 为模块定义 getter,决定在不同的环境如何正确导出
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// 如果模块存在且是es6模式的导出形式 则获取的时候 返回 module['default']
// 否则,即对应的导出形式是CommonJS的 则获取的时候 返回 module
// 与非协调模块兼容的getDefaultExport函数
__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 配置中的publicPath ,用于加载被分割出去的异步代码
__webpack_require__.p = "";
// index 为0 的模块就是main.js对应的文件,也就是执行入口模块
// __webpack_require__.s 的含义是启动模块对应的index
return __webpack_require__(__webpack_require__.s = 0);
})
// modules 参数列表 实参
([
/* 模块0 */
(function(module, exports, __webpack_require__) {
// 使用定义的__webpack_require__ 导入模块1,
const show = __webpack_require__(1);
// 执行 show 函数
show('Webpack');
/***/ }),
/* 模块1 */
(function(module, exports) {
// 操作 DOM 元素,把 content 显示到网页上
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// 导出 show 函数
module.exports = show;
/***/ })
]);
简单的来说,整个代码为:
// 立即执行函数
(function(modules){
// 模拟require语句
function __webpack_require__() {}
// 执行存放所有模块数组中的第0个模块 即入口模块
__webpack_require__(0);
}([/*存放所有模块的数组*/]))
bundle.js
能直接运行在浏览器中的原因是,在输出的文件中通过__webpack_require__
函数,定义了一个可以在浏览器中执行的加载函数,来模拟 Node.js
中的require
语句。
// 把第一例中的main.js 改为异步加载 其中的show.js webpack.config.js不变
// 使用import异步加载返回一个promise 其中resolve的参数就是导出值
// main.js
import('./show.js').then((show) => {
show('webpack');
})
打包之后,会输出两个文件,bundle.js
和 异步加载的文件 0.bundle.js
,其中0.bundle.js
的内容如下:
// 加载本文件(0.bundle.js)中包含的模块
// 第一个参数:Array 在其他文件中存放的模块的ID,即别的文件通过什么方式引入0.bundle.js
// 第二个参数:Array 本文件所包含的模块
webpackJsonp([0],[
// show.js对应的模块
(function(module, exports){
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
module.exports = show;
})
])
其中bundle.js
内容如下
(function(modules) {
var parentJsonpFunction = window["webpackJsonp"];
/**
* webpackJsonp 用于从异步加载的文件中安装模块。
* 将webpackJsonp 挂载到全局是为了方便在其他文件中调用。
*
* @param chunkIds 异步加载的文件中存放的需要安装的模块对应的Chunk ID
* @param moreModules 异步加载的文件中存放的需要安装的模块列表
* @param executeModules 在异步加载的文件中存放的需要安装的模块都安装成功后,需要执行的模块对应的index
* */
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
var moduleId;
var chunkId;
var i = 0;
var resolves = []; // 所有已加载的模块的resolve方法
var result;
// 缓存已安装的模块
var installedModules = {};
// 存储每个Chunk 的加载状态:键为Chunk的ID,值为0 代表己经加载成功
var installedChunks = {
1: 0
};
// 将 moreModules 添加到 modules 对象中 -> 本文件所需的模块
// 将所有chunkIds 对应的模块都标记成己经加载成功
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];
}
}
// 如果window.webpackJsonp存在,则直接调用
if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
// 调用所有resolve回调
while(resolves.length) {
resolves.shift()();
}
};
// 模拟require方法
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;
}
/**
* 用于加载被分割出去的需要异步加载的Chunk 对应的文件
* @param chunkId 需要异步加载的Chunk 对应的ID
* @return {Promise}
* */
__webpack_require__.e = function requireEnsure(chunkId) {
// 获取该需要异步加载的模块是否存在于已加载数组中
var installedChunkData = installedChunks[chunkId];
// 为0 说明 已加载成果 直接返回 Promise对象 并直接执行resolve 如果外部定义有then成功回调,则直接被触发
if(installedChunkData === 0) {
return new Promise(function(resolve) { resolve(); });
}
// installedChunkData 不为空且不为0 时,表示该Chunk 正在网络加裁中
if(installedChunkData) {
// 返回存放在installedChunkData 数组中的Promise 对象
// 下方有定义
// installedChunkData[0] -> resolve
// installedChunkData[1] -> reject
// installedChunkData[2] -> [resolve, reject]
// 并且定义了installedChunks
// 上方resolves.push的是installedChunks[chunkId][0] 即resolve
return installedChunkData[2];
}
// installedChunkData 为空,表示该Chunk 还没有加载过,去加载该Chunk 对应的文件
// 先为该文件以Promise存储在状态数组中
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
// 通过DOM操作往HEAD中插入script标签,去异步加载chunk 对应的文件
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);
}
// 文件的路径由配置的publicPath 、chunkid 拼接而成
// 在这里即'./0.bundle.js'
script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
// 设置异步加载的最长超时时间
var timeout = setTimeout(onScriptComplete, 120000);
script.onerror = script.onload = onScriptComplete;
// 在script 加载和执行完成时回调
function onScriptComplete() {
// 防止内存泄漏
script.onerror = script.onload = null;
clearTimeout(timeout);
// 去检查chunkid 对应的Chunk 是否安装成功,安装成功时才会存在于 installedChunks 中
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
// 为0且不为空 时,表示该Chunk 正在网络加裁中,但是加载失败了
if(chunk) {
chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
}
// 为空时,代表未加载,置为undefined
installedChunks[chunkId] = undefined;
}
};
head.appendChild(script);
return promise;
};
// 暴露所有的模块对象 从moreModules复制的modules
__webpack_require__.m = modules;
// 暴露已缓存的模块数组
__webpack_require__.c = installedModules;
// 定义getter函数 为exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// 根据不同情况 返回不同的导出值取值
__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); };
// 配置的publicPath
__webpack_require__.p = "";
// 定义抛出错误函数
__webpack_require__.oe = function(err) { console.error(err); throw err; };
// 加载并执行入口模块,和上面介绍的一致
return __webpack_require__(__webpack_require__.s = 0);
})
([
/* 0 */
// main.js 对应的模块
(function(module, exports, __webpack_require__) {
// 异步加载 show.js对应的chunk
__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then((show) => {
// 执行 show 函数
show('Webpack');
});
})
]);
多了一个webpack.require.e
,用于加载被分割出去的需要异步加载的Chunk
对应的文件。
多了一个webpackJsonp
函数,用于从异步加载的文件中安装模块。
使用CommonsChunkPJugin
提取公共代码时输出的文件和使用异步加载时输出的文件是一样的,都会有webpack.require.e
和webpackJsonp
。原因在于提取公共代码和异步加载在本质上都是代码分割。