module-federation是webpack5更新的一项新特性, 可以更容易的共享前端代码, 本文将介绍@module-federation/webpack-4实现原理及其与webpack5的差异

背景

在公司内我们搭建了微前端包管理平台, 由于有大量webpack4的项目, 我们使用umd规范来共享资源, 也产出了和mf同等作用的插件import-http-webpack-plugin, 但是精力有限我们不打算在umd规范下建立微前端生态, 转而投入到有同样作用的mf, 借助其已有的各领域能力来继续搭建微前端生态。

现阶段mf的优势:

  1. webpack5内置插件
  2. webpack4(webpack4插件)、rollup/vite(vite插件)环境也有生态支持
  3. 非编译环境(usemf)
  4. 各领域相关能力均有提供(ssr、typescript、hmr、dashboard等)

module-federation/webpack-4实现原理

简单的解释下实现原理, webpack4和webpack5是怎么实现互通的呢? 有三个关键点

  • usemf(使用webpack5 build输出的sdk, 用于在非webpack5环境模拟一个webpack5环境来加载module-federation)
  • 遵循module-federation的加载流程(1. init all remote container 2. merge shareScopes 3. 还原webpack5-share的共享规则); 输出module-federation-container
    // container
    { async init(shareScope){}, get(name){ return async factory() }
    }// shareScopes example
    {[default]: {[react]: {[18.0.2]: {get() {return async function factory() {return module}},...other},[17.0.2]: {get() {return async function factory() {return module}},...other}}}
    }
  • 最后是webpack4所欠缺的一项能力, 使jsonp-chunk支持等待依赖(远程模块)加载

通过插件实现上述流程(图示)

  1. 增加一个新入口, 用来实现module-federation的加载流程, 并输出container
  2. 拦截remotes的模块加载, 不再直接加载本地模块, 而是使用远程模块
  3. 拦截shared的模块加载, 不再直接加载本地模块, 而是使用远程模块
  4. shared的请求都被拦截, 但仍需要输出shared bundle, 并将加载函数merge shareScopes

其中介绍图中两处红色部分, 如何改变webpack4加载流程使其支持加载远程模块

  • 拦截import, 预留依赖标记

    1. 设置alias, 将remotes转至一个不存在的url(不存在才可在第二步拦截)
    2. 在compiler.resolverFactory.plugin(“resolver normal”) --> resolver.hooks.resolve.tapAsync钩子将remotes转发至特定loader
    3. 在loader留下字符串标记当前module依赖远程模块, 获取并导出远程模块的值
  • jsonp chunk等待远程依赖加载
    1. 在compilation.mainTemplate.hooks.jsonpScriptchunk钩子使jsonp chunk等待远程模块加载完成后再执行

源码解析

https://github.com/module-federation/webpack-4

// module-federation/webpack-4/lib/plugin.js
apply(compiler) {// 1. 生成唯一的jsonpFunction全局变量防止冲突compiler.options.output.jsonpFunction = `mfrename_webpackJsonp__${this.options.name}`// 2. 生成4个虚拟模块备用this.genVirtualModule(compiler)// 3. 在entry chunks初始化远程模块映射关系// 4. 在entry chunks加载所有的container初始化依赖集合(shareScopes)this.watchEntryRecord(compiler)this.addLoader(compiler)// 5. 生成mf的入口文件(一般是remoteEntry.js)this.addEntry(compiler)this.genRemoteEntry(compiler)// 6. 拦截remotes、shared模块的webpack编译this.convertRemotes(compiler)this.interceptImport(compiler)// 7. 使webpack jsonp chunk等待远程依赖加载this.patchJsonpChunk(compiler)this.systemParse(compiler)
}

1. 生成唯一的jsonpFunction全局变量防止冲突

compiler.options.output.jsonpFunction = `mfrename_webpackJsonp__${this.options.name}`

2.生成4个虚拟模块备用

只是将这4个文件代码模块作为webpack虚拟模块来注册, 可被后续流程引入使用

3.在entry chunks初始化远程模块映射关系

4. 在entry chunks加载所有的container初始化依赖集合

初始化所有container(其他mf模块), 并将加载过程以promise形式导出, 以标识初始化阶段的完成(所有的jsonp chunk需要等待初始化阶段完成)

module-federation/webpack-4/lib/virtualModule/exposes.js

5. 输出mf的入口文件(一般是remoteEntry.js)

  1. 生成入口文件(module-federation/webpack-4/lib/plugin.js)
// 1. 使用singleEntry添加mf入口
new SingleEntryPlugin(compiler.options.context, virtualExposesPath, "remoteEntry").apply(compiler)// 2. 复制remoteEntry入口最后生成的文件, 并重命名
entryChunks.forEach(chunk => {this.eachJsFiles(chunk, (file) => {if (file.indexOf("$_mfplugin_remoteEntry.js") > -1) {compilation.assets[file.replace("$_mfplugin_remoteEntry.js", this.options.filename)] = compilation.assets[file]// delete compilation.assets[file]}})
})
  1. 暴露container api(module-federation/webpack-4/lib/virtualModule/exposes.js)
`/* eslint-disable */...const {setInitShared} = require("${virtualSetSharedPath}")// 此处使用dynamic-import预设了所有exposes moduleconst exposes = {[moduleName]: async () {}}// 1. 在全局以类似global的方式注册containermodule.exports = window["${options.name}"] = {async get(moduleName) {// 2. 使用代码分割来暴露导出的模块const module = await exposes[moduleName]()return function() {return module}},// 此处是某个scope之内的sharedasync init(shared) {// 4. 合并share、等待init阶段完成setInitShared(shared)await window["__mfplugin__${options.name}"].initSharedPromisereturn 1}}`

6. 拦截remotes、shared模块的webpack编译

  1. 将remotes、shared的模块设置别名, 标识特殊路径, 转发到一个不存在的文件路径(只有不存在的文件路径可以被resolver钩子拦截并继续转发)(module-federation/webpack-4/lib/virtualModule/plugin.js)
const { remotes, shared } = this.optionsObject.keys(remotes).forEach(key => {compiler.options.resolve.alias[key] = `wpmjs/$/${key}`compiler.options.resolve.alias[`${key}$`] = `wpmjs/$/${key}`})Object.keys(shared).forEach(key => {// 不存在的文件才能拦截compiler.options.resolve.alias[key] = `wpmjs/$/mfshare:${key}`compiler.options.resolve.alias[`${key}$`] = `wpmjs/$/mfshare:${key}`})
  1. 拦截remotes、shared的别名, 转发到import-wpm-loader.js生成请求远程资源的代码(module-federation/webpack-4/lib/plugin.js)
compiler.resolverFactory.plugin('resolver normal', resolver => {resolver.hooks.resolve.tapAsync(pluginName, (request, resolveContext, cb) => {if (是来自remotes、shared的别名) {// 携带pkgName参数转发至import-wpm-loadercb(null, {path: emptyJs,request: "",query: `?${query.replace('?', "&")}&wpm&type=wpmPkg&mfName=${this.options.name}&pkgName=${encodeURIComponent(pkgName + query)}`,})} else {// 请求本地模块cb()}});
});
  1. 生成请求远程资源的代码(module-federation/webpack-4/lib/import-wpm-loader.js), 2处关键代码
module.exports = function() {`/* eslint-disable */if (window.__wpm__importWpmLoader__garbage) {// 1. 留下代码标识, 标识依赖的远程模块, 用于让chunk等待远程依赖加载window.__wpm__importWpmLoader__garbage = "__wpm__importWpmLoader__wpmPackagesTag${pkgName}__wpm__importWpmLoader__wpmPackagesTag";}// 2. 进入此模块代码时, 远程模块已经加载完毕, 可以使用get获取模块的同步值, 并返回module.exports = window["__mfplugin__${mfName}"].get("${decodeURIComponent(pkgName)}")`
}

7. 使webpack jsonp chunk等待远程依赖加载

  1. 使用正则匹配到jsonp chunk依赖的远程模块, 使chunk等待依赖加载
  2. 使webpack jsonp加载函数支持jsonp等待加载依赖(module-federation/webpack-4/lib/plugin.js)

与webpack5的差异

@module-federation/webpack-4插件已经实现了module-federation的主要能力, 并且可以在webpack4和webpack5互相引用 , 下面说明下哪些参数是插件是未支持的

不支持的参数

options.library

此参数优先级不是很高, 在webpack4种实现较为复杂, 在webpack5中使用也仍有问题, 详见https://github.com/webpack/webpack/issues/16236 , 故在webpack4中的实现类似于设置了library.type = “global”

options.remotes.xxx.shareScope

同一个mf container只可以用一个shareScope初始化, 如果被多次使用shareScope设置的不一致webpack会报错, 并且shareScope可设置处过多比较混乱, 即使在纯webpack5中使用表现也不可预估, 建议使用options.shared.xxx.shareScope、options.shareScope替代

module-federation生态包

webpack-4插件暂未集成webpack-5相关包的能力(ssr、typescript、hmr、dashboard等), 但已实现4、5互通, 可以助您可以放心的使用webpack5实现新项目, 而无需重构已有项目

已支持的参数

  • options.remotes
  • options.name
  • options.shareScope
  • options.shared
  • options.exposes

webpack4 module federation相关推荐

  1. 探索 webpack5 新特性 Module federation 引发的javascript共享模块变革

    webpack5 新特性 Module federation 引言 什么是Module federation "模块联邦" Module federation "模块联邦 ...

  2. 在 vue/cli 中使用 Module Federation

    webpack5 的新特性,分模块共同开发.多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们.这通常被称作微前端,但并不仅限于此. 我们分为本地模块 ...

  3. Webpack 新功能 Module Federation 深入解析

    导读:Federated Modules 是一个令人激动的功能,它可能会改变未来几年的前端打包方式,作者深入分析了 Module Federation 的原理及其应用场景,希望能对大家有所启发. WH ...

  4. Webpack5的Module Federation是什么来头?

    背景 目前很多公司的业务都涉及到多个端的开发,有PC端/小程序/原生客户端等,而不同端都有对应的一个或几个独立的项目,而这些不同的项目之间都有一些可复用的业务逻辑,开发者往往需要在不同的项目中维护相同 ...

  5. Module Federation在客服工单业务中的最佳实践

    Module Federation:是模块联邦的意思,在webpack 5中流行起来的,也属于一种微前端方案. 一.背景 1.客服高频工作场景 一线客服: 基于一站式工作台中的在线工作台及电话工作台, ...

  6. 基于Module Federation的模块化跨栈方案探索

    公司发展到一定程度,随着业务分支不断变多,B端C端的项目也随之增多,由于历史原因可能产生新老技术栈(vue/react)共存的情况,这既不利于组件物料的抽离统一(一类通用组件需适配多套技术栈),也增大 ...

  7. Webpack5 Federation

    准备阅读 本文对前端基础有一定要求,推荐先熟读以下文章 探索 webpack5 新特性 Module federation 在腾讯文档的应用 webpack5 模块联合官文 一.Usage Examp ...

  8. babel原理_带你了解 snowpack 原理,你还学得动么(下)

    作者:AlienZHOU 转发链接:https://zhuanlan.zhihu.com/p/149351900 目录 带你了解 snowpack 原理,你还学得动么(上) 带你了解 snowpack ...

  9. webpack 修改title_Webpack漫谈

    本文使用「署名 4.0 国际 (CC BY 4.0)」 许可协议,欢迎转载.或重新修改使用,但需要注明来源. 作者: 百应前端团队 @双鱼 https://juejin.im/user/3075189 ...

最新文章

  1. 公众号微信支付ios和android,【微信支付】
  2. isis学不到looback口的路由_使用路由器后测速达不到宽带的网速怎么办?
  3. jinja2语法中{%raw%}和{{}}的等效替换
  4. oracle+标记要,oracle ORA-00031:session marked for kill(标记要终止的会话)解决方法
  5. C语言 while 循环 - C语言零基础入门教程
  6. 单因素方差分析[转载]
  7. libsvm3.22——使用指南
  8. java方法_Java方法
  9. 仿微信朋友圈图片按下效果
  10. simulink中if模块_Simulink与圈复杂度
  11. 越南山寨QQ,我今天才知道,太山寨了!
  12. 阿里云携手蓝凌软件,打造全球化企业智慧办公平台
  13. SAS入门之(四)改变数据类型
  14. 毕业生就业管理系统 C++
  15. 给自己做一块开发板 #AnyBoard
  16. javaweb 温习
  17. ant design 动态给a-input设置默认值
  18. Lodop设置指定打印机打印,避免默认打印机被修改
  19. android 语音和输入法按钮切换,android 切换系统语言,输入法也随之切换
  20. 概率分布,独立同分布在图像分类与检测中到底代表什么?

热门文章

  1. JavaScript-数组(new Array)
  2. SolidWorks弯曲的波纹管制作教程
  3. 硬件基础-锁相放大器详解
  4. Leaf中的Marker cluster
  5. 中国邮路问题的解决(数据结构课程设计)
  6. tddl+diamond(二)
  7. 密码学实验6 维吉尼亚密码
  8. openface安装
  9. Android记事本 (附apk和源码)
  10. JavaEE----JPA中配置文件persistence.xml