Webpack Module Federation

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容