Angular Micro-Frontend Component Library Development
Since there is no angular micro front-end component implementation on the wujie official website, we will implement one ourselves
client preview
前面 我们已经实践了用wujie改造以Angular为主应用的微前端项目。但在Angular子应用组件的封装上只实现了最基础的配置,我们将继续实现完整的 ngx-wujie
组件并发布到我们的npm 仓库。
Project Setup
利用Angular cli创建一个 ngx-wujie
组件库项目,并安装 wujie
核心包。 此处省略(可以参考 Angular library created)
Core component implementation
1. Main structure
typescript
import { Component, Input, Output, EventEmitter, ElementRef, ViewChild, OnChanges, SimpleChanges, AfterViewInit, OnDestroy } from "@angular/core";
import { startApp, destroyApp, plugin, loadErrorHandler } from "wujie";
import { lifecycle } from "wujie/esm/sandbox";
@Component({
selector: "ngx-wujie",
standalone: true,
imports: [],
template: ` <div #container></div> `,
styles: ``,
})
export class NgxWujieComponent implements AfterViewInit, OnChanges, OnDestroy {
// 组件实现...
}
我们采用Angular 新版推荐的Standalone组件的实现方法(同时兼容Module 写法)。
2. Input Options
参考wujie options, 我们的输入属性覆盖了 wujie 的所有功能,输入类型同时参考了wujie api.
typescript
// 必需属性
@Input({ required: true }) name!: string; // 微应用名称
@Input({ required: true }) url!: string; // 微应用地址
// 可选配置属性
@Input() width?: string; // 容器宽度
@Input() height?: string; // 容器高度
@Input() loading?: HTMLElement; // 加载状态元素
@Input() sync?: boolean = false; // 同步加载
@Input() prefix?: { [key: string]: string; }; // 路由前缀
@Input() alive?: boolean = false; // 保活模式
@Input() props?: { [key: string]: unknown }; // 传递给微应用的数据
@Input() replace?: (code: string) => string; // 代码替换函数
@Input() fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>; // 自定义 fetch
@Input() fiber?: boolean = true; // 启用 fiber 模式
@Input() attrs?: Record<string, string>; // 自定义属性
@Input() degrade?: boolean = false; // 降级到 iframe
@Input() plugins?: Array<plugin>; // 插件列表
@Input() exec?: boolean; // 执行模式
@Input() iframeAddEventListeners?: Array<string>; // iframe 事件监听器
@Input() iframeOnEvents?: Array<string>; // iframe 事件
// 生命周期钩子
@Input() beforeLoad?: lifecycle; // 加载前
@Input() beforeMount?: lifecycle; // 挂载前
@Input() afterMount?: lifecycle; // 挂载后
@Input() beforeUnmount?: lifecycle; // 卸载前
@Input() afterUnmount?: lifecycle; // 卸载后
@Input() activated?: lifecycle; // 激活时
@Input() deactivated?: lifecycle; // 停用时
@Input() loadError?: loadErrorHandler; // 加载错误处理
3. Output Options
typescript
@Output() mounted = new EventEmitter<void>(); // 挂载完成
@Output() unmounted = new EventEmitter<void>(); // 卸载完成
@Output() error = new EventEmitter<unknown>(); // 错误事件
抛出关键的挂载事件和错误收集事件供主应用使用。
4. Core functions
buildStartOptions
typescript
private buildStartOptions(el?: HTMLElement) {
return {
name: this.name,
url: this.url,
el: el ?? this.containerRef?.nativeElement,
props: this.props,
attrs: this.attrs,
replace: this.replace,
sync: this.sync,
prefix: this.prefix,
alive: this.alive,
fiber: this.fiber,
degrade: this.degrade,
loading: this.loading,
fetch: this.fetch,
iframeAddEventListeners: this.iframeAddEventListeners,
iframeOnEvents: this.iframeOnEvents,
plugins: this.plugins,
beforeLoad: this.beforeLoad,
beforeMount: this.beforeMount,
afterMount: this.afterMount,
beforeUnmount: this.beforeUnmount,
afterUnmount: this.afterUnmount,
activated: this.activated,
deactivated: this.deactivated,
loadError: this.loadError,
};
}
life cycle
typescript
// 启动微应用
private start(): void {
if (!this.containerRef || !this.name || !this.url) return;
const el = this.containerRef.nativeElement;
try {
startApp(this.buildStartOptions(el));
} catch (err) {
console.error('[wujie-angular] start [${this.name}] error:', err);
this.error.emit(err);
return;
}
this.hasMounted = true;
}
// 销毁微应用
private destroy(): void {
try {
if (this.name) {
destroyApp(this.name)
};
} catch (err) {
console.error(`[wujie-angular] destroy [${this.name}] error:`, err);
}
this.hasMounted = false;
}
// 重新挂载
private remount(): void {
if (!this.containerRef || !this.name || !this.url) return;
//销毁重建
this.destroy();
this.start();
}
// 热更新
private hotUpdate(): void {
try {
const el = this.containerRef?.nativeElement;
startApp(this.buildStartOptions(el));
} catch (err) {
console.error('[wujie-angular] hotUpdate error:', err);
this.error.emit(err);
}
}
5. Angular lifecycle
Component initialization
typescript
ngAfterViewInit(): void {
this.start();
}
Responsive update handling
typescript
ngOnChanges(changes: SimpleChanges): void {
if (!this.hasMounted) return;
const nameOrUrlChanged = !!(changes['name'] || changes['url']);
const behaviorChanged = !!(changes['replace'] || changes['fetch'] || changes['plugins'] || changes['fiber'] || changes['degrade'] || changes['sync'] || changes['prefix']);
const propsChanged = !!(changes['props'] || changes['attrs']);
const aliveChanged = !!changes['alive'];
// 名称或 URL 变化时重新挂载
if (nameOrUrlChanged) {
this.remount();
return;
}
// alive 状态变化处理
if (aliveChanged) {
if (this.alive === false) {
this.remount();
return;
} else {
this.hotUpdate();
return;
}
}
// 属性或行为变化处理
if (propsChanged || behaviorChanged) {
if (this.alive) {
this.hotUpdate();
} else {
this.remount();
}
return;
}
}
Destory
typescript
ngOnDestroy(): void {
if (!this.alive) {
this.destroy();
}
}
connfig & build
1. package.json config
json
{
"name": "ngx-wujie",
"version": "0.0.1",
"description": "Angular wrapper for wujie micro-frontend framework",
"keywords": ["angular", "wujie", "micro-frontend", "microfrontend"],
"author": "slim-fighting",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/slim-fighting/ngx-wujie.git"
},
"main": "bundles/ngx-wujie.umd.js",
"module": "fesm2015/ngx-wujie.js",
"es2015": "fesm2015/ngx-wujie.js",
"esm2015": "esm2015/ngx-wujie.js",
"fesm2015": "fesm2015/ngx-wujie.js",
"typings": "ngx-wujie.d.ts",
"metadata": "ngx-wujie.metadata.json",
"peerDependencies": {
"@angular/common": "^19.2.0",
"@angular/core": "^19.2.0",
"wujie": ">=1.0.0"
},
"dependencies": {
"tslib": "^2.3.0"
},
"sideEffects": false
}
2. ng-package.json config
json
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/ngx-wujie",
"lib": {
"entryFile": "src/public-api.ts"
}
}
3. Build Script
json
{
"scripts": {
"build:lib": "ng build ngx-wujie",
"package": "ng build ngx-wujie && cd dist/ngx-wujie && npm pack",
"publish:lib": "ng build ngx-wujie && cd dist/ngx-wujie && npm publish"
}
}
publish to npm
1. build
bash
# 登录 npm
npm login
# 构建库
npm run build:lib
# 测试打包
npm run package
2. Publish
bash
npm run publish:lib
Summary
通过实现 ngx-wujie
组件库,我们成功的将wejie 与 Angular 生命周期相结合, 实现 angular 的wujie组件。再使用的时候安装 ngx-wujie
就可以了。
Links
