@ice/mf-runtime 1.0.2 → 1.0.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -1
- package/docs/PLUGIN_SYSTEM_GUIDE.md +294 -0
- package/es2017/RemoteModule.js +73 -21
- package/es2017/__tests__/plugin-manager.test.d.ts +1 -0
- package/es2017/__tests__/plugin-manager.test.js +291 -0
- package/es2017/__tests__/setup.d.ts +1 -0
- package/es2017/__tests__/setup.js +28 -0
- package/es2017/index.d.ts +2 -0
- package/es2017/index.js +4 -2
- package/es2017/plugin-manager.d.ts +74 -0
- package/es2017/plugin-manager.js +128 -0
- package/es2017/runtime-plugin.js +29 -27
- package/es2017/types.d.ts +67 -0
- package/es2017/types.js +18 -1
- package/esm/RemoteModule.js +73 -30
- package/esm/__tests__/plugin-manager.test.d.ts +1 -0
- package/esm/__tests__/plugin-manager.test.js +343 -0
- package/esm/__tests__/setup.d.ts +1 -0
- package/esm/__tests__/setup.js +43 -0
- package/esm/index.d.ts +2 -0
- package/esm/index.js +4 -2
- package/esm/plugin-manager.d.ts +74 -0
- package/esm/plugin-manager.js +201 -0
- package/esm/runtime-plugin.js +33 -37
- package/esm/types.d.ts +67 -0
- package/esm/types.js +20 -1
- package/package.json +14 -3
package/es2017/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { UserOptions } from '@module-federation/runtime-core';
|
|
2
2
|
import type { Remote } from '@module-federation/runtime-core/dist/src/types';
|
|
3
|
+
import * as React from 'react';
|
|
3
4
|
export interface ExtraInfo {
|
|
4
5
|
external?: Record<string, string>;
|
|
5
6
|
legacy?: boolean;
|
|
@@ -18,3 +19,69 @@ export interface MicroMod {
|
|
|
18
19
|
moduleFederatedName: string;
|
|
19
20
|
extraInfo?: ExtraInfo;
|
|
20
21
|
}
|
|
22
|
+
export interface FederationRuntimePlugin {
|
|
23
|
+
name: string;
|
|
24
|
+
afterResolve?: (args: any) => any;
|
|
25
|
+
onLoad?: (args: any) => any;
|
|
26
|
+
beforeRequest?: (args: any) => any;
|
|
27
|
+
}
|
|
28
|
+
export interface WrapperContext {
|
|
29
|
+
remoteName: string;
|
|
30
|
+
moduleName: string;
|
|
31
|
+
props: Record<string, any>;
|
|
32
|
+
React: typeof import('react');
|
|
33
|
+
ReactDOM?: typeof import('react-dom');
|
|
34
|
+
}
|
|
35
|
+
export interface InjectionContext {
|
|
36
|
+
remoteName: string;
|
|
37
|
+
moduleName: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ComponentWrapper {
|
|
40
|
+
(WrappedComponent: React.ComponentType<any>, context: WrapperContext): React.ComponentType<any>;
|
|
41
|
+
}
|
|
42
|
+
export interface PropInjector {
|
|
43
|
+
(props: Record<string, any>, context: InjectionContext): Record<string, any>;
|
|
44
|
+
}
|
|
45
|
+
export interface EnhancedRuntimePlugin {
|
|
46
|
+
name: string;
|
|
47
|
+
wrapComponent?: ComponentWrapper;
|
|
48
|
+
injectProps?: PropInjector;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Runtime Plugin 包装器对象的接口
|
|
52
|
+
* 包含原始组件和运行时信息,让 RemoteModule 控制 HOC 层级顺序
|
|
53
|
+
*/
|
|
54
|
+
export interface RuntimePluginWrapper {
|
|
55
|
+
readonly type: 'runtime-plugin-wrapper';
|
|
56
|
+
readonly originalComponent: React.ComponentType<any>;
|
|
57
|
+
readonly runtimeInfo: {
|
|
58
|
+
readonly remoteVersion?: string;
|
|
59
|
+
readonly hostVersion?: string;
|
|
60
|
+
readonly remoteReact: () => typeof import('react');
|
|
61
|
+
readonly remoteReactDOM: () => typeof import('react-dom');
|
|
62
|
+
readonly mountNode?: HTMLElement;
|
|
63
|
+
readonly containerClassName?: string;
|
|
64
|
+
};
|
|
65
|
+
readonly reactInstances: {
|
|
66
|
+
readonly react: typeof import('react');
|
|
67
|
+
readonly reactDOM?: typeof import('react-dom');
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 类型守卫:检查是否为 Runtime Plugin 包装器
|
|
72
|
+
*/
|
|
73
|
+
export declare function isRuntimePluginWrapper(value: any): value is () => RuntimePluginWrapper;
|
|
74
|
+
/**
|
|
75
|
+
* 创建 Runtime Plugin 包装器的工厂函数
|
|
76
|
+
*/
|
|
77
|
+
export declare function createRuntimePluginWrapper(originalComponent: React.ComponentType<any>, runtimeInfo: {
|
|
78
|
+
remoteVersion?: string;
|
|
79
|
+
hostVersion?: string;
|
|
80
|
+
remoteReact: () => typeof import('react');
|
|
81
|
+
remoteReactDOM: () => typeof import('react-dom');
|
|
82
|
+
mountNode?: HTMLElement;
|
|
83
|
+
containerClassName?: string;
|
|
84
|
+
}, reactInstances: {
|
|
85
|
+
react: typeof import('react');
|
|
86
|
+
reactDOM?: typeof import('react-dom');
|
|
87
|
+
}): () => RuntimePluginWrapper;
|
package/es2017/types.js
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* 类型守卫:检查是否为 Runtime Plugin 包装器
|
|
4
|
+
*/ export function isRuntimePluginWrapper(value) {
|
|
5
|
+
return value && value.__mf_fallback_render;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 创建 Runtime Plugin 包装器的工厂函数
|
|
9
|
+
*/ export function createRuntimePluginWrapper(originalComponent, runtimeInfo, reactInstances) {
|
|
10
|
+
const wrapper = ()=>({
|
|
11
|
+
type: 'runtime-plugin-wrapper',
|
|
12
|
+
originalComponent,
|
|
13
|
+
runtimeInfo,
|
|
14
|
+
reactInstances
|
|
15
|
+
});
|
|
16
|
+
wrapper.__mf_fallback_render = true;
|
|
17
|
+
return wrapper;
|
|
18
|
+
} // 不再需要联合类型,两种插件分开管理
|
package/esm/RemoteModule.js
CHANGED
|
@@ -6,10 +6,13 @@ import { _ as _ts_generator } from "@swc/helpers/_/_ts_generator";
|
|
|
6
6
|
import { loadRemote } from "@module-federation/runtime";
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
import { useMemo, useState, useLayoutEffect, forwardRef } from "react";
|
|
9
|
+
import * as ReactDOM from "react-dom";
|
|
9
10
|
import { ErrorBoundary } from "react-error-boundary";
|
|
10
11
|
import { FallBack } from "./FallBack";
|
|
11
12
|
import { setFederatedModulePublicPath } from "./set-public-path";
|
|
12
13
|
import { getMicroMod } from "./mf-global-store";
|
|
14
|
+
import { useEnhancedPluginManager } from "./plugin-manager";
|
|
15
|
+
import { isRuntimePluginWrapper } from "./types";
|
|
13
16
|
var useMountNode = function(mountNodeConfig) {
|
|
14
17
|
var _useState = _sliced_to_array(useState(undefined), 2), resolvedNode = _useState[0], setResolvedNode = _useState[1];
|
|
15
18
|
// 解析各种类型的 mountNode
|
|
@@ -40,6 +43,7 @@ var RemoteModuleInner = function(param, ref) {
|
|
|
40
43
|
var _microMod, _runtime, _runtime1;
|
|
41
44
|
var microMod = getMicroMod(scope);
|
|
42
45
|
var resolvedMountNode = useMountNode(mountNode);
|
|
46
|
+
var enhancedPluginManager = useEnhancedPluginManager();
|
|
43
47
|
if ((_microMod = microMod) === null || _microMod === void 0 ? void 0 : _microMod.publicPath) {
|
|
44
48
|
setFederatedModulePublicPath(microMod.moduleFederatedName, microMod.publicPath);
|
|
45
49
|
}
|
|
@@ -49,7 +53,7 @@ var RemoteModuleInner = function(param, ref) {
|
|
|
49
53
|
var Component = useMemo(function() {
|
|
50
54
|
if (!module || !scope) return null;
|
|
51
55
|
return /*#__PURE__*/ React.lazy(/*#__PURE__*/ _async_to_generator(function() {
|
|
52
|
-
var _typedRemoteModule, remoteModule,
|
|
56
|
+
var _typedRemoteModule, remoteModule, _remoteModule, originalComponent, runtimeInfo, reactInstances, remoteReact, remoteReactDOM, PluginWrappedComponent, FinalComponent, typedRemoteModule, BaseComponent, FinalComponent1, react, reactDOM, PluginWrappedComponent1;
|
|
53
57
|
return _ts_generator(this, function(_state) {
|
|
54
58
|
switch(_state.label){
|
|
55
59
|
case 0:
|
|
@@ -59,48 +63,80 @@ var RemoteModuleInner = function(param, ref) {
|
|
|
59
63
|
];
|
|
60
64
|
case 1:
|
|
61
65
|
remoteModule = _state.sent();
|
|
62
|
-
//
|
|
63
|
-
if (
|
|
64
|
-
|
|
66
|
+
// 使用清晰的对象结构检查是否为 Runtime Plugin 包装器
|
|
67
|
+
if (isRuntimePluginWrapper(remoteModule)) {
|
|
68
|
+
_remoteModule = remoteModule(), originalComponent = _remoteModule.originalComponent, runtimeInfo = _remoteModule.runtimeInfo, reactInstances = _remoteModule.reactInstances;
|
|
69
|
+
remoteReact = reactInstances.react;
|
|
70
|
+
remoteReactDOM = reactInstances.reactDOM;
|
|
71
|
+
PluginWrappedComponent = enhancedPluginManager.wrapComponent(originalComponent, {
|
|
72
|
+
remoteName: scope,
|
|
73
|
+
moduleName: module,
|
|
74
|
+
props: componentProps || {},
|
|
75
|
+
React: remoteReact,
|
|
76
|
+
ReactDOM: remoteReactDOM
|
|
77
|
+
});
|
|
78
|
+
FinalComponent = FallBack({
|
|
79
|
+
Original: PluginWrappedComponent,
|
|
80
|
+
remoteVersion: runtimeInfo.remoteVersion ? function() {
|
|
81
|
+
return runtimeInfo.remoteVersion;
|
|
82
|
+
} : undefined,
|
|
83
|
+
hostVersion: runtimeInfo.hostVersion ? function() {
|
|
84
|
+
return runtimeInfo.hostVersion;
|
|
85
|
+
} : undefined,
|
|
86
|
+
remoteReactDOM: runtimeInfo.remoteReactDOM,
|
|
87
|
+
remoteReact: runtimeInfo.remoteReact,
|
|
88
|
+
mountNode: resolvedMountNode,
|
|
89
|
+
containerClassName: fallbackContainerClassName
|
|
90
|
+
});
|
|
65
91
|
return [
|
|
66
92
|
2,
|
|
67
93
|
{
|
|
68
|
-
default:
|
|
94
|
+
default: FinalComponent
|
|
69
95
|
}
|
|
70
96
|
];
|
|
71
97
|
}
|
|
72
98
|
typedRemoteModule = remoteModule;
|
|
73
99
|
if (!((_typedRemoteModule = typedRemoteModule) === null || _typedRemoteModule === void 0 ? void 0 : _typedRemoteModule.default)) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
default: remoteModule
|
|
78
|
-
}
|
|
79
|
-
];
|
|
100
|
+
BaseComponent = remoteModule;
|
|
101
|
+
} else {
|
|
102
|
+
BaseComponent = typedRemoteModule.default;
|
|
80
103
|
}
|
|
104
|
+
// 如果需要 runtime 处理(不同 React 版本或 mountNode)
|
|
81
105
|
if (runtime) {
|
|
82
106
|
react = runtime.react, reactDOM = runtime.reactDOM;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
107
|
+
PluginWrappedComponent1 = enhancedPluginManager.wrapComponent(BaseComponent, {
|
|
108
|
+
remoteName: scope,
|
|
109
|
+
moduleName: module,
|
|
110
|
+
props: componentProps || {},
|
|
111
|
+
React: react,
|
|
112
|
+
ReactDOM: reactDOM
|
|
113
|
+
});
|
|
114
|
+
FinalComponent1 = FallBack({
|
|
115
|
+
Original: PluginWrappedComponent1,
|
|
116
|
+
remoteReact: function() {
|
|
117
|
+
return react;
|
|
118
|
+
},
|
|
119
|
+
remoteReactDOM: function() {
|
|
120
|
+
return reactDOM;
|
|
121
|
+
},
|
|
122
|
+
mountNode: resolvedMountNode,
|
|
123
|
+
containerClassName: fallbackContainerClassName
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
// 没有 runtime 需求时,直接应用插件包装器
|
|
127
|
+
FinalComponent1 = enhancedPluginManager.wrapComponent(BaseComponent, {
|
|
128
|
+
remoteName: scope,
|
|
129
|
+
moduleName: module,
|
|
130
|
+
props: componentProps || {},
|
|
131
|
+
React: React,
|
|
132
|
+
ReactDOM: ReactDOM
|
|
133
|
+
});
|
|
99
134
|
}
|
|
135
|
+
console.log("FinalComponent", FinalComponent1);
|
|
100
136
|
return [
|
|
101
137
|
2,
|
|
102
138
|
{
|
|
103
|
-
default:
|
|
139
|
+
default: FinalComponent1
|
|
104
140
|
}
|
|
105
141
|
];
|
|
106
142
|
}
|
|
@@ -112,7 +148,9 @@ var RemoteModuleInner = function(param, ref) {
|
|
|
112
148
|
(_runtime = runtime) === null || _runtime === void 0 ? void 0 : _runtime.react,
|
|
113
149
|
(_runtime1 = runtime) === null || _runtime1 === void 0 ? void 0 : _runtime1.reactDOM,
|
|
114
150
|
resolvedMountNode,
|
|
115
|
-
fallbackContainerClassName
|
|
151
|
+
fallbackContainerClassName,
|
|
152
|
+
enhancedPluginManager,
|
|
153
|
+
componentProps
|
|
116
154
|
]);
|
|
117
155
|
var Loading = LoadingComponent || /*#__PURE__*/ React.createElement("div", null, "Loading...");
|
|
118
156
|
var ErrorFallback = function(param) {
|
|
@@ -120,6 +158,11 @@ var RemoteModuleInner = function(param, ref) {
|
|
|
120
158
|
return ErrorComponent || /*#__PURE__*/ React.createElement("div", null, "远程模块加载失败: ", error.message);
|
|
121
159
|
};
|
|
122
160
|
if (!Component) return Loading;
|
|
161
|
+
// 应用增强插件的属性注入
|
|
162
|
+
var injectedProps = enhancedPluginManager.injectProps(componentProps || {}, {
|
|
163
|
+
remoteName: scope,
|
|
164
|
+
moduleName: module
|
|
165
|
+
});
|
|
123
166
|
return /*#__PURE__*/ React.createElement(ErrorBoundary, {
|
|
124
167
|
resetKeys: [
|
|
125
168
|
module,
|
|
@@ -132,7 +175,7 @@ var RemoteModuleInner = function(param, ref) {
|
|
|
132
175
|
fallback: Loading
|
|
133
176
|
}, /*#__PURE__*/ React.createElement(Component, _object_spread({
|
|
134
177
|
ref: ref
|
|
135
|
-
},
|
|
178
|
+
}, injectedProps), children)));
|
|
136
179
|
};
|
|
137
180
|
// 使用 forwardRef 包装组件以支持 ref
|
|
138
181
|
export var RemoteModule = /*#__PURE__*/ forwardRef(RemoteModuleInner);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { _ as _object_spread } from "@swc/helpers/_/_object_spread";
|
|
2
|
+
import { _ as _object_spread_props } from "@swc/helpers/_/_object_spread_props";
|
|
3
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
4
|
+
import { EnhancedPluginManager, getEnhancedPluginManager, registerEnhancedPlugins } from "../plugin-manager";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
describe("EnhancedPluginManager", function() {
|
|
7
|
+
var manager;
|
|
8
|
+
beforeEach(function() {
|
|
9
|
+
manager = new EnhancedPluginManager();
|
|
10
|
+
});
|
|
11
|
+
describe("Plugin Registration", function() {
|
|
12
|
+
it("should register an enhanced plugin", function() {
|
|
13
|
+
var enhancedPlugin = {
|
|
14
|
+
name: "test-enhanced",
|
|
15
|
+
wrapComponent: function(Component) {
|
|
16
|
+
return Component;
|
|
17
|
+
},
|
|
18
|
+
injectProps: function(props) {
|
|
19
|
+
return props;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
manager.register(enhancedPlugin);
|
|
23
|
+
var plugins = manager.getPlugins();
|
|
24
|
+
expect(plugins).toHaveLength(1);
|
|
25
|
+
expect(plugins[0].name).toBe("test-enhanced");
|
|
26
|
+
});
|
|
27
|
+
it("should replace existing plugin with same name", function() {
|
|
28
|
+
var plugin1 = {
|
|
29
|
+
name: "test-plugin",
|
|
30
|
+
wrapComponent: function(Component) {
|
|
31
|
+
return Component;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var plugin2 = {
|
|
35
|
+
name: "test-plugin",
|
|
36
|
+
injectProps: function(props) {
|
|
37
|
+
return props;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
manager.register(plugin1);
|
|
41
|
+
manager.register(plugin2);
|
|
42
|
+
var plugins = manager.getPlugins();
|
|
43
|
+
expect(plugins).toHaveLength(1);
|
|
44
|
+
expect(plugins[0]).toBe(plugin2);
|
|
45
|
+
});
|
|
46
|
+
it("should register multiple plugins", function() {
|
|
47
|
+
var plugins = [
|
|
48
|
+
{
|
|
49
|
+
name: "plugin1",
|
|
50
|
+
wrapComponent: function(Component) {
|
|
51
|
+
return Component;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "plugin2",
|
|
56
|
+
injectProps: function(props) {
|
|
57
|
+
return props;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
manager.registerPlugins(plugins);
|
|
62
|
+
expect(manager.getPlugins()).toHaveLength(2);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("Component Wrapping", function() {
|
|
66
|
+
it("should wrap component with single plugin", function() {
|
|
67
|
+
var TestComponent = function() {
|
|
68
|
+
return React.createElement("div", null, "test");
|
|
69
|
+
};
|
|
70
|
+
var mockWrapper = vi.fn(function(Component) {
|
|
71
|
+
return Component;
|
|
72
|
+
});
|
|
73
|
+
var plugin = {
|
|
74
|
+
name: "wrapper-plugin",
|
|
75
|
+
wrapComponent: mockWrapper
|
|
76
|
+
};
|
|
77
|
+
manager.register(plugin);
|
|
78
|
+
var context = {
|
|
79
|
+
remoteName: "test-remote",
|
|
80
|
+
moduleName: "test-module",
|
|
81
|
+
props: {
|
|
82
|
+
test: true
|
|
83
|
+
},
|
|
84
|
+
React: React,
|
|
85
|
+
ReactDOM: undefined
|
|
86
|
+
};
|
|
87
|
+
var wrappedComponent = manager.wrapComponent(TestComponent, context);
|
|
88
|
+
expect(mockWrapper).toHaveBeenCalledWith(TestComponent, context);
|
|
89
|
+
expect(wrappedComponent).toBe(TestComponent);
|
|
90
|
+
});
|
|
91
|
+
it("should apply multiple wrappers in order", function() {
|
|
92
|
+
var TestComponent = function() {
|
|
93
|
+
return React.createElement("div", null, "original");
|
|
94
|
+
};
|
|
95
|
+
var Wrapper1 = function() {
|
|
96
|
+
return React.createElement("div", null, "wrapper1");
|
|
97
|
+
};
|
|
98
|
+
var Wrapper2 = function() {
|
|
99
|
+
return React.createElement("div", null, "wrapper2");
|
|
100
|
+
};
|
|
101
|
+
var plugin1 = {
|
|
102
|
+
name: "wrapper1",
|
|
103
|
+
wrapComponent: function() {
|
|
104
|
+
return Wrapper1;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var plugin2 = {
|
|
108
|
+
name: "wrapper2",
|
|
109
|
+
wrapComponent: function() {
|
|
110
|
+
return Wrapper2;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
// 注册顺序很重要
|
|
114
|
+
manager.register(plugin1);
|
|
115
|
+
manager.register(plugin2);
|
|
116
|
+
var context = {
|
|
117
|
+
remoteName: "test-remote",
|
|
118
|
+
moduleName: "test-module",
|
|
119
|
+
props: {},
|
|
120
|
+
React: React,
|
|
121
|
+
ReactDOM: undefined
|
|
122
|
+
};
|
|
123
|
+
var result = manager.wrapComponent(TestComponent, context);
|
|
124
|
+
// plugin2 应该最后执行,所以返回 Wrapper2
|
|
125
|
+
expect(result).toBe(Wrapper2);
|
|
126
|
+
});
|
|
127
|
+
it("should handle wrapper errors gracefully", function() {
|
|
128
|
+
var TestComponent = function() {
|
|
129
|
+
return React.createElement("div", null, "test");
|
|
130
|
+
};
|
|
131
|
+
var consoleSpy = vi.spyOn(console, "error").mockImplementation(function() {});
|
|
132
|
+
var plugin = {
|
|
133
|
+
name: "error-plugin",
|
|
134
|
+
wrapComponent: function() {
|
|
135
|
+
throw new Error("Wrapper error");
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
manager.register(plugin);
|
|
139
|
+
var context = {
|
|
140
|
+
remoteName: "test-remote",
|
|
141
|
+
moduleName: "test-module",
|
|
142
|
+
props: {},
|
|
143
|
+
React: React,
|
|
144
|
+
ReactDOM: undefined
|
|
145
|
+
};
|
|
146
|
+
var result = manager.wrapComponent(TestComponent, context);
|
|
147
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Error applying wrapper from plugin "error-plugin"'), expect.any(Error));
|
|
148
|
+
expect(result).toBe(TestComponent); // 应该返回原始组件
|
|
149
|
+
consoleSpy.mockRestore();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe("Props Injection", function() {
|
|
153
|
+
it("should inject props with single plugin", function() {
|
|
154
|
+
var mockInjector = vi.fn(function(props) {
|
|
155
|
+
return _object_spread_props(_object_spread({}, props), {
|
|
156
|
+
injected: true
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
var plugin = {
|
|
160
|
+
name: "injector-plugin",
|
|
161
|
+
injectProps: mockInjector
|
|
162
|
+
};
|
|
163
|
+
manager.register(plugin);
|
|
164
|
+
var context = {
|
|
165
|
+
remoteName: "test-remote",
|
|
166
|
+
moduleName: "test-module"
|
|
167
|
+
};
|
|
168
|
+
var result = manager.injectProps({
|
|
169
|
+
original: true
|
|
170
|
+
}, context);
|
|
171
|
+
expect(mockInjector).toHaveBeenCalledWith({
|
|
172
|
+
original: true
|
|
173
|
+
}, context);
|
|
174
|
+
expect(result).toEqual({
|
|
175
|
+
original: true,
|
|
176
|
+
injected: true
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
it("should apply multiple injectors in order", function() {
|
|
180
|
+
var plugin1 = {
|
|
181
|
+
name: "injector1",
|
|
182
|
+
injectProps: function(props) {
|
|
183
|
+
return _object_spread_props(_object_spread({}, props), {
|
|
184
|
+
step1: true
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
var plugin2 = {
|
|
189
|
+
name: "injector2",
|
|
190
|
+
injectProps: function(props) {
|
|
191
|
+
return _object_spread_props(_object_spread({}, props), {
|
|
192
|
+
step2: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
manager.register(plugin1);
|
|
197
|
+
manager.register(plugin2);
|
|
198
|
+
var context = {
|
|
199
|
+
remoteName: "test-remote",
|
|
200
|
+
moduleName: "test-module"
|
|
201
|
+
};
|
|
202
|
+
var result = manager.injectProps({
|
|
203
|
+
original: true
|
|
204
|
+
}, context);
|
|
205
|
+
expect(result).toEqual({
|
|
206
|
+
original: true,
|
|
207
|
+
step1: true,
|
|
208
|
+
step2: true
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
it("should handle injection errors gracefully", function() {
|
|
212
|
+
var consoleSpy = vi.spyOn(console, "error").mockImplementation(function() {});
|
|
213
|
+
var plugin = {
|
|
214
|
+
name: "error-plugin",
|
|
215
|
+
injectProps: function() {
|
|
216
|
+
throw new Error("Injection error");
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
manager.register(plugin);
|
|
220
|
+
var context = {
|
|
221
|
+
remoteName: "test-remote",
|
|
222
|
+
moduleName: "test-module"
|
|
223
|
+
};
|
|
224
|
+
var result = manager.injectProps({
|
|
225
|
+
original: true
|
|
226
|
+
}, context);
|
|
227
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Error injecting props from plugin "error-plugin"'), expect.any(Error));
|
|
228
|
+
expect(result).toEqual({
|
|
229
|
+
original: true
|
|
230
|
+
}); // 应该返回原始props
|
|
231
|
+
consoleSpy.mockRestore();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe("Plugin Management", function() {
|
|
235
|
+
it("should get plugin info", function() {
|
|
236
|
+
var wrapperPlugin = {
|
|
237
|
+
name: "wrapper-plugin",
|
|
238
|
+
wrapComponent: function(Component) {
|
|
239
|
+
return Component;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var injectorPlugin = {
|
|
243
|
+
name: "injector-plugin",
|
|
244
|
+
injectProps: function(props) {
|
|
245
|
+
return props;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
var hybridPlugin = {
|
|
249
|
+
name: "hybrid-plugin",
|
|
250
|
+
wrapComponent: function(Component) {
|
|
251
|
+
return Component;
|
|
252
|
+
},
|
|
253
|
+
injectProps: function(props) {
|
|
254
|
+
return props;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
manager.register(wrapperPlugin);
|
|
258
|
+
manager.register(injectorPlugin);
|
|
259
|
+
manager.register(hybridPlugin);
|
|
260
|
+
var info = manager.getPluginInfo();
|
|
261
|
+
expect(info).toHaveLength(3);
|
|
262
|
+
expect(info[0]).toEqual({
|
|
263
|
+
name: "wrapper-plugin",
|
|
264
|
+
hasWrapper: true,
|
|
265
|
+
hasInjector: false
|
|
266
|
+
});
|
|
267
|
+
expect(info[1]).toEqual({
|
|
268
|
+
name: "injector-plugin",
|
|
269
|
+
hasWrapper: false,
|
|
270
|
+
hasInjector: true
|
|
271
|
+
});
|
|
272
|
+
expect(info[2]).toEqual({
|
|
273
|
+
name: "hybrid-plugin",
|
|
274
|
+
hasWrapper: true,
|
|
275
|
+
hasInjector: true
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
it("should remove plugin", function() {
|
|
279
|
+
var plugin = {
|
|
280
|
+
name: "test-plugin",
|
|
281
|
+
wrapComponent: function(Component) {
|
|
282
|
+
return Component;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
manager.register(plugin);
|
|
286
|
+
expect(manager.getPlugins()).toHaveLength(1);
|
|
287
|
+
var removed = manager.removePlugin("test-plugin");
|
|
288
|
+
expect(removed).toBe(true);
|
|
289
|
+
expect(manager.getPlugins()).toHaveLength(0);
|
|
290
|
+
});
|
|
291
|
+
it("should return false when removing non-existent plugin", function() {
|
|
292
|
+
var removed = manager.removePlugin("non-existent");
|
|
293
|
+
expect(removed).toBe(false);
|
|
294
|
+
});
|
|
295
|
+
it("should clear all plugins", function() {
|
|
296
|
+
manager.register({
|
|
297
|
+
name: "plugin1",
|
|
298
|
+
wrapComponent: function(Component) {
|
|
299
|
+
return Component;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
manager.register({
|
|
303
|
+
name: "plugin2",
|
|
304
|
+
injectProps: function(props) {
|
|
305
|
+
return props;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
expect(manager.getPlugins()).toHaveLength(2);
|
|
309
|
+
manager.clear();
|
|
310
|
+
expect(manager.getPlugins()).toHaveLength(0);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
describe("Global Enhanced Plugin Manager", function() {
|
|
314
|
+
beforeEach(function() {
|
|
315
|
+
// 清理全局状态
|
|
316
|
+
getEnhancedPluginManager().clear();
|
|
317
|
+
});
|
|
318
|
+
it("should provide singleton instance", function() {
|
|
319
|
+
var manager1 = getEnhancedPluginManager();
|
|
320
|
+
var manager2 = getEnhancedPluginManager();
|
|
321
|
+
expect(manager1).toBe(manager2);
|
|
322
|
+
});
|
|
323
|
+
it("should register multiple plugins via convenience function", function() {
|
|
324
|
+
var plugins = [
|
|
325
|
+
{
|
|
326
|
+
name: "plugin1",
|
|
327
|
+
wrapComponent: function(Component) {
|
|
328
|
+
return Component;
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "plugin2",
|
|
333
|
+
injectProps: function(props) {
|
|
334
|
+
return props;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
];
|
|
338
|
+
registerEnhancedPlugins(plugins);
|
|
339
|
+
var manager = getEnhancedPluginManager();
|
|
340
|
+
expect(manager.getPlugins()).toHaveLength(2);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { _ as _to_consumable_array } from "@swc/helpers/_/_to_consumable_array";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
// Test setup for ice-mf-runtime
|
|
4
|
+
// Mock @ice/stark-app dependency
|
|
5
|
+
vi.mock("@ice/stark-app", function() {
|
|
6
|
+
return {
|
|
7
|
+
getBasename: function() {
|
|
8
|
+
return "/app";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
// Mock @module-federation/runtime
|
|
13
|
+
vi.mock("@module-federation/runtime", function() {
|
|
14
|
+
return {
|
|
15
|
+
init: vi.fn(),
|
|
16
|
+
registerPlugins: vi.fn(),
|
|
17
|
+
loadRemote: vi.fn()
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
// Mock performance API
|
|
21
|
+
Object.defineProperty(globalThis, "performance", {
|
|
22
|
+
value: {
|
|
23
|
+
now: function() {
|
|
24
|
+
return Date.now();
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
writable: true
|
|
28
|
+
});
|
|
29
|
+
// Suppress console warnings during tests
|
|
30
|
+
var originalWarn = console.warn;
|
|
31
|
+
console.warn = function() {
|
|
32
|
+
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
33
|
+
args[_key] = arguments[_key];
|
|
34
|
+
}
|
|
35
|
+
var _originalWarn;
|
|
36
|
+
var _args__includes, _args_;
|
|
37
|
+
if ((_args_ = args[0]) === null || _args_ === void 0 ? void 0 : (_args__includes = _args_.includes) === null || _args__includes === void 0 ? void 0 : _args__includes.call(_args_, "Warning: ReactDOM.render is no longer supported")) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
(_originalWarn = originalWarn).call.apply(_originalWarn, [
|
|
41
|
+
console
|
|
42
|
+
].concat(_to_consumable_array(args)));
|
|
43
|
+
};
|
package/esm/index.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ import type { ExtendedUserOptions, MicroMod } from './types';
|
|
|
2
2
|
export { loadRemote, registerPlugins } from '@module-federation/runtime';
|
|
3
3
|
export * from './FallBack';
|
|
4
4
|
export * from './RemoteModule';
|
|
5
|
+
export * from './types';
|
|
6
|
+
export * from './plugin-manager';
|
|
5
7
|
export declare function init(options: ExtendedUserOptions, microMods?: MicroMod[]): void;
|
|
6
8
|
export declare function initByMicroMods(microMods: MicroMod[], hostName?: string): void;
|