jingzhiMo.github.io
jingzhiMo.github.io copied to clipboard
浅析 webpack 5 module federation 加载构成
前言
这篇文章主要是调研 module federation
的时候. 对 webpack 异步加载代码分割文件与加载远端组件的流程简述.前半部分是流程与部分代码分析, 后半部分是webpack代码的注释笔记.
介绍
Webpack 5 新增一个 module federation
的特性, 详情可以看 官方文档. 这个特性大概的作用是:
多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。
这通常被称作微前端,但并不仅限于此。
例如在 A 应用上运行的 组件 FOO
, B 应用也会有类似的需求, 需要复用组件 FOO
, 通常我们可能会把 FOO
组件抽离到一个 npm 包, 然后发布到 npm 平台, 那么在 A, B 两个应用都进行引入对应的 npm 包, 后续的维护,会独立在 npm 包中进行发布. 如果 npm 包发生更新, 那么对应 A, B 两个应用都进行重新打包. 这一种维护组件的模式,会有点在“编译时”的味道.
而 module federation
的插件主要针对应用“运行时”. 就如上面的例子, A 应用可以通过 webpack 配置 ModuleFederationPlugin
的插件, 在构建A应用的时候, 把 FOO
组件顺便打包为一个远端组件包,通常为 remoteEntry.js
. B 应用想使用 FOO
组件, 则通过在入口 html 文件中, 引入 A 应用地址下的remoteEntry.js
文件(这一步通常也由ModuleFederationPlugin
完成 ), 并通过 import
关键词引入即可. 后续FOO
组件的发布与维护,都在 A 应用进行处理. A 发布新的包, B应用可以不重新构建代码,就可以引用最新的代码. 简单如下图所示:
但是要有一个前提条件,A, B两个应用都是使用 webpack 5 来打包, 否则无法识别,
调研例子
看到这个远端加载组件的方法还挺有趣,于是乎想看一下具体的实现.后面会贴一些源代码的实现.后续的所有例子,都是基于 module-federation-examples/basic-host-remote
例子来调研.
分析
我们先了解一下 webpack 打包出来的产物的两个概念:
-
chunk
-
module
chunk 是文件级别, 利用 Code Splitting
分割的代码,每个文件都是一个 chunk, 通常一个 chunk 中包含一个或者多个 module. 而实际webpack代码运行的时候, 是根据 module id 进行定位.
文件源码结构分析
这里对三种文件类型文件源码结构进行分析:
- webpack 打包出来的主文件
- Code Splitting 分割的 chunk 文件
- remoteEntry 文件 (由 ModuleFederationPlugin 插件生成的文件)
会先从普通 Code Splitting 的文件加载说起, 然后对 remoteEntry 类文件进行分析.
主文件的结构如下:
(() => {
// 定义变量
var __webpack_modules__ = {}
var __webpack_module_cache__ = {}
function __webpack_require__() {}
// 往 __webpack_require__ 对象挂载各种数据、函数等
__webpack_require__.m = __webpack_modules__
__webpack_require__.n = function() {}
// ...
// 定义 jsonp 的回调函数,后面会说到:
function webpackJsonpCallback() {}
// 对指定对象数组的 push 方法进行劫持, 后面会说到
var chunkLoadingGlobal = self["webpackChunk_basic_host_remote_app1"] = self["webpackChunk_basic_host_remote_app1"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
// 加载并执行对应模块
Promise.all([
__webpack_require__.e(558),
__webpack_require__.e(165)
]).then(__webpack_require__.bind(__webpack_require__, 165))
})()
从主文件结构开始分析, 在 Promise.all
函数执行之前, 都是一些变量与函数的定义, 正式启动是调用 __webpack_require__.e
接着是 __webpack_require__
函数的执行.
__webpack_require__.e
这种挂载的函数或者变量很多,前缀很长...后面会用__.e
来代替
__.e
函数,是一个统一异步加载 chunk 的入口,
__webpack_require__
是一个对 module 进行引入的工具函数, 如果第一次执行,还会对该 module 进行执行, 返回内容挂载在 exports
中.
所以主文件的处理方式就是: 加载 chunk id 为558, 165 的 chunk 文件, 然后执行 module id 为 165 的 module.
那么问题来了, moduleId: 165 是位于 异步加载的 chunkId: 165 中, 怎样可以让局部变量 __webpack_require__
来加载呢?
Code Splitting文件结构如下:
(self["webpackChunk_basic_host_remote_app1"] = self["webpackChunk_basic_host_remote_app1"] || []).push(
[165], // 这是这个文件对应的 chunkId
// 以下这两个是 moduleId, 对应的模块内容的函数
{
165: () => {},
408: () => {}
}
)
被代码分割出来的文件比较简单, 直接往 webpackChunk_basic_host_remote_app1
的全局变量数组push
两个变量[165, { 165: '', 408: 'xx'}]
, 该类型文件并没有一些调用的函数.
这里的关键点, 在于主文件对变量 webpackChunk_basic_host_remote_app1
的 push 函数进行了劫持:
var chunkLoadingGlobal = self["webpackChunk_basic_host_remote_app1"] = self["webpackChunk_basic_host_remote_app1"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
当该全局变量数组执行 push
函数的之后, 并不是执行正常数组把数据添加到数组, 而是执行 webpackJsonpCallback
函数, 这个函数的作用是负责把对应 chunk 标识为已加载成功, 并把加载到的 chunk 中的 module, 逐个注册到 __.m
变量中, 这样子后续主文件的代码,就能够拿到异步加载的 module. 简要如下图:
remoteEntry 文件结构如下:
// 定义全局变量, 这个全局变量是在 ModuleFederationPlugin 插件中定义
var app2; app2 = (() => {
// 这个函数的内容,与主文件类似, 定义一部分变量
var __webpack_modules__ = {
677: () => {
// ... 还有其他代码
__webpack_require__.d(exports, {
// get 方法是用于, 根据组件名称获取相关组件内容
get: () => get,
// init 方法是用于初始化当前分离打包组件需要依赖包的版本这部分
init: () => init
});
}
}
var __webpack_module_cache__ = {}
function __webpack_require__() {}
// 后续就不详细展开,也有定义 jsonp 的回调函数等
function webpackJsonpCallback() {}
// 与主文件不一致的地方, 给全局变量返回对应的数据
return __webpack_require__(677);
})()
由 module federation
插件打包出来的 remoteEntry 的文件结构,与普通代码分割的 chunk 文件不一致, 所以不能用类似的方法进行加载, remoteEntry 类文件加载会相对复杂一点点.
这里需要看一下 __.e
函数的作用, 之前说过, __.e
函数是用于加载异步的 chunk, 而实际上, 加载异步的 chunk 会有几种类型, 这几种类型分别都有独自的处理方法, 分别是:
-
__.f.j
处理普通代码分割的 chunk -
__.f.remotes
处理需要从远端获取的组件 -
__.f.consumes
处理加载 remoteEntry 相关 chunk
__.e
只是一个壳, 每次执行,都会依次把这三个函数执行一遍. 这三种处理方法,通常如何辨别, 一个 chunk id 过来,是否符合当前处理的类型?
假设一个 chunkId: 165, 的文件需要加载, 这种文件只是一种普通的代码分割的 chunk, __.f.remotes & __.f.consumes
这两个函数不需要实际上发起请求. 针对这种情况, 处理函数通常会维护一份 chunkMapping
, 通过判断 chunkId 在 chunkMapping 来确认继续, 例如
// __.f.consumes
const chunkMapping = {
160: xx
}
对于__.f.consumes
函数来说,只有 chunkId: 160, 是有效的, 对于其他无效 chunkId, 会跳过实际处理环节.
说完 __.f
相关函数的加载简要描述, 接下来说 remoteEntry 的加载过程:
-
__.f.consumes
执行, 对module federation
插件配置的share
相关包进行版本注册(通常是一些公共基础包, 例如 react, react-dom 等). 配置share
, 可以减少重复加载基础包 - 加载对应的 remoteEntry.js 文件, 根据插件配置的全局变量, 获取到 remoteEntry 暴露的数据
- 调用 remoteEntry 暴露的
init
方法(上述: remoteEntry 文件结构中的 init 函数), 把主应用的公共基础包与 remoteEntry 基础包的版本进行对比, 根据x.y.z
版本号的方式, 看双方版本是否适配. 如果适配, 加载同一份公共基础包, 否则, 各自加载. - 当应用中, 有需要用到 remoteEntry 中的组件, 则会调用这一步,
__.f.remotes
-
__.f.remotes
加载对应远端组件, 调用 remoteEntry 暴露的get
方法(上述: remoteEntry 文件结构中的 get 函数), 根据组件名称,获取到对应的组件, 挂载到__webpack_modules__
变量下, 后续会被__webpack_require__
方法所使用
下图对版本处理,做了简化,只显示加载与调用的过程
remoteEntry 类的文件, 也需要使用全局变量做为其中一个中介来传递数据, 与webpackJsonpCallback
有一部分相同之处, 但是 remoteEntry 多了版本的判断, 这一部分其实非常复杂,上面只是简要对过程进行了分析, 远端版本控制没有做深入的讲解.
小结
webpack实现异步加载的方法都很巧妙,无论是利用劫持全局变量方法,还是通过“伪”全局变量来做数据中转.函数设计分工精细. 例如在 __.e
与 __.f.j & __.f.remotes & __f.consumes
之间的联动, 还是底层工具方法的定义, 对日常开发思路都有比较不错的参考.后续的部分,是在调研过程中,对部分代码的分析做的一部分笔记, 也做为简要的 api 文档来查阅. 需要可以往后查看. (完)
部分代码实现
__webpack_require__ 代码实现
function __webpack_require__(moduleId) {
// 判断该模块是否已经缓存,已缓存直接返回该模块
if(__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
// 没有缓存,根据 moduleId 创建一个缓存模块
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
};
/******/
// Execute the module function
// 执行目标模块
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// 执行完毕之后,module 这个对象就被挂上目标模块了,
// 因为module对象内存地址是同一个,在执行模块的时候,已被赋值
return module.exports;
}
__webpack_require__.o
// hasOwnProperty 的简写
__webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
__webpack_require__.m
// 获取 __webpack_modules__ 对象
__webpack_require__.m = __webpack_modules__
__webpack_require__.d
// 对 webpack 的模块进行数据劫持,类似 vue 的数据劫持
// 但是直接获取模块的值的时候进行劫持,不会对 set 进行赋值
// 能够保证模块暴露(module.exports)的值,不会被外部模块重写
__webpack_require__.d = (exports, definition) => {
for(var key in definition) {
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
}
}
};
__webpack_require__.g
// 返回当前的全局变量,例如 nodeJs 中的 globalThis、浏览器中的 window 变量
__webpack_require__.g = (function() {
if (typeof globalThis === 'object') return globalThis;
try {
return this || new Function('return this')();
} catch (e) {
if (typeof window === 'object') return window;
}
})();
__webpack_require__.n
// 兼容获取 只进行 export default 的es模块打包,提取 export default 的模快
// 通常是针对引入的模块的获取进行劫持
// 但是这里 对 getter 的 a 变量的 get 劫持,不是十分了解
__webpack_require__.n = (module) => {
var getter = module && module.__esModule ?
() => module['default'] :
() => module;
__webpack_require__.d(getter, { a: getter });
return getter;
};
__webpack_require__.f
// 这个对象下,挂载需要拆分打包(import() 或 require.ensure)的模块函数, 例如:
// f.j, 入口文件 entry 的依赖
// f.consumes, f.remotes module federation的依赖
__webpack_require__.f = {};
__webpack_require__.e
// 对 entry 入口文件中依赖的 chunk, 按顺序,进行加载并执行
__webpack_require__.e = (chunkId) => {
return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
__webpack_require__.f[key](chunkId, promises);
return promises;
}, []));
};
__webpack_require__.u
// 拼接 chunk 的文件名称(根据 webpack 配置的 basename?)
__webpack_require__.u = (chunkId) => {
// return url for filenames based on template
return "" + chunkId + ".js";
};
__webpack_require__.l
// 用于通过 scripts 标签加载 js 文件
// 限制加载 js 文件超时时间为 120s
// 加载 js 文件完毕之后,会删除 scripts 标签
(() => {
var inProgress = {};
// loadScript function to load a script via script tag
/**
* @params url 请求 js 文件的 url
* @params done 请求完毕之后的回调函数
* @params key 带有 chunk 的 id 的字符串,例如 chunk-1
*/
__webpack_require__.l = (url, done, key) => {
if(inProgress[url]) { inProgress[url].push(done); return; }
var script, needAttach;
if(key !== undefined) {
// 通过以下循环,判断当前该 js 文件是否已经加载
// 若已加载,则不会通过创建 scripts 标签加载 js 文件
var scripts = document.getElementsByTagName("script");
for(var i = 0; i < scripts.length; i++) {
var s = scripts[i];
if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == key) { script = s; break; }
}
}
if(!script) {
needAttach = true;
script = document.createElement('script');
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.setAttribute("data-webpack", key);
script.src = url;
}
inProgress[url] = [done];
var onScriptComplete = (event) => {
onScriptComplete = () => {
}
// 避免在 IE 中内存泄漏
script.onerror = script.onload = null;
clearTimeout(timeout);
var doneFns = inProgress[url];
delete inProgress[url];
script.parentNode.removeChild(script);
doneFns && doneFns.forEach((fn) => fn(event));
}
;
var timeout = setTimeout(() => {
onScriptComplete({ type: 'timeout', target: script })
}, 120000);
script.onerror = script.onload = onScriptComplete;
needAttach && document.head.appendChild(script);
};
})();
__webpack_require__.r
// 通过 Object.defineProperty 来劫持 es 模块的 exports 对象
// 使得 es 模块的 __esModule 字段返回是 true 或
// es 模块的 Symbol.toStringTag 字段,返回固定值 "Module"
__webpack_require__.r = (exports) => {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
__webpack_require__.p
// 固定返回 webpack 设置的 publicPath
__webpack_require__.p = "http://localhost:3001/";
__webpack_require__.f.j
// 加载 chunk 文件
// 用于处理所有 chunk 文件的状态
// key 为 chunkId, value 是该 chunk 文件的状态,分别有
// * undefined 不会进行加载
// * null 为该 chunk 是 preloaded/prefetched 的类型
// * Promise 为该 chunk 还在加载当中
// * 0 为该 chunk 已经加载完毕
var installedChunks = {
179: 0
};
__webpack_require__.f.j = (chunkId, promises) => {
// JSONP chunk loading for javascript
var installedChunkData = __webpack_require__.o(installedChunks, chunkId)
? installedChunks[chunkId]
: undefined;
// 判断是否已经安装过,若已安装,则直接返回
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
if(true) { // all chunks have JS
// setup Promise in chunk cache
var promise = new Promise((resolve, reject) => {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
// 拼接需要请求的 js 文件链接
var url = __webpack_require__.p + __webpack_require__.u(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
var loadingEnded = (event) => {
// 加载 js 文件完毕之后的回调函数
// 执行的时机,可以看 __webpack_require__.l 的函数
if(__webpack_require__.o(installedChunks, chunkId)) {
installedChunkData = installedChunks[chunkId];
// 重点关注:这个时候,如果正常加载完毕的话,installedChunkData[chunkId] = 0
if(installedChunkData !== 0) installedChunks[chunkId] = undefined;
if(installedChunkData) {
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;
installedChunkData[1](error);
}
}
};
__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId);
} else installedChunks[chunkId] = 0;
}
}
};
webpackJsonpCallback
在说,webpackJsonpCallback
函数之前,先讲一下之前的函数列表。
main.js
文件底部,会直接执行__webpack_require__.e
函数用于启动获取主函数。
入口文件先启动的函数顺序是
__webpack_require__.e
=> __webpack_require__.f.j
=> __webpack_require__.l
来加载 js 文件。__webpack_require__.f.j
函数里面有一个 loadingEnded
的回调函数,这个函数是在 js 文件加载完之后,onload
触发的。但是,判断加载的 chunk 文件是否成功,是根据 installedChunkData
这个变量来确定的。只有 installedChunkData
的值为 0 的时候,才算成功。从函数调用顺序来看,没有看到什么时候对 installedChunkData
的值进行赋值,而这个赋值,就是在 webpackJsonpCallback
来进行处理的。webpackJsonpCallback
代码如下:
webpack 在全局中定义变量webpackJsonpmodule_federation_starter
(webapck 5以下是:webpackJsonp 变量),该变量是一个数组,劫持了该数组的 push
方法,当有新的元素 push 到该数组,就先调用 webpackJsonpCallback
方法。
在 webpackJsonpCallback
方法中,主要做两件事:
- 把成功加载的 chunk 的标识置为:0,在
__webpack_require__.f.j
中能够识别已加载成功 - 把成功加载的 chunk 中,含有的所有 module 添加到
__webpack_require__.m
(__webpack_modules__
)中,其他 module 依赖就可以直接获取
// install a JSONP callback for chunk loading
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
var runtime = data[3];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [];
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
// 关键点
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(__webpack_require__.o(moreModules, moduleId)) {
// 关键点
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if(runtime) runtime(__webpack_require__);
if(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) {
resolves.shift()();
}
};
// 关键点
var jsonpArray = window["webpackJsonpmodule_federation_starter"] = window["webpackJsonpmodule_federation_starter"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
var parentJsonpFunction = oldJsonpFunction;
__webpack_require__.f.remotes
// 需要被处理的 chunkId
var chunkMapping = {
"164": [
164
]
};
var idToExternalAndNameMapping = {
"164": [
"default",
"./Button",
980
]
};
__webpack_require__.f.remotes = (chunkId, promises) => {
if(__webpack_require__.o(chunkMapping, chunkId)) {
chunkMapping[chunkId].forEach((id) => {
var getScope = __webpack_require__.R;
if(!getScope) getScope = [];
var data = idToExternalAndNameMapping[id];
if(getScope.indexOf(data) >= 0) return;
getScope.push(data);
if(data.p) return promises.push(data.p);
var onError = (error) => {
if(!error) error = new Error("Container missing");
if(typeof error.message === "string")
error.message += '\nwhile loading "' + data[1] + '" from ' + data[2];
__webpack_modules__[id] = () => {
throw error;
}
data.p = 0;
};
// 处理回调的工厂方法
var handleFunction = (fn, arg1, arg2, d, next, first) => {
try {
var promise = fn(arg1, arg2);
if(promise && promise.then) {
var p = promise.then((result) =>
(next(result, d)),
onError
);
if(first) promises.push(data.p = p); else return p;
} else {
return next(promise, d, first);
}
} catch(error) {
onError(error);
}
}
// 判断是否已经请求远端 remoteEntry 文件, 若未请求, __webpack_require__.I 会发出请求
var onExternal = (external, _, first) => (external
? handleFunction(
__webpack_require__.I,
data[0],
0,
external,
onInitialized,
first
)
: onError()
);
// 调用 remoteEntry 中暴露的 get 焊方法
var onInitialized = (_, external, first) => (
handleFunction(
external.get,
data[1],
getScope,
0,
onFactory,
first
)
);
// 往 __webpack_modules__ 中挂载 moduleId, 后续给到 __webpack_require__ 所调用
var onFactory = (factory) => {
// chunk 加载中
data.p = 1;
__webpack_modules__[id] = (module) => {
module.exports = factory();
}
};
// 执行顺序 __webpack_require__ => remoteEntry.get => __webpack_module__.m => __webpack_require__
handleFunction(__webpack_require__, data[2], 0, 0, onExternal, 1);
});
}
}
})();
__webpack_require__.f.consumes
在使用 ModuleFederationPlugin
的时候,配置 shared
依赖包的加载处理,例如配置为:
new ModuleFederationPlugin({
// ... 其他的配置
// 共享模块的配置 f.consumes 函数就是处理这些依赖
shared: ["react", "react-dom"],
}),
// 定义 chunkId 需要依赖的 chunk 的关系
// 例如 801 这个 chunk 是需要把 module id 为 250, 138 的依赖进行加载
// 该关系,在 webpack 打包的时候,自动生成
var chunkMapping = {
"591": [
591
],
"801": [
250,
138
]
};
// 定义share 依赖包进行加载的方法,与对应的版本
// 版本用来在其他 webpack 应用共享的时候,进行是否复用判断
var moduleToHandlerMapping = {
250: () => loadStrictVersionCheckFallback("default", "react-dom", ["16",13,0], () => Promise.all([__webpack_require__.e(338), __webpack_require__.e(591)]).then(() => () => __webpack_require__(338))),
138: () => loadStrictVersionCheckFallback("default", "react", ["16",13,0], () => __webpack_require__.e(162).then(() => () => __webpack_require__(162))),
591: () => loadStrictVersionCheckFallback("default", "react", ["16",14,0], () => __webpack_require__.e(764).then(() => () => __webpack_require__(162)))
};
__webpack_require__.f.consumes = (chunkId, promises) => {
// 需要判断 chunkId 是否在配置中
if(__webpack_require__.o(chunkMapping, chunkId)) {
chunkMapping[chunkId].forEach((id) => {
if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]);
// chunk 加载成功,加入到对应 __webpack_module__ 中,模块后续不需要重新加载
var onFactory = (factory) => {
installedModules[id] = 0;
__webpack_modules__[id] = (module) => {
delete __webpack_module_cache__[id];
module.exports = factory();
}
};
var onError = (error) => {
delete installedModules[id];
__webpack_modules__[id] = (module) => {
delete __webpack_module_cache__[id];
throw error;
}
};
try {
// 调用方法,进行加载对应的 chunk
var promise = moduleToHandlerMapping[id]();
if(promise.then) {
promises.push(installedModules[id] = promise.then(onFactory).catch(onError));
} else onFactory(promise);
} catch(e) { onError(e); }
});
}
}
__webpack_require__.S
// 定义分享模块的 scope , 例如 default, default 里面会挂载依赖包的版本
__webpack_require__.S = {} // 初始化为空对象
// __webpack_require__.S = {
// default: {
// react: xxx,
// react-dom: xxx
// }
// }
__webpack_require__.I
// name 就是 scope 的 name
// __webpack_require__.I 函数就是为 scope 注册对应的依赖版本
// 注册完,挂载到 __webpack_require__.S 中
__webpack_require__.I = (name) => {
// only runs once
if(initPromises[name]) return initPromises[name];
// handling circular init calls
initPromises[name] = 1;
// creates a new share scope if needed
if(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {};
// runs all init snippets from all modules reachable
var scope = __webpack_require__.S[name];
var warn = (msg) => typeof console !== "undefined" && console.warn && console.warn(msg);;
// 为当前的包的所有版本都注册到 default 这个 scope 中
// 并对版本进行判断
// 通常版本号是 x.y.z 的,webpack 会把三个版本都进行注册,例如 react 16.14.0
// 分别注册为
// react`16, react`16`4, react`16`4`0
// 这三个版本都挂载到 scope 下,也就是
// __webpack_require__.S['default'] = {
// react`16: { get: , factory: },
// react`16`14: { get: , factory: },
// react`16`14`0: { get: , factory: },
// }
var register = (name, version, factory, currentName) => {
// ...
};
var initExternal = (id) => {}
var promises = [];
switch(name) {
case "default": {
register(
"react",
[16,14,0],
// 定义获取当前库的方法, webpack 常用手段
// 先通过 .e 函数来加载,然后通过 __webpack_require__ 来包装对象
() => __webpack_require__.e(162).then(() => () => __webpack_require__(162))
);
register(
"react-dom",
[16,14,0],
() => Promise.all([
__webpack_require__.e(338),
__webpack_require__.e(591)
]).then(() => () => __webpack_require__(338))
);
}
break;
}
return promises.length && (initPromises[name] = Promise.all(promises).then(() => initPromises[name] = 1));
};