广州做网站的公司哪家好,东昌网站建设公司,软件网页制作,百度推广关键词规划师module federation是什么
webpack5新增了module federation#xff0c;module federation的作用#xff0c;将每个构建(build)作为容器(这是一个概念)#xff0c;构建后的资源可以正常部署#xff0c;同时还具备在运行时对外暴露其中的模块#xff0c;这就意味着多个构建…module federation是什么
webpack5新增了module federationmodule federation的作用将每个构建(build)作为容器(这是一个概念)构建后的资源可以正常部署同时还具备在运行时对外暴露其中的模块这就意味着多个构建可以独立完成独立部署所需的依赖可以在运行时加载。对于多个构建公共的依赖可以通过shared来指定这些依赖也可以在运行时加载并且只加载一次。
事实上公共依赖模块也可以通过npm包的形式来实现共享这种方式的共享不得不依赖于app shell这种容器预先加载共享包。module federation只在第一次加载模块A时加载共享包加载模块B时共享包已被缓存。
模块与容器的概念有点重叠容器实际上是一些模块的集合与构建相关是我们传统意义上的bundle只是在加入module federation能力后容器可以对外导出成员这一点跟模块比较接近而已。
这种能力刚好与微前端架构所需的前端集成能力一致。前端集成技术就是应用A将应用B、C等应用的某些页面、组件、片段等等集成到自己页面里。
加入module federation的构建
传统的构建过程模块之间如果存在依赖关系这些模块会在一个构建过程中打包成一个bundle。
而module federation让这种存在依赖关系的模块各自有各自的构建过程并各自实现自己的bundle和部署最终在运行时异步获取依赖模块。这种方式提高了模块的自主性但可能因为异步的原因降低了首屏渲染性能、运行时的用户交互体验等等。
有对外导出的容器示例
这里展示一个module federation的示例使用。products项目展示一些产品名称其工程目录如下
- products/- public/- index.html // 模板- src/- bootstrap.js // 导入faker生成假数据导出一个mount方法将html内容挂载到某个DOM节点上- index.js // 入口文件导入bootstrap.js模块- package.json - webpack.config.js // 配置module federation的配置文件bootstrap.js的内容如下
// 声明为shared的模块会被拆分为异步模块所以需要异步加载
const faker await import(faker);function mount(el) {let products ;for (let i 1; i 5; i) {products div${faker.commerce.productName()}/div;}el.innerHTML products;
}if (process.env.NODE_ENV development) {// 依赖该模块的容器必须提供一个id属性为dev-products的DOM元素const el document.querySelector(#dev-products);if (el) mount(el);
}export { mount };构建产物
webpack的module federation相关配置示例 devServer: {port: 8081,},// 省略其他传统的配置plugins: [new ModuleFederationPlugin({name: products, // 当前容器的名称其他容器导入该容器时的标识filename: remoteEntry.js, // 当前容器的入口文件与output.filename不是一回事exposes: {// 导出成员对应的模块会被拆离为异步模块./Index: ./src/bootstrap.js, // 指定对外暴露的模块列表标识符: 模块地址},shared: { // 公共的共享模块其他容器也会使用faker这个模块共享模块在构建产物会被作为单独的包存在由容器异步加载faker: {singleton: true,},},}),
],在加入module federation的webpack配置下构建产物发生了一定的变化除了传统的bundle外还会有如下产物
module federation插件产生的容器入口文件如上面配置的remoteEntry.js;对外暴露的模块如上面配置的./Index: ./src/bootstrap.js的产物src_bootstrap_js.js;共享模块如上面配置的faker产物是vendors-node_modules_faker_index_js.js;
而通过模板生成的index.html中不仅有传统的bundle产物main.js也会有remoteEntry.js。对于传统的部署而言remoteEntry.js是没必要的。main.js与remoteEntry.js有很多重复的webpack胶水代码。
容器对外的入口文件remoteEntry.js
查看由module federation生成的容器入口文件可以看到与传统的bundle不一样的地方在于容器入口文件包含一个变量声明var products, 与配置name: products一致。
remoteEntry.js会包含一个moduleMap包含模块标识符’./Index’与src_bootstrap_js.js
remoteEntry.js包含本地容器的初始化init方法和获取导出成员get方法被加载后导出了products变量携带init和get方法。
/***/ // webpack/container/entry/products:
/*!***********************!*\!*** container entry ***!\***********************/
/*** remoteEntry.js中 webpack/container/entry/products 对应的函数内部的eval代码整理如下*/var moduleMap {./Index: () {return __webpack_require__.e(src_bootstrap_js).then(() () __webpack_require__(/*! ./src/bootstrap.js */ ./src/bootstrap.js));},
};
// container的getter将container中的module加载并返回加载后的module
var get (module, getScope) {__webpack_require__.R getScope;getScope __webpack_require__.o(moduleMap, module)? moduleMap[module](): Promise.resolve().then(() {throw new Error(Module module does not exist in container.);});__webpack_require__.R undefined;return getScope;
};
// 初始化容器通过shareScope来提供对外共享的module如果声明了shared每个build都会有shared的module即便有重复
// 如果共享module已经被使用了那么该容器的共享module会被忽略但会作为fallback
var init (shareScope, initScope) {if (!__webpack_require__.S) return;var name default;var oldScope __webpack_require__.S[name];if (oldScope oldScope ! shareScope)throw new Error(Container initialization failed as it has already been initialized with a different share scope);__webpack_require__.S[name] shareScope;return __webpack_require__.I(name, initScope);
};// This exports getters to disallow modifications
__webpack_require__.d(exports, {get: () get,init: () init,
});//# sourceURLwebpack://products/container_entry?;
有导入其他容器的容器示例
通常导入别的容器的容器会作为一个app shell加载其他容器的模块这正是微前端的客户端集成方案。这里的container项目加载products的bootstrap模块使用mount方法挂载HTML内容。目录示例如下
- container/- public/- index.html // 模板- src/- bootstrap.js // 导入products的bootstrap模块使用mount方法将html内容挂载到某个DOM节点上- index.js // 入口文件导入bootstrap.js模块- package.json - webpack.config.js // 配置module federation的配置文件其中bootstrap.js的内容如下
// 这里不使用const { mount: mountProducts } await import(products/Index)的语法
// 是因为模块本身不导入导出任何成员webpack不认为是ESM只有ESM才能使用顶层的await语法
// 在这种情况下使用import ESM语法那么index.js必须使用import(./bootstrap.js)的动态导入语法因为远程模块的导入必须是异步的
import { mount as mountProducts } from products/Index
// 模板index.html中已经有div idprod-products/div
mountProducts(document.querySelector(#prod-products));相关的module federation配置如下 devServer: {port: 8080},plugins: [new ModuleFederationPlugin({name: container, // 容器名称remotes: { // 指定远程模块依赖// 模块别名: 模块别名模块入口地址模块别名是要在代码里导入该模块成员时使用products: productshttp://localhost:8081/remoteEntry.js, // 模块名称: 模块地址}}),container容器应用可以拿到products容器应用的bootstrap模块使用mount方法来挂载HTML内容了。可以尝试一下启动8080端口可以看到页面有一些由products容器应用的bootstrap模块挂载的HTML内容。
构建产物
由于container容器没有对外暴露的模块因此没有remoteEntry.js这样的入口文件也没有共享模块所以container容器的构建产物与传统的构建一致只有bundle。bundle文件中有包含products容器的引用和模块加载代码
/***/ webpack/container/reference/products:
/*!****************************************************************!*\!*** external productshttp://localhost:8081/remoteEntry.js ***!\****************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) {var __webpack_error__ new Error();
module.exports new Promise((resolve, reject) {if(typeof products ! undefined) return resolve();__webpack_require__.l(http://localhost:8081/remoteEntry.js, (event) {if(typeof products ! undefined) return resolve();var errorType event (event.type load ? missing : event.type);var realSrc event event.target event.target.src;__webpack_error__.message Loading script failed.\n( errorType : realSrc );__webpack_error__.name ScriptExternalLoadError;__webpack_error__.type errorType;__webpack_error__.request realSrc;reject(__webpack_error__);}, products);
}).then(() (products));/***/ })模块成员标识符规则
模块对外导出的成员应该如何标识例如products对外导出的./Index能不能写成’Index’或者’Hello’
在导入成员时使用import xxx from products/Indexwebpack会转换为’./Index’作为模块标识符因此products对外导出成员时的标识符不能随意写要按照规则./[name]的形式来书写 webpack内部使用了一系列映射关系来确定导出成员如下面代码所示 var chunkMapping {/******/ // src/bootstrap.js中导入了products/Index和products/Worldsrc_bootstrap_js: [/******/ webpack/container/remote/products/Index, /******/webpack/container/remote/products/World/******/]/******/};/******/var idToExternalAndNameMapping {/******/webpack/container/remote/products/Index: [/******/default, /******/./Index, /******/ 导入成员的标识符webpack/container/reference/products/******/],/******/webpack/container/remote/products/World: [/******/default, /******/./World, /******/ 导入成员的标识符webpack/container/reference/products/******/]/******/};异步模块有异步依赖时使用异步导入还是同步导入
module federation导致的模块拆分如果是异步模块A依赖了异步模块B在A中可以同步导入模块Bimport xxx from module-B因为webpack会使用Promise.all来加载模块A和被A依赖的模块B。所以只要使用动态导入import(‘module-A’)即可不需要在A中使用动态导入B了。当然动态导入B模块也是可以的。
总结
module federation是一种支持当前应用在运行时加载其他运行时应用内部模块的技术在webpack配置时当前应用需要用remote指定要加载的应用名称, 其他应用使用exposes指定对外暴露的内部模块使用shared指定公共的共享模块。应用可以各自独立构建独立部署只在运行时产生耦合加载。各个应用在开发构建时都是独立的降低了开发构建时的耦合性。