@ epam/ 微前端
这个软件包包含了简化微前端开发的公用设施和脚本.
定义
- 远程应用程序:主机远程部件的应用程序.
- 主机应用程序:生成远程部件的应用程序.
- 远程外观( N)
widgets.js:在"Remote app"上托管的统一界面被"Host app"用于渲染远程部件.
主思想
"Host app"使用"Remote外观"从"Remote app"中取回了远程部件,该软件位于一个著名的URL.
举例来说,这是比较好的.
让我们假设 Remote app 此来源为: http://127.0.0.1:4000
主办方认为 Remote facade 它位于一个著名的 URL,它看起来像: http://127.0.0.1:4000/widgets.js
由"Remote app"托管的有其他几种资源,而"Host"不知道此类资源的存在. 然而,它们被隐含地使用(由 widgets.js在远程部件的安装中。 以下是资源清单:
- 远程资产表
http://127.0.0.1:4000/<path_to_build_manifest>(中文(简体) ) - 远程 App JS 捆绑
http://127.0.0.1:4000/<path_to_main_js_bundle>(中文(简体) ) - 远程 App CSS 捆绑 (
http://127.0.0.1:4000/<path_to_main_css_bundle>(中文(简体) )
以下是主机为挂载远程部件而执行的动作序列 .
- 洛杉矶
// The "Remote facade" is loaded using a well-known URL. Internally, the Host loads the widget like this. const remote = await import('http://127.0.0.1:4000/widgets.js'); - 内 容
/** * Several IMPLICIT actions are done during the init(). The Host doesn't know anything about these actions; they are encapsulated inside "widgets.js". * 1) The "Remote Assets Manifest" file is loaded (if applicable). * 2) The URL to the "Remote App JS bundle" is determined from the manifest (if applicable). * 3) The URL to the "Remote App CSS bundle" is determined from the manifest (if applicable). * 4) The window.Widgets global variable is created (unless already defined). It acts as a simple registry that helps to define/require widgets. * 5) The "Remote App JS bundle" is loaded via window.Widgets.require(/../). Note: A "script" tag is used under the hood to load the JS bundle. It's loaded only once and cached. * 6) Widgets defined during the load (via window.Widgets.define(...)) are collected. * Note: The "Remote App CSS bundle" is NOT loaded at this step. It will be loaded later, during the widget "mount". */ const widgets = await remote.default.init(context /* this context is optional */); - 备忘录
// The "Remote App CSS bundle" is loaded as part of this call (right before the actual mount occurs). const { unmount } = widgets['demoWidget'].mount(/*...*/); - 弹药
// The "Remote App CSS bundle" is unloaded after this step. unmount();
已知问题
"相去相去"(widgets.js)的地物
- 自定义挂载的可能性(详情见CLI 使用等)
- 规定 a
Widgets支持以流行格式装入捆绑的模块系统(IIFE、无害环境管理) - 已处理
Babel以保证与浏览器兼容 - 处理错误
- 转角案件处理
- 概括分析不同类型资产表文件的逻辑
- 它只主机在远程侧,因为它包含远程应用程序特有的逻辑
CLI 使用量
(Remote app) 生成远程外观 (widgets.js)
从下面运行命令 remote 项目的根文件夹 .
它创建“ 外观” 文件并将其放入 ./public 文件夹.
注: 它需要运行 第一次, 只能再次运行 @epam/microfrontends 软件包版本已更改.
# 1) Without installing the NPM package, this is preferable. Exact version can be specified instead of "latest".
npx --yes @epam/microfrontends@latest epam-microfrontends-init --app-type=webpack
# 2) When "@epam/microfrontends" is installed locally.
npx epam-microfrontends-init --app-type=webpack
将创建以下文件:
widgets.js (+ widgets.js.map)- 远程版的"Remote facecade"的精密版本. "Host app"下载并运行此文件以获取远程部件.widgets.dev.js and widgets-dev.html- 不可忽略的"背面"版本. 它可能被用作在当地孤立地测试远程部件的起点.
注:删除这些文件是安全的,除非您计划通过“widgets-dev.html”本地测试您的部件.
所有选项 :
--app-type (required) Application type (in all cases the widget.js/widgets-dev.html will be copied to "./public" dir)
webpack - for Webpack apps
vite - for Vite apps
angular - for Angular apps
--bundle-format (optional) The format of the app bundle which contains single Widgets.define(...):
iife - when bundle is IIFE (this is a default value for --app-type=webpack
esm - when bundle is ESM (this is a default value for --app-type=vite and --app-type=angular)
--remote-css (optional)
shadowRoot (this is a default value for --app-type=webpack and --app-type=vite) - the CSS will be added to the shadow root of the target element
global - the CSS will be added to the document.head of the host page.
skip - (this is a default value for --app-type=angular) CSS will be loaded as part of mount method of the remote widget.
--add-css-to-host (optional)
{path} - path to the css on remote widget's server. It will be injected into the host page when the widget mounts. E.g.: /app/app_name/host/styles.css
--reset-css-in-shadow-dom (optional)
{boolean} - when true (it is by default), it adds inline style which resets CSS in ShadowRoot
(Angular app- specific) 生成资产管理. json
# Run next CLI command AFTER every Angular build. This command will generate asset-manifest based on the Angular build output (directory "./dist")
npx --yes @epam/microfrontends@latest epam-microfrontends-init --cmd=angular
反应
定义远程部件
import { createRoot } from 'react-dom/client';
import { IHistoryMicroFe } from '@epam/microfrontends/helpers/history';
const demoWidget = {
mount: (target: HTMLElement, params: any) => {
/*
* If a remote widget uses routing, then it's necessary to do all routing via the "params.historyMicroFe".
* Use "params.historyMicroFe" directly or via converters exported from "@epam/microfrontends/helpers/history"
*/
const root = createRoot(target);
root.render(<DemoWidgetComponent />);
return {
unmount: () => {
root.unmount();
}
}
}
};
if (isPartOfTheNormalApp) {
// render app
}
(window as any).Widgets?.define(() => ({
demoWidget,
}));
控制远程部件
import { RemoteWidget, RemoteWidgetContext } from '@epam/microfrontends/helpers/react';
import { IHistory4Full_to_IHistoryMicroFe } from '@epam/microfrontends/helpers/history';
import { createBrowserHistory } from 'history';
//...//
const history = createBrowserHistory();
const historyMicroFe = IHistory4Full_to_IHistoryMicroFe(history as any);
//...//
function App() {
// Somewhere in the app:
const params = { historyMicroFe };
return (
<RemoteWidgetContext.Provider value={params}>
<RemoteWidget
url="http://172.20.0.1:4001/widgets.js"
name="demoWidget"
/>
<RemoteWidget
url="http://172.20.0.1:4002/widgets.js"
name="demoWidget"
/>
</>
)
}
角质
(远程) 定义远程部件
// 1. Add widget component
@Component({
selector: `demoWidget-root`,
standalone: true,
templateUrl: "./demoWidget.component.html",
encapsulation: ViewEncapsulation.ShadowDom,
})
export class DemoWidgetComponent {
title = "angular";
}
// 2. Register remote widget
(window as any).Widgets?.define(() => ({
demoWidget: {
mount: (targetElement: HTMLElement, props: Record<string, any>) => {
targetElement.appendChild(document.createElement(`demoWidget-root`));
bootstrapApplication(AppComponent, {
providers: [{ provide: YOUR_TOKEN, useValue: props.yourValue }] }
).catch((err) => console.error(err));
return { unmount: () => {} };
}
},
}));
控制远程部件
import { RemoteWidgetComponent } from '@epam/microfrontends/helpers/angular17';
//...//
@Component({
selector: 'mfe-demo',
standalone: true,
imports: [RemoteWidgetComponent],
template: '<mfe-angular [url]="url" [widgetName]="widgetName" [props]="props"></mfe-angular>',
})
export class AppComponent {
public url = `http://localhost:4004/apps/remotes-angular/widgets.js`;
public widgetName = 'demoWidget';
public props = { yourValue: { id: '1', name: 'Input for mfe Angular' } };
}
在远程部件中运行
严格地说,这个库对如何在远程或主机中执行路由没有任何限制. 本节仅载有在大多数典型情况下可能有用的建议.
如果一些远程部件使用路由,那么预计它应该使用由"Host app"提供的"HoryMicroFe"标准接口. 一般情况下,主机和远程可以使用不同的路由器,例如:
(HOST: 未知1) -- -- >(IHistoryMicroFe) -- >(REMOTE: 未知2)
在这种情况下,需要一些适配器:
- 未知 1 - > 历史缩写
- IHistoryMicroFe - > 未知数 2
此软件包中有多个适配器可用 OOTB. 它们涵盖一些典型的使用案例:
- IHistory4Full to IHistoryMicroFe (英语)
- Irouter6 to IHistoryMicroFe (英语)
- IHistoryMicroFe to IHistory 路透社6
- IHistoryMicroFe to IHistory4Full (英语)
使用“ 基准名 ”
- 预计"Host app"不会使用路由器的"basename"特性. 因此,该全位置路径被"Remote app"所曝光.
- 如有需要,"Remote app"可以选择使用"basename"进行路由. 见这里的例子:
packages/examples/remotes/cra-router5/src/widgets/demoWidget/demoWidget.tsx