百度工程师带你了解Module Federation

码影随行
• 阅读 792

百度工程师带你了解Module Federation

作者 | 一贫

导读

本文介绍了Module Federation的概念、应用场景,并结合具体的代码示例帮助大家对Module Federation的模块共享,公共依赖加载有个初步的认识,方便后续更深入的学习相关内容,同时也给微前端的探索提供一种新的思路,定会给大家一定的提升和启发。

全文5405字,预计阅读时间14分钟。

01 什么是Module Federation(MF)?

普遍直译为『模块联邦』,我们看看官网是怎么说的?

Motivation

Multiple separate builds should form a single application. These separate builds should not have dependencies between each other, so they can be developed and deployed individually. This is often known as Micro-Frontends, but is not limited to that.

多个独立的构建可以形成一个应用程序。这些独立的构建不会相互依赖,因此可以单独开发和部署它们。

这通常被称为微前端,但并不仅限于此。

通俗点讲,即MF提供了能在当前应用中远程加载其他服务器上应用的能力。对此,可以引出下面两个概念:

  • host:引用了其他应用的应用
  • remote:被其他应用所使用的应用

百度工程师带你了解Module Federation

△图片来源于网络

它与我们普遍讨论的基座应用、微应用有所不同,它是去中心化的,相互之间是平等的,每个应用是单独部署在各自的服务器,每个应用都可以引用其他应用,也能被其他应用所引用,即每个应用可以充当host的角色,亦可以作为remote出现。

百度工程师带你了解Module Federation

△图片来源于网络

02 应用场景

  • 微前端:通过shared以及exposes可以将多个应用引入同一应用中进行管理。
  • 资源复用,减少编译体积:可以将多个应用都用到的通用组件单独部署,通过MF的功能在runtime时引入到其他项目中,这样组件代码就不会编译到项目中,同时亦能满足多个项目同时使用的需求,一举两得。

03 如何使用

项目结构如下:

  • module-home:首页,在layout展示一个字符串。
  • module-layout:布局,只包含一个html模板。
  • module-lib:暴露工具方法,共享lodash库。

百度工程师带你了解Module Federation

3.1 相关配置参数一览

百度工程师带你了解Module Federation

3.2 各应用的配置

// apps/module-lib/webpack.config.js
plugins: [
    new ModuleFederationPlugin({
        name: 'lib',
        filename: 'remoteLib.js',
        library: { type: 'var', name: 'lib' },
        exposes: {
            // 提供工具方法
            './utils': './index.js',
        },
        shared: ["lodash"]
    })
]

// apps/module-home/webpack.config.js
plugins: [
    new ModuleFederationPlugin({
        name: 'home',
        filename: 'remoteHome.js',
        library: { type: 'var', name: 'home' },
        exposes: {
            // 提供挂载方法
            './mount': './index.js',
        },
        shared: ["lodash"]
    })
]

// apps/module-layout/webpack.config.js
plugins: [
    new ModuleFederationPlugin({
        name: 'main',
        filename: 'remoteMain.js',
        remotes: {
            'lib': 'lib@http://localhost:3001/remoteLib.js',
            'home': 'home@http://localhost:3003/remoteHome.js',
        },
        shared: ['lodash']
    }),
    new HtmlWebpackPlugin({
        template: path.resolve(__dirname, './public/index.html'),
        inject: 'body'
    })
]

// apps/module-layout/boot.js
import {getUid, setUid} from 'lib/utils' // 使用module-lib中暴露的方法
import {mount} from 'home/mount' // 使用module-home中暴露的挂载方法
import _ from 'lodash';
setUid();
setUid();
console.log(getUid())
console.log(_.get)

mount()

如下图所示:在layout中展示了home挂载的节点,控制台也打印了调用lib中方法的log,同时lib分享的lodash也生效了(全程只加载了一个lodash)。

百度工程师带你了解Module Federation

百度工程师带你了解Module Federation

3.3 以remoteLib为例简要分析

// 定义全局变量
var lib;
/******/ (() => { // webpackBootstrap
/******/   "use strict";
/******/   var __webpack_modules__ = ({

/***/ "webpack/container/entry/lib":
/*!***********************!*\
  !*** container entry ***!
  \***********************/
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {

eval("var moduleMap = {\n\t\"./utils\": () => {\n\t\treturn __webpack_require__.e(\"index_js\").then(() => (() => ((__webpack_require__(/*! ./index.js */ \"./index.js\")))));\n\t}\n};\nvar get = (module, getScope) => {\n\t__webpack_require__.R = getScope;\n\tgetScope = (\n\t\t__webpack_require__.o(moduleMap, module)\n\t\t\t? moduleMap[module]()\n\t\t\t: Promise.resolve().then(() => {\n\t\t\t\tthrow new Error('Module \"' + module + '\" does not exist in container.');\n\t\t\t})\n\t);\n\t__webpack_require__.R = undefined;\n\treturn getScope;\n};\nvar init = (shareScope, initScope) => {\n\tif (!__webpack_require__.S) return;\n\tvar name = \"default\"\n\tvar oldScope = __webpack_require__.S[name];\n\tif(oldScope && oldScope !== shareScope) throw new Error(\"Container initialization failed as it has already been initialized with a different share scope\");\n\t__webpack_require__.S[name] = shareScope;\n\treturn __webpack_require__.I(name, initScope);\n};\n\n// This exports getters to disallow modifications\n__webpack_require__.d(exports, {\n\tget: () => (get),\n\tinit: () => (init)\n});\n\n//# sourceURL=webpack://module-lib/container_entry?");

/***/ })

1、moduleMap:用来映射expose的模块
2、get方法:导出给host应用,用于获取remote expose的模块
3、init方法:导出给host应用,用于将remote模块注入

get方法中调用了__webpack_require__.e加载chunk

/******/     // This file contains only the entry chunk.
/******/     // The chunk loading function for additional chunks
/******/     __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__.e调用__webpack_require__.f

/******/     __webpack_require__.f.consumes = (chunkId, promises) => {
/******/       if(__webpack_require__.o(chunkMapping, chunkId)) {
/******/         chunkMapping[chunkId].forEach((id) => {
                            // 如果host已经有则直接使用,否则去remote安装
/******/           if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]);
/******/           var onFactory = (factory) => {
/******/             installedModules[id] = 0;
/******/             __webpack_require__.m[id] = (module) => {
/******/               delete __webpack_require__.c[id];
/******/               module.exports = factory();
/******/             }
/******/           };
/******/           var onError = (error) => {
/******/             delete installedModules[id];
/******/             __webpack_require__.m[id] = (module) => {
/******/               delete __webpack_require__.c[id];
/******/               throw error;
/******/             }
/******/           };
/******/           try {
/******/             var promise = moduleToHandlerMapping[id]();
/******/             if(promise.then) {
/******/               promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError));
/******/             } else onFactory(promise);
/******/           } catch(e) { onError(e); }
/******/         });
/******/       }
/******/     }
/******/   })();
  • host加载自己的bundle main.js,其中使用jsonp加载对应remote提供的remoteLib.js;
  • 在remote中暴露了全局变量,host将remote注入后可以获取对应模块,其中的共享模块使用前会检查自身有没有,如果没有再去加载remote的内容。

04 拓展学习

可以学习开源项目:基于 Webpack 5 Module Federation,优雅且实用的微前端解决方案 https://github.com/yuzhanglon...

——END——

参考资料:

[1]How to Build a Micro Frontend with Webpack's Module Federation Plugin

[2]Webpack 新功能 Module Federation 深入解析

[3]基于 Webpack Module Federation,这可能是一个比较优雅的微前端解决方案

[4]探索 webpack5 新特性 Module federation 在腾讯文档的应用

[5]Module Federation原理剖析

推荐阅读

巧用Golang泛型,简化代码编写

Go语言DDD实战初级篇

Diffie-Hellman密钥协商算法探究

贴吧低代码高性能规则引擎设计

浅谈权限系统在多利熊业务应用

分布式系统关键路径延迟分析实践

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
10个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
MySQL数据库InnoDB存储引擎Log漫游(1)
作者:宋利兵来源:MySQL代码研究(mysqlcode)0、导读本文介绍了InnoDB引擎如何利用UndoLog和RedoLog来保证事务的原子性、持久性原理,以及InnoDB引擎实现UndoLog和RedoLog的基本思路。00–UndoLogUndoLog是为了实现事务的原子性,
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这