我目前正在与 Module Federation 探索微前端。我刚刚分叉了一个沙箱,当两个模块都可用时尝试成功。它有 2 个模块,
app1
作为 host,以及 app2
作为 remote 组件。但由于我认为模块联合中的每个模块应该是独立的,因此我尝试使 app2
不可用,因为我没有启动它。因此,当我运行 app1
时出现错误,它完成加载并显示 React 的 Suspense
的后备,但几毫秒后,它变成空白,因为有错误我无法检索,因此我真的不知道。
之后,我尝试了Webpack的Promise Based Dynamic Remotes,然后我的
webpack-config.js
变成了这样:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const ExternalTemplateRemotesPlugin = require('external-remotes-plugin');
const path = require('path');
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
static: path.join(__dirname, 'dist'),
port: 3001,
},
output: {
publicPath: 'auto',
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'app1',
remotes: {
app2: Promise((resolve) => {
const urlParams = new URLSearchParams(window.location.search);
const version = urlParams.get('app1VersionParam');
// This part depends on how you plan on hosting and versioning your federated modules
const remoteUrlWithVersion = '[app2Url]' + '/remoteEntry.js';
const script = document.createElement('script');
script.src = remoteUrlWithVersion;
script.onload = () => {
// the injected script has loaded and is available on window
// we can now resolve this Promise
const proxy = {
get: (request) => window.app1.get(request),
init: (arg) => {
try {
return window.app1.init(arg);
} catch (e) {
console.log('remote container already initialized');
}
},
};
resolve(proxy);
};
// inject this script with the src set to the versioned remoteEntry.js
document.head.appendChild(script);
}),
// "app2@[app2Url]/remoteEntry.js",
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
new ExternalTemplateRemotesPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
我尝试再次启动
app1
,然后出现这个错误:
$ webpack serve
[webpack-cli] Failed to load '/home/projects/github-rl5uyr/app1/webpack.config.js' config
[webpack-cli] TypeError: undefined is not a promise
at Promise (<anonymous>)
at Object.eval (/home/projects/github-rl5uyr/app1/webpack.config.js:32:15)
at Object.function (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:11:114831)
at Module._compile (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:6:167880)
at Object.Module._extensions..js (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:6:168239)
at Module.load (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:6:166317)
at Function.Module._load (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:6:163857)
at Module.require (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:6:166635)
at i (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:6:427483)
at _0x5301a6 (https://github-rl5uyr.w.staticblitz.com/blitz.01faa899fac41642342f4b7113feacabea334fa1.js:11:114450)
那么,模块联盟可以彼此独立运行吗?如果不是,那么作为整体前端的普通库依赖项而不是这个复杂的微前端的真正区别是什么,我认为它应该能够像微服务一样独立工作?
我认为该承诺定义中有一些需要修复的内容: 首先它应该被定义为一个字符串
app2:
new Promise((resolve) => { ....
其中 app2 传递的配置是 ` 字符 之间的字符串
如文档
中所述之后,您应该更改代理定义以获取
window.app2
而不是 window.app1
。
最后
remoteUrlWithVersion = '[app2Url]' + '/remoteEntry.js';
不是一个有效的URL
希望有帮助
我遇到了同样的问题,我找到的解决方案不是在
webpack.config.js
中,而是在react中导入模块。首先是安装这2个依赖项
npm i react-lazily (https://www.npmjs.com/package/react-lazily)
npm i react-error-boundary (https://www.npmjs.com/package/react-error-boundary)
例如,您想要从任何应用程序导入的组件或视图将使用
lazily
来调用
const { Login } = lazily(() => import("auth/Auth"));
const { Directory } = lazily(() => import("directory/Directory"));
const { Resources } = lazily(() => import("resources/Resources"));
const { Statistics } = lazily(() => import("statistics/Statistics"));
对于
react-error-boundary
的使用,我建议制作一个单独的组件,所以我这样做了
import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
export const Externals = ({ children }) =>
{
return (
<Suspense
fallback="loading...."
>
<ErrorBoundary>
{ children }
</ErrorBoundary>
</Suspense>
)
}
在我的例子中,我在我的
AppRouter.jsx
文件中使用了这个组件,如下
import React from 'react';
import { createBrowserRouter, Navigate } from "react-router-dom";
import { lazily } from 'react-lazily'
import { Authentification, AuthentificationChecked } from '../views/grid/index';
import { Externals } from './Externals';
const { Login } = lazily(() => import("auth/Auth"));
const { Directory } = lazily(() => import("directory/Directory"));
const { Resources } = lazily(() => import("resources/Resources"));
const { Statistics } = lazily(() => import("statistics/Statistics"));
export const router = createBrowserRouter(
[
{
path: '/*', element: <Navigate to="auth/login" />
},
{
path: 'auth',
element: <Authentification />,
children:
[
{ path: 'login', element: <Externals><Login /></Externals>, errorElement: <div>Error al cargar el modulo</div> }
]
},
{
path: 'main',
element: <AuthentificationChecked />,
children:
[
{ path: 'directory', element: <Externals><Directory /></Externals>, errorElement: <div>Error al cargar el modulo</div> },
{ path: 'resources', element: <Externals><Resources /></Externals>, errorElement: <div>Error al cargar el modulo</div> },
{ path: 'statistics', element: <Externals><Statistics /></Externals>, errorElement: <div>Error al cargar el modulo</div> }
]
}
]);
其中
<Externals />
封装了我从任何 apps
导入的组件,如果它在视图中正确响应,该组件将被反映,但如果发生错误,导入它的应用程序将不会挂起或保持白色,在这种情况下,它将显示包含在每条路线的 errorElement
中的错误消息。
而且无需在
webpack.config.js
中进行任何额外的配置,这就是我的文件的外观
remotes:
{
auth: "auth@http://localhost:8081/remoteEntry.js",
directory: "directory@http://localhost:8082/remoteEntry.js",
resources: "resources@http://localhost:8083/remoteEntry.js",
statistics: "statistics@http://localhost:8084/remoteEntry.js"
},
问题:
TypeError: Failed to fetch dynamically imported module:
const Counter = lazy(() => import("remoteApp/Counter")); // it may break!
我在
react
端本地解决了这个问题,没有通过捕获 import statement
上的错误添加任何额外的依赖项,如下所示:
import { lazy, Suspense } from "react";
const Counter = lazy(() =>
import("remoteApp/Counter").catch(() => ({
default: ({ onLoadingError }: { onLoadingError: (error: Error) => void }) => {
onLoadingError(new Error("Unable to load Counter"));
return <h5>Unable to load Counter, please reload the page!</h5>;
},
}))
);
export default function App() {
function errorHandler(e: Error) {
console.error(e.message);
// Your app is saved from bowing up!
// Do whatever you want here...
}
return (
<>
<h1>Host App</h1>
<div className="card">
<Suspense fallback="Loading Counter...">
<Counter onLoadingError={errorHandler} />
</Suspense>
</div>
</>
);
}
Counter
是常规的 React 组件。<h5>Unable to load Counter, please reload the page!</h5>
我传递了一个名为
onLoadingError
的自定义错误处理程序,以通知我的业务逻辑远程组件无法加载。