Skip to content

Commit

Permalink
feat(mini-micro-frontend): init project
Browse files Browse the repository at this point in the history
  • Loading branch information
hua-bang committed Sep 1, 2024
1 parent 00c5464 commit 8ef36d6
Show file tree
Hide file tree
Showing 22 changed files with 536 additions and 19 deletions.
16 changes: 16 additions & 0 deletions packages/micro-frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "micro-frontend",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^5.5.4"
}
}
41 changes: 41 additions & 0 deletions packages/micro-frontend/src/app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Sandbox from "../sandbox";
import {
MiniMicroFrontendAppConfig,
MiniMicroFrontendRunOptions,
} from "../typings";

class MiniMicroFrontendApp {
private appConfig: MiniMicroFrontendAppConfig;
private config: MiniMicroFrontendRunOptions;
private sourceCode: string;
private providerRes: any;
private sandbox: Sandbox = new Sandbox();

constructor(
appConfig: MiniMicroFrontendAppConfig,
sourceCode: string,
config: MiniMicroFrontendRunOptions
) {
this.appConfig = appConfig;
this.sourceCode = sourceCode;
this.config = config;
}

mount() {
const source = this.sourceCode;

const { provider } = this.sandbox.run(source) as any;
this.providerRes = provider();
this.providerRes?.render({
dom: document.querySelector(this.config.domGetter!),
});
}

unmount() {
this.providerRes?.destroy({
dom: document.querySelector(this.config.domGetter!),
});
}
}

export default MiniMicroFrontendApp;
1 change: 1 addition & 0 deletions packages/micro-frontend/src/built-in/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./plugins";
1 change: 1 addition & 0 deletions packages/micro-frontend/src/built-in/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./lifeCycle";
12 changes: 12 additions & 0 deletions packages/micro-frontend/src/built-in/plugins/lifeCycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { MiniMicroFrontendPlugin } from "../../hooks";

export type LifeCyclePluginOptions = Partial<MiniMicroFrontendPlugin>;

export const getLifeCyclePlugin = (options: LifeCyclePluginOptions) => {
return {
name: "lifeCyclePlugin",
...options,
};
};

export default getLifeCyclePlugin;
35 changes: 35 additions & 0 deletions packages/micro-frontend/src/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import MiniMicroFrontendApp from "./app";
import { LifeCyclePluginOptions, getLifeCyclePlugin } from "./built-in";
import PluginSystem from "./hooks/pluginSystem";
import Loader from "./loader";
import {
MiniMicroFrontendAppConfig,
MiniMicroFrontendRunOptions,
} from "./typings";

class MiniMicroFrontend {
status: "started" | "stopped" = "stopped";
loader: Loader = new Loader();
options: MiniMicroFrontendRunOptions = {};
activeApp: MiniMicroFrontendApp | null = null;
hooks: PluginSystem = new PluginSystem();

run(userOptions?: MiniMicroFrontendRunOptions & LifeCyclePluginOptions) {
this.options = userOptions || {};
this.hooks.usePlugin(getLifeCyclePlugin(userOptions || {}));
this.status = "started";
this.hooks.lifeCycle.bootstrap.emit(this.options);
}

async load(app: MiniMicroFrontendAppConfig) {
this.hooks.lifeCycle.beforeLoad.emit(app);
const { sourceCode } = await this.loader.loadApp(app);
const appInstance = new MiniMicroFrontendApp(app, sourceCode, this.options);
this.hooks.lifeCycle.afterLoad.emit(app);
return appInstance;
}
}

const miniMicroFrontend = new MiniMicroFrontend();

export default miniMicroFrontend;
19 changes: 19 additions & 0 deletions packages/micro-frontend/src/decorators/checkStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function CheckStatus(
expectedStatus: "started" | "stopped",
errorMessage: string
) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (this: any, ...args: any[]) {
if (this.status !== expectedStatus) {
throw new Error(errorMessage);
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
1 change: 1 addition & 0 deletions packages/micro-frontend/src/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./checkStatus";
3 changes: 3 additions & 0 deletions packages/micro-frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./syncHook";
export * from "./pluginSystem";
export * from "./typings";
31 changes: 31 additions & 0 deletions packages/micro-frontend/src/hooks/pluginSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { SyncHook } from "./syncHook";
import { LifeCycle, LifeCycleKey, MiniMicroFrontendPlugin } from "./typings";

class PluginSystem {
public lifeCycle: LifeCycle = {
bootstrap: new SyncHook("bootstrap"),
beforeLoad: new SyncHook("beforeLoad"),
afterLoad: new SyncHook("afterLoad"),
beforeMount: new SyncHook("beforeMount"),
afterMount: new SyncHook("afterMount"),
beforeUnmount: new SyncHook("beforeUnmount"),
afterUnmount: new SyncHook("afterUnmount"),
};

private plugins: MiniMicroFrontendPlugin[] = [];

usePlugin(plugin: MiniMicroFrontendPlugin) {
if (this.plugins.includes(plugin)) {
throw new Error(`plugin ${plugin.name} has been used`);
}
this.plugins.push(plugin);
for (let key in this.lifeCycle) {
const callback = plugin[key as LifeCycleKey];
if (callback) {
this.lifeCycle[key as LifeCycleKey].on(callback);
}
}
}
}

export default PluginSystem;
29 changes: 29 additions & 0 deletions packages/micro-frontend/src/hooks/syncHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type Callback<T, K> = (...args: ArgsType<T>) => K;
export type ArgsType<T> = T extends Array<any> ? T : Array<any>;

export class SyncHook<T = any, K = any> {
public type: string = "";
public listeners = new Set<Callback<T, K>>();

constructor(type: string) {
this.type = type;
}

on(callback: Callback<T, K>) {
this.listeners.add(callback);
}

emit(...args: ArgsType<T>) {
this.listeners.forEach((listener) => {
return listener.apply(null, args);
});
}

remove(callback: Callback<T, K>) {
this.listeners.delete(callback);
}

removeAll() {
this.listeners.clear();
}
}
2 changes: 2 additions & 0 deletions packages/micro-frontend/src/hooks/typings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./lifeCycle";
export * from "./plugin";
13 changes: 13 additions & 0 deletions packages/micro-frontend/src/hooks/typings/lifeCycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SyncHook } from "..";

export interface LifeCycle {
bootstrap: SyncHook;
beforeLoad: SyncHook;
afterLoad: SyncHook;
beforeMount: SyncHook;
afterMount: SyncHook;
beforeUnmount: SyncHook;
afterUnmount: SyncHook;
}

export type LifeCycleKey = keyof LifeCycle;
7 changes: 7 additions & 0 deletions packages/micro-frontend/src/hooks/typings/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LifeCycle, LifeCycleKey } from "./lifeCycle";

export type MiniMicroFrontendPlugin = {
name: string;
} & {
[k in LifeCycleKey]?: Parameters<LifeCycle[LifeCycleKey]["on"]>[0];
};
4 changes: 4 additions & 0 deletions packages/micro-frontend/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import miniMicroFrontend from "./core";
export * from "./core";

export default miniMicroFrontend;
23 changes: 23 additions & 0 deletions packages/micro-frontend/src/loader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { MiniMicroFrontendAppConfig } from "../typings";
import { AppLoaderInfo } from "./typings";
export * from "./typings";

class Loader {
appMap: WeakMap<MiniMicroFrontendAppConfig, AppLoaderInfo> = new WeakMap();

async loadApp(app: MiniMicroFrontendAppConfig) {
if (this.appMap.has(app)) {
return this.appMap.get(app) as AppLoaderInfo;
}

const sourceCode = await (await fetch(app.entry)).text();
const appLoaderInfo: AppLoaderInfo = {
sourceCode,
};
this.appMap.set(app, appLoaderInfo);

return appLoaderInfo;
}
}

export default Loader;
3 changes: 3 additions & 0 deletions packages/micro-frontend/src/loader/typings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AppLoaderInfo {
sourceCode: string;
}
Loading

0 comments on commit 8ef36d6

Please sign in to comment.