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


前面 我们已经实践了用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就可以了。