您现在的位置是:网站首页> 编程资料编程资料

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:

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、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!

-六神源码网