您现在的位置是:网站首页> 编程资料编程资料
JavaScript面试Module Federation实现原理详解_javascript技巧_
2023-05-24
468人已围观
简介 JavaScript面试Module Federation实现原理详解_javascript技巧_
基本概念
1、什么是 Module Federation?
首先看一下官方给出的解释:
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 解决的问题其实和微前端有些类似,都是将一个应用拆分成多个子应用,每个应用都可以独立开发、部署,但是他们也有一些区别,比如微前端需要一个中心应用(简称基座)去承载子应用,而 MF 不需要,因为任何一个应用都可以作为中心应用,其次就是 MF 可以实现应用之间的依赖共享。
2、Module Federation核心概念
- Container
一个使用 ModuleFederationPlugin 构建的应用就是一个 Container,它可以加载其他的 Container,也可以被其他的 Container 加载。
- Host&Remote
从消费者和生产者的角度看 Container,Container 可以分为 Host 和 Remote,Host 作为消费者,他可以动态加载并运行其他 Remote 的代码,Remote 作为提供方,他可以暴露出一些属性、方法或组件供 Host 使用,这里要注意的一点是一个 Container 既可以作为 Host 也可以作为 Remote。
- Shared
shared 表示共享依赖,一个应用可以将自己的依赖共享出去,比如 react、react-dom、mobx等,其他的应用可以直接使用共享作用域中的依赖从而减少应用的体积。
3、使用案例
下面通过一个实例来演示一下 MF 的功能,该项目由 main 和 component 两个应用组成,component 应用会将自己的组件 exposes 出去,并将 react 和 react-dom 共享出来给 main 应用使用。
完成代码可查看这里 github.com/projectcss/…
大家最好将源代码下载下来自己跑一遍便于理解,下面展示的是 main 应用的代码,在 App 组件中我们引入了 component 应用的 Button、Dialog和 ToolTip 组件。
main/src/App.js
import React, {useState} from 'react'; import Button from 'component-app/Button'; import Dialog from 'component-app/Dialog'; import ToolTip from 'component-app/ToolTip'; const App = () => { const [dialogVisible, setDialogVisible] = useState(false); const handleClick = (ev) => { setDialogVisible(true); } const handleSwitchVisible = (visible) => { setDialogVisible(visible); } return ( Open Dev Tool And Focus On Network,checkout resources details
components hosted on component-app
Buttons:
Dialog:
hover me please!
); } export default App; 效果如下:

我们看到,因为 main 应用 引用了 component 应用的组件,所以在渲染的时候需要异步去下载 component 应用的入口代码(remoteEntry)以及组件,同时只下载了 main 应用共享出去的 react 和 react-dom 这两个依赖,也就是说 component 中的组件使用的就是 main 应用 提供的依赖,这样就实现了代码动态加载以及依赖共享的功能。
4、插件配置
为了实现联邦模块的功能,webpack 接住了一个插件 ModuleFederationPlugin,下面我们就拿上面的例子来介绍插件的配置。
component/webpack.config.js
const {ModuleFederationPlugin} = require('webpack').container; const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './index.js', // ... plugins: [ new ModuleFederationPlugin({ name: 'component_app', filename: 'remoteEntry.js', exposes: { './Button': './src/Button.jsx', './Dialog': './src/Dialog.jsx', './Logo': './src/Logo.jsx', './ToolTip': './src/ToolTip.jsx', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true } }, }) ], }; 作为提供方,component 将自己的 Button、Dialog等组件暴露出去,同时将 react 和 react-dom 这两个依赖共享出去。
main/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container; const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); module.exports = { entry: './index.js', // ... plugins: [ new ModuleFederationPlugin({ name: 'main_app', remotes: { 'component-app': 'component_app@http://localhost:3001/remoteEntry.js', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true } }, }) ], }; 作为消费者的 main 应用需要定义需要消费的应用的名称以及地址,同时 main 应用也将自己的 react 和 react-dom 这两个依赖共享出去。
下面来介绍几个核心的配置字段:
- name
name 表示当前应用的别名,当作为 remote 时被 host引用时需要在路径前加上这个前缀,比如 main 中的 remote 配置:
remotes: { 'component-app': 'component_app@http://localhost:3001/remoteEntry.js', }, 路径的前缀 component_app 就是 component 应用的 name 值。
- filename filename 表示 remote 应用提供给 host 应用使用时的入口文件,比如上面 component 应用设置的是 remoteEntry,那么在最终的构建产物中就会出现一个 remoteEntry.js 的入口文件供 main 应用加载。
- exposes
exposes 表示 remote 应用有哪些属性、方法和组件需要暴露给 host 应用使用,他是一个对象,其中 key 表示在被 host 使用的时候的相对路径,value 则是当前应用暴露出的属性的相对路径,比如在引入 Button 组件时可以这么写:
import Button from 'component-app/Button';
- remote
remote 表示当前 host 应用需要消费的 remote 应用的以及他的地址,他是一个对象,key 为对应 remote 应用的 name 值,这里要注意这个 name 不是 remote 应用中配置的 name,而是自己为该 remote 应用自定义的值,value 则是 remote 应用的资源地址。
- shared
当前应用无论是作为 host 还是 remote 都可以共享依赖,而共享的这些依赖需要通过 shared 去指定。
new ModuleFederationPlugin({ name: 'main_app', shared: { react: { singleton: true }, 'react-dom': { singleton: true } }, }) 他的配置方式有三种,具体可以查看官网,这里只介绍常用的对象配置形式,在对象中 key 表示第三方依赖的名称,value 则是配置项,常用的配置项有 singleton 和 requiredVersion。
- singleton 表示是否开启单例模式,如果开启的话,共享的依赖则只会加载一次(优先取版本高的)。
- requiredVersion 表示指定共享依赖的版本。
比如 singleton 为 true 时,main 的 react 版本为 16.14.0,component 的 react 版本为 16.13.0,那么 main 和 component 将会共同使用 16.14.0 的 react 版本,也就是 main 提供的 react。
如果这时 component 的配置中将 react 的 requiredVersion 设置为 16.13.0,那么 component 将会使用 16.13.0,main 将会使用 16.14.0,相当于它们都没有共享依赖,各自下载自己的 react 版本。
工作原理
1、使用MF后在构建上有什么不同?
在没有使用 MF 之前,component,lib 和 main 的构建如下:

使用 MF 之后构建结果如下:

对比上面两张图我们可以看出使用 MF 构建出的产物发生了变化,里面新增了 remoteEntry-chunk、shared-chunk、expose-chunk 以及 async-chunk。
其中 remoteEntry-chunk、shared-chunk 和 expose-chunk 是因为使用了 ModuleFederationPlugin 而生成的,async-chunk 是因为我们使用了异步导入 import() 而产生的。
下面我们对照着 component 的插件配置介绍一下每个 chunk 的生成。
component/wenpack.config.js
const { ModuleFederationPlugin } = require('webpack').container; const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); module.exports = { entry: './index.js', // .... plugins: [ new ModuleFederationPlugin({ name: 'component_app', filename: 'remoteEntry.js', exposes: { './Button': './src/Button.jsx', './Dialog': './src/Dialog.jsx', './Logo': './src/Logo.jsx', './ToolTip': './src/ToolTip.jsx', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true } }, }) ] }; - remoteEntry-chunk 是当前应用作为远程应用(Remote)被调用的时候请求的文件,对应的文件名为插件里配置的 filename,我们当前设置的名称就叫做 remoteEntry.js,我们可以打开 main 应用的控制台查看:

- shared-chunk 是当前应用开启了 shared 功能后生成的,比如我们在 shared 中指定了 react 和 react-dom,那么在构建的时候 react 和 react-dom 就会被分离成新的 shared-chunk,比如 vendors-node_modules_react_index_js.js 和 vendors-node_modules_react-dom_index_js.js。
- espose-chunk 是当前应用暴露一些属性/组件给外部使用的时候生成的,在构建的时候会根据 exposes 配置项生成一个或多个 expose-chunk,比如 src_Button_jsx.js、src_Dialog_jsx.js 和 src_ToolTip_jsx.js。
- async-chunk 是一个异步文件,这里指的其实就是 bootstrap_js.js,为什么需要生成一个异步文件呢?我们看看 main 应用中的 bootstrap.js 和 index.js 文件:
main/src/bootstrap.js
import React from 'react'; import ReactDOM from 'react-dom'; import App from './app'; ReactDOM.render(, document.getElementById('app'));
main/src/index.js
import('./bootstrap') 一般在我们的项目中 index.js 作为我们的入口文件里面应该存放的是 bootstrap.js 中的代码,这里却将代码单独抽离出来放到 bootstrap.js 中,同时在 index.js 中使用 import('./bootstrap') 来异步加载 bootstrap.js,这是为什么呢?
我们来看下这段代码:
main/src/App.js
import React, {useState} from 'react'; import Button from 'component-app/Button'; const App = () => { return ( ); } export default App; 如果 bootstrap.js 不是异步加载的话而是直接打包在 main.js 里面,那么 import Button from 'component-app/Button 就会被立即执行了,但是此时 component 的资源根本没有被下载下来,所以就会报错。
提示:
本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
相关内容
- 使用Nest.js实现接口教程示例_javascript技巧_
- dispatchEvent解决重叠元素响应事件示例详解_javascript技巧_
- Vue中render函数调用时机与执行细节源码分析_vue.js_
- React-hooks面试考察知识点汇总小结(推荐)_React_
- 基于 angular material theming 机制修改 mat-toolbar 的背景色(示例详解)_AngularJS_
- 微信小程序实战项目之富文本编辑器实现_javascript技巧_
- Swiper如何实现两行四列轮播图效果实例_javascript技巧_
- TypeScript开发小状况记录之选且只选一个_javascript技巧_
- vue+threejs写物体动画之物体缩放动画效果_vue.js_
- vue学习记录之动态组件浅析_vue.js_
