美文网首页
Webpack Module Federation

Webpack Module Federation

作者: 张培_ | 来源:发表于2022-04-21 20:01 被阅读0次

Module federation让一个JS APP能够在server端或者client端运行来自其他bundle/build中的代码。

背景

共享code/组件一直是一个永恒的话题。
在同一APP中想要复用/共享一段代码很简单,可以提出一个function,如果是UI级别的复用,那么就是提出一个组件,通过模块的import/export实现。

但是在不同的APP中想要共享/复用一段代码/组件就没有那么简单了,通常都只是通过提出一个npm包,放在npm register中统一管理,不同的APP如果想要使用,就从npm register安装。如果遇到版本升级,所有使用该库的APP都需要手动进行升级,依然不是很方便。

What is Module Federation?

Module Federation是webpack5中的新功能,这个功能能够让一个JS APP动态的从另外一个JS APP加载代码,利用最小的花费实现代码的复用。
从另一个JS APP中加载来的代码通常被叫做federated module code
加载来的代码还能够直接使用当前APP中的shared dependency作为自己的dependency, 如果当前APPshared dependencies中并没有其所需的dependency,那么webpack会从federated module code所在的APP中下载。

Terminology

  • Module federation: 在多APP(浏览器/nodejs)中实现动态地代码共享,

  • A host: 其实就是一个可以自己运行的APP,但是会作为消费者,从其他的APP中请求federated module code。

  • A remote: 其实是另一个APP, 但是会提供federated module code

  • Bidirectional-hosts: 即可以自己运行作为host消费其他APP的federated module code,也可以作为remote 为其他的APP提供federated module code。

SPA Bidirectional-hosts

假设我们计划做一个SPA, 这个APP可能有三个页面:

  • Dashboard页面
  • SRP页面
  • PDP页面

我们期待这个三个页面可以独立开发,独立部署,独立运行, 那么webpack federation就是一个很好的工具。

每一个页面都是一个单独的APP,有独立的repo,独立开发,独立部署,独立运行。

因此每一个页面,当然也是每一个APP都是bi-directional hosts.

任何一个页面(APP),如果先打开(先load)那么就是host, 如果要跳转到其他页面,也能够利用react router,做一个动态import从其他page(APP)中,把相应页面的组件加载过来。此时在新的页面上刷新,那么当前页面所在的APP就会被load,此时当前页面所在的APP就变成了host。

这样,由于每一个page(APP)

  • 都使用react router成为了一个SPA,因此可以作为host
  • 都使用webpack federation module,将当前页面的根组件作为federated code暴露出去,因此也可以作为remote。

这样,如果你了解过Route级别的code split,那么页面之间的跳转就不再需要external跳转,就只需要使用动态import,从remote将新页面的组件取回即可。

如何处理federated component的dependency

加入App A暴露了一个APP component, 这时候APP B作为消费者会使用APP component,但是APP component 可能依赖于
"react", "react-dom" 。

那我们的处理方案是否和一般的npm package一样,将该package需要使用的所有依赖都包含在这个npm package中?

这样做会出现的问题:

  • 包非常的大
  • 如果npm package需要依赖react,如果其将react打入到了最终的npm包中,如果APP B也是一个react app,那么极有可能出现APP B中有两React,这个是绝对不能被React 允许的

这种时候对于NPM包我们会想到使用peer dependency,对就是直接让包使用宿主中的依赖。

同理webpack federation也提供了shared字段,也就是明确写明,当前的repo中能够用于和remote共享的依赖是那些。

这样从其他APP引入的component可以直接使用宿主的shared dependency,尽可能减少code duplication

实例

APP1

  • APP1中将会一个根组件APPContainer, 这个组件可以被其他APP消费
  • APP1也是一个SPA, 可以独立运行,也可以作为消费者消费其他APP的组件
  • 由于APP1会做消费者(host),因此会和federated Module共享依赖
const HtmlWebpackPlugin = require("html-webpack-plugin"); 
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
    // other webpack configs...
    
    plugins: [
        new ModuleFederationPlugin({
            name: '_app_one_remote',  // 当前APP作为remote暴露组件时的APP的名字
            library: 'app_one_remote', // 当前APP作为remote暴露组件时的library名字
            filename: 'remoteEntry.js',
            // 所有被暴露的组件会打包到这个文件中,同理使用者也需要从这里引入
            remotes: {  
                app_two: "app_two_remote",  
                app_three: "app_three_remote"  
            }, // 定义该库作为host时可能要引用的remote
            exposes: {
                'AppContainer': './src/App'
            }, // 定义该库作为remote时,要暴露出去的组件。左边是相对路径和组件名字(其他库使用时候),右边是该组件在本库内的路径
            shared: ["react", "react-dom","react-router-dom"]// 和引入的组件公用的dependency
        })
    ]
};

那么当APP1作为remote的时候,其他的库是如何使用APP1暴露出去的组件APPContainer呢?很简单、

APP1已经被部署和独立运行后,由于webpack的构建,该APP会有不同的入口和bundle。

  • 正常的入口是index.js保证APP的运行
  • 另外一个入口是暴露出去的组件

因此一旦这些打出来的静态文件被服务器server,比如服务运行在localhost:3000,

  • 你可以通过访问http://localhost:3000看到APP正常运行
  • 也可以访问http://localhost:3000/app_one_remote.js获取到该APP暴露出来的组件。

作为consumer你可以这样引入来自APP1的组件

  • 配置webpack,使其可以顺利的找到remote的组件
const ModuleFederationPlugin = require('webpack-plugin-module-federation');

module.exports = {
    // ...
    plugins: [
        new ModuleFederationPlugin({
            name: '_app_two_remote',
            library: 'app_two_remote',
            filename: 'remoteEntry.js',
            libraryTarget: 'global',
            remotes: {
                'app_one_remote': '_app_one_remote'
            },
            expose: {
                App: './src/App'
            },
        }),
    ]
};
  • 首先需要在HTML中引入remote的组件包文件
<head>  
    <script src="http://localhost:3001/remoteEntry.js"></script> 
</head>  
<body>  
    <div id="root"></div>  
</body>

  • 动态import将要使用的组件从HTML中加载回来的remoteEntry.js文件中引入
import React, { lazy, Suspense, useState } from 'react';
import Footer from './Footer';

const AppContainer = lazy(() => import('app_one_remote/AppContainer')); // federated

export default () => {
    return (
        <>
            <Suspense fallback={'fallback'}>
                <AppContainer />
            </Suspense>
            <p>
                // ....
            </p>
            <Footer />
        </>
    );
};

使用场景

  • Domain

通常对于一个可能涉及多个不同sub site的网站,比如举个例子房产网站,类似于安居客之类的。这样的网站大多会涉及多种不同的业务,比如二手房屋买卖,新房买卖,房屋出租等,这些不同的领域通常由不同的组来维护,最适合使用webpack module federation做成micro frontend。

不同的组独立开发独立部署自己的网站,同时:

  • 将自己的网站的APP组件暴露出来
  • 能够动态引入其他网站的APP组件
image.png
  • Widget

专门独立部署一个APP,其中包含多个不同的widget,或者UI组件专门供给不同的APP

image.png

相关文章

网友评论

      本文标题:Webpack Module Federation

      本文链接:https://www.haomeiwen.com/subject/ycjwertx.html