@ice/mf-runtime 1.0.1-beta.2 → 1.0.2-beta.1

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 CHANGED
@@ -1,12 +1,18 @@
1
1
  # @ice/mf-runtime
2
2
 
3
- 基于 Module Federation 的运行时工具,用于在 ice.js 应用中加载和管理远程模块。支持跨版本 React 组件加载
3
+ 基于 Module Federation 的运行时工具,用于在 ice.js 应用中加载和管理远程模块。支持跨版本 React 组件加载,并提供增强的插件机制。
4
4
 
5
5
  ## 特性
6
6
 
7
7
  - 基于 [Module Federation 2.0](https://module-federation.io/index.html)
8
8
  - 支持跨版本 React 组件加载
9
9
  - 内置冲突检测和降级渲染
10
+ - 🔌 **增强插件机制**: 完全兼容标准 MF 运行时插件
11
+ - 🎯 **组件增强**: 支持 HOC、Props 注入等组件扩展机制
12
+ - 🛡️ **错误边界**: 内置错误处理和恢复机制
13
+ - 📊 **性能监控**: 支持组件加载和渲染性能追踪
14
+ - 🔄 **热插拔**: 运行时动态注册和移除插件
15
+ - 📝 **TypeScript**: 完整的类型定义支持
10
16
 
11
17
  ## 安装
12
18
 
@@ -14,6 +20,220 @@
14
20
  $ npm i @ice/mf-runtime --save
15
21
  ```
16
22
 
23
+ ## 插件机制
24
+
25
+ 从 v1.0.1 开始,`@ice/mf-runtime` 支持强大的插件机制,允许在运行时动态扩展微前端功能。
26
+
27
+ ### 插件类型
28
+
29
+ #### 1. 标准 MF 插件 (FederationRuntimePlugin)
30
+ 标准 MF 插件使用原生的 `@module-federation/runtime` API 进行管理:
31
+
32
+ ```typescript
33
+ import { registerPlugins } from '@module-federation/runtime';
34
+ import type { FederationRuntimePlugin } from '@ice/mf-runtime';
35
+
36
+ const mfPlugin: FederationRuntimePlugin = {
37
+ name: 'my-mf-plugin',
38
+ beforeRequest: (args) => {
39
+ console.log('模块请求前:', args);
40
+ return args;
41
+ },
42
+ onLoad: (args) => {
43
+ console.log('模块加载后:', args);
44
+ return args;
45
+ },
46
+ afterResolve: (args) => {
47
+ console.log('模块解析后:', args);
48
+ return args;
49
+ }
50
+ };
51
+
52
+ // 使用原生 API 注册标准 MF 插件
53
+ registerPlugins([mfPlugin]);
54
+ ```
55
+
56
+ #### 2. 增强插件 (EnhancedRuntimePlugin)
57
+ 增强插件使用新的 API 进行管理,提供组件级别的扩展能力:
58
+
59
+ ```typescript
60
+ import { registerEnhancedPlugins } from '@ice/mf-runtime';
61
+ import type { EnhancedRuntimePlugin } from '@ice/mf-runtime';
62
+
63
+ const enhancedPlugin: EnhancedRuntimePlugin = {
64
+ name: 'my-enhanced-plugin',
65
+
66
+ // 组件包装器 - 支持 HOC 模式
67
+ wrapComponent: (WrappedComponent, context) => {
68
+ return function Enhanced(props) {
69
+ console.log(`渲染组件: ${context.remoteName}/${context.moduleName}`);
70
+ return <WrappedComponent {...props} />;
71
+ };
72
+ },
73
+
74
+ // 属性注入器
75
+ injectProps: (props, context) => {
76
+ return {
77
+ ...props,
78
+ injectedProp: `来自 ${context.remoteName} 的数据`
79
+ };
80
+ }
81
+ };
82
+
83
+ // 使用新的 API 注册增强插件
84
+ registerEnhancedPlugins([enhancedPlugin]);
85
+ ```
86
+
87
+ ### 注册和使用插件
88
+
89
+ #### 基本用法
90
+
91
+ ```typescript
92
+ import { init, registerEnhancedPlugins, RemoteModule } from '@ice/mf-runtime';
93
+ import { registerPlugins } from '@module-federation/runtime';
94
+
95
+ // 注册标准 MF 插件
96
+ registerPlugins([mfPlugin]);
97
+
98
+ // 注册增强插件
99
+ registerEnhancedPlugins([enhancedPlugin]);
100
+
101
+ // 初始化运行时
102
+ init({
103
+ remotes: [
104
+ {
105
+ name: 'remote-app',
106
+ entry: 'http://localhost:3001/remoteEntry.js'
107
+ }
108
+ ]
109
+ });
110
+
111
+ // 使用远程模块(插件会自动应用)
112
+ function App() {
113
+ return (
114
+ <RemoteModule
115
+ scope="remote-app"
116
+ module="Button"
117
+ componentProps={{ text: '点击我' }}
118
+ />
119
+ );
120
+ }
121
+ ```
122
+
123
+ #### 使用示例插件包
124
+
125
+ 我们提供了一个示例插件包 `@ice/mf-plugin-example`,包含常用的插件:
126
+
127
+ ```bash
128
+ npm install @ice/mf-plugin-example
129
+ ```
130
+
131
+ ```typescript
132
+ import { registerPlugins } from '@module-federation/runtime';
133
+ import { registerEnhancedPlugins } from '@ice/mf-runtime';
134
+ import {
135
+ errorBoundaryPlugin,
136
+ commonPropsPlugin,
137
+ developmentMFLoggingPlugin,
138
+ developmentEnhancedLoggingPlugin
139
+ } from '@ice/mf-plugin-example';
140
+
141
+ // 注册标准 MF 插件
142
+ registerPlugins([
143
+ developmentMFLoggingPlugin // MF 日志插件
144
+ ]);
145
+
146
+ // 注册增强插件
147
+ registerEnhancedPlugins([
148
+ developmentEnhancedLoggingPlugin, // 增强日志插件
149
+ errorBoundaryPlugin, // 错误边界插件
150
+ commonPropsPlugin // 通用属性注入插件
151
+ ]);
152
+ ```
153
+
154
+ ### 插件开发指南
155
+
156
+ #### 错误边界插件示例
157
+
158
+ ```typescript
159
+ import { ErrorBoundary } from 'react-error-boundary';
160
+
161
+ const errorBoundaryPlugin: EnhancedRuntimePlugin = {
162
+ name: 'error-boundary',
163
+ wrapComponent: (WrappedComponent, context) => {
164
+ return function ErrorBoundaryWrapper(props) {
165
+ return (
166
+ <ErrorBoundary
167
+ fallback={<div>组件 {context.remoteName} 加载失败</div>}
168
+ onError={(error) => {
169
+ console.error(`组件错误:`, error);
170
+ }}
171
+ >
172
+ <WrappedComponent {...props} />
173
+ </ErrorBoundary>
174
+ );
175
+ };
176
+ }
177
+ };
178
+ ```
179
+
180
+ #### 属性注入插件示例
181
+
182
+ ```typescript
183
+ const themePlugin: EnhancedRuntimePlugin = {
184
+ name: 'theme-injector',
185
+ injectProps: (props, context) => {
186
+ return {
187
+ ...props,
188
+ theme: {
189
+ mode: 'light',
190
+ primaryColor: '#1890ff'
191
+ }
192
+ };
193
+ }
194
+ };
195
+ ```
196
+
197
+ ### 插件 API
198
+
199
+ #### EnhancedPluginManager
200
+
201
+ ```typescript
202
+ import { getEnhancedPluginManager } from '@ice/mf-runtime';
203
+
204
+ const manager = getEnhancedPluginManager();
205
+
206
+ // 注册增强插件
207
+ manager.register(enhancedPlugin);
208
+
209
+ // 获取插件信息
210
+ const info = manager.getPluginInfo();
211
+ // 返回: [{ name: 'plugin-name', hasWrapper: true, hasInjector: false }]
212
+
213
+ // 移除插件
214
+ manager.removePlugin('plugin-name');
215
+
216
+ // 清空所有插件
217
+ manager.clear();
218
+ ```
219
+
220
+ #### React Hook
221
+
222
+ ```typescript
223
+ import { useEnhancedPluginManager } from '@ice/mf-runtime';
224
+
225
+ function MyComponent() {
226
+ const enhancedPluginManager = useEnhancedPluginManager();
227
+
228
+ // 动态操作插件
229
+ const handleAddPlugin = () => {
230
+ enhancedPluginManager.register(newEnhancedPlugin);
231
+ };
232
+
233
+ return <div>...</div>;
234
+ }
235
+ ```
236
+
17
237
  ## 使用
18
238
 
19
239
  ### 1. 初始化配置
@@ -6,6 +6,7 @@ import { ErrorBoundary } from 'react-error-boundary';
6
6
  import { FallBack } from './FallBack';
7
7
  import { setFederatedModulePublicPath } from './set-public-path';
8
8
  import { getMicroMod } from './mf-global-store';
9
+ import { useEnhancedPluginManager } from './plugin-manager';
9
10
  const useMountNode = (mountNodeConfig)=>{
10
11
  const [resolvedNode, setResolvedNode] = useState(undefined);
11
12
  // 解析各种类型的 mountNode
@@ -35,6 +36,7 @@ const RemoteModuleInner = ({ module, scope, runtime, publicPath, LoadingComponen
35
36
  var _microMod, _runtime, _runtime1;
36
37
  const microMod = getMicroMod(scope);
37
38
  const resolvedMountNode = useMountNode(mountNode);
39
+ const enhancedPluginManager = useEnhancedPluginManager();
38
40
  if ((_microMod = microMod) === null || _microMod === void 0 ? void 0 : _microMod.publicPath) {
39
41
  setFederatedModulePublicPath(microMod.moduleFederatedName, microMod.publicPath);
40
42
  }
@@ -45,6 +47,7 @@ const RemoteModuleInner = ({ module, scope, runtime, publicPath, LoadingComponen
45
47
  if (!module || !scope) return null;
46
48
  return /*#__PURE__*/ React.lazy(async ()=>{
47
49
  var _typedRemoteModule;
50
+ // 使用标准 MF 加载逻辑,MF 插件自动执行
48
51
  const remoteModule = await loadRemote(`${scope}/${module}`);
49
52
  // 检查是否是来自 runtime plugin 的包装函数(通过标记识别)
50
53
  if (typeof remoteModule === 'function' && remoteModule.__ICE_MF_RUNTIME_WRAPPER__) {
@@ -55,25 +58,34 @@ const RemoteModuleInner = ({ module, scope, runtime, publicPath, LoadingComponen
55
58
  };
56
59
  }
57
60
  const typedRemoteModule = remoteModule;
61
+ let BaseComponent;
58
62
  if (!((_typedRemoteModule = typedRemoteModule) === null || _typedRemoteModule === void 0 ? void 0 : _typedRemoteModule.default)) {
59
- return {
60
- default: remoteModule
61
- };
63
+ BaseComponent = remoteModule;
64
+ } else {
65
+ BaseComponent = typedRemoteModule.default;
62
66
  }
67
+ // 先应用增强插件的组件包装器
68
+ const WrappedComponent = enhancedPluginManager.wrapComponent(BaseComponent, {
69
+ remoteName: scope,
70
+ moduleName: module,
71
+ props: componentProps || {}
72
+ });
73
+ // 然后处理 runtime 和 mountNode(如果需要)
74
+ let FinalComponent;
63
75
  if (runtime) {
64
76
  const { react, reactDOM } = runtime;
65
- return {
66
- default: FallBack({
67
- Original: typedRemoteModule.default,
68
- remoteReact: ()=>react,
69
- remoteReactDOM: ()=>reactDOM,
70
- mountNode: resolvedMountNode,
71
- containerClassName: fallbackContainerClassName
72
- })
73
- };
77
+ FinalComponent = FallBack({
78
+ Original: WrappedComponent,
79
+ remoteReact: ()=>react,
80
+ remoteReactDOM: ()=>reactDOM,
81
+ mountNode: resolvedMountNode,
82
+ containerClassName: fallbackContainerClassName
83
+ });
84
+ } else {
85
+ FinalComponent = WrappedComponent;
74
86
  }
75
87
  return {
76
- default: typedRemoteModule.default
88
+ default: FinalComponent
77
89
  };
78
90
  });
79
91
  }, [
@@ -82,11 +94,18 @@ const RemoteModuleInner = ({ module, scope, runtime, publicPath, LoadingComponen
82
94
  (_runtime = runtime) === null || _runtime === void 0 ? void 0 : _runtime.react,
83
95
  (_runtime1 = runtime) === null || _runtime1 === void 0 ? void 0 : _runtime1.reactDOM,
84
96
  resolvedMountNode,
85
- fallbackContainerClassName
97
+ fallbackContainerClassName,
98
+ enhancedPluginManager,
99
+ componentProps
86
100
  ]);
87
101
  const Loading = LoadingComponent || /*#__PURE__*/ React.createElement("div", null, "Loading...");
88
102
  const ErrorFallback = ({ error })=>ErrorComponent || /*#__PURE__*/ React.createElement("div", null, "远程模块加载失败: ", error.message);
89
103
  if (!Component) return Loading;
104
+ // 应用增强插件的属性注入
105
+ const injectedProps = enhancedPluginManager.injectProps(componentProps || {}, {
106
+ remoteName: scope,
107
+ moduleName: module
108
+ });
90
109
  return /*#__PURE__*/ React.createElement(ErrorBoundary, {
91
110
  resetKeys: [
92
111
  module,
@@ -98,8 +117,8 @@ const RemoteModuleInner = ({ module, scope, runtime, publicPath, LoadingComponen
98
117
  }, /*#__PURE__*/ React.createElement(React.Suspense, {
99
118
  fallback: Loading
100
119
  }, /*#__PURE__*/ React.createElement(Component, _object_spread({
101
- ref: ref
102
- }, componentProps), children)));
120
+ ref
121
+ }, injectedProps), children)));
103
122
  };
104
123
  // 使用 forwardRef 包装组件以支持 ref
105
124
  export const RemoteModule = /*#__PURE__*/ forwardRef(RemoteModuleInner);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,294 @@
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, registerEnhancedPlugin, registerEnhancedPlugins } from '../plugin-manager';
5
+ import * as React from 'react';
6
+ describe('EnhancedPluginManager', ()=>{
7
+ let manager;
8
+ beforeEach(()=>{
9
+ manager = new EnhancedPluginManager();
10
+ });
11
+ describe('Plugin Registration', ()=>{
12
+ it('should register an enhanced plugin', ()=>{
13
+ const enhancedPlugin = {
14
+ name: 'test-enhanced',
15
+ wrapComponent: (Component)=>Component,
16
+ injectProps: (props)=>props
17
+ };
18
+ manager.register(enhancedPlugin);
19
+ const plugins = manager.getPlugins();
20
+ expect(plugins).toHaveLength(1);
21
+ expect(plugins[0].name).toBe('test-enhanced');
22
+ });
23
+ it('should replace existing plugin with same name', ()=>{
24
+ const plugin1 = {
25
+ name: 'test-plugin',
26
+ wrapComponent: (Component)=>Component
27
+ };
28
+ const plugin2 = {
29
+ name: 'test-plugin',
30
+ injectProps: (props)=>props
31
+ };
32
+ manager.register(plugin1);
33
+ manager.register(plugin2);
34
+ const plugins = manager.getPlugins();
35
+ expect(plugins).toHaveLength(1);
36
+ expect(plugins[0]).toBe(plugin2);
37
+ });
38
+ it('should register multiple plugins', ()=>{
39
+ const plugins = [
40
+ {
41
+ name: 'plugin1',
42
+ wrapComponent: (Component)=>Component
43
+ },
44
+ {
45
+ name: 'plugin2',
46
+ injectProps: (props)=>props
47
+ }
48
+ ];
49
+ manager.registerPlugins(plugins);
50
+ expect(manager.getPlugins()).toHaveLength(2);
51
+ });
52
+ });
53
+ describe('Component Wrapping', ()=>{
54
+ it('should wrap component with single plugin', ()=>{
55
+ const TestComponent = ()=>React.createElement('div', null, 'test');
56
+ const mockWrapper = vi.fn((Component)=>Component);
57
+ const plugin = {
58
+ name: 'wrapper-plugin',
59
+ wrapComponent: mockWrapper
60
+ };
61
+ manager.register(plugin);
62
+ const context = {
63
+ remoteName: 'test-remote',
64
+ moduleName: 'test-module',
65
+ props: {
66
+ test: true
67
+ }
68
+ };
69
+ const wrappedComponent = manager.wrapComponent(TestComponent, context);
70
+ expect(mockWrapper).toHaveBeenCalledWith(TestComponent, context);
71
+ expect(wrappedComponent).toBe(TestComponent);
72
+ });
73
+ it('should apply multiple wrappers in order', ()=>{
74
+ const TestComponent = ()=>React.createElement('div', null, 'original');
75
+ const Wrapper1 = ()=>React.createElement('div', null, 'wrapper1');
76
+ const Wrapper2 = ()=>React.createElement('div', null, 'wrapper2');
77
+ const plugin1 = {
78
+ name: 'wrapper1',
79
+ wrapComponent: ()=>Wrapper1
80
+ };
81
+ const plugin2 = {
82
+ name: 'wrapper2',
83
+ wrapComponent: ()=>Wrapper2
84
+ };
85
+ // 注册顺序很重要
86
+ manager.register(plugin1);
87
+ manager.register(plugin2);
88
+ const context = {
89
+ remoteName: 'test-remote',
90
+ moduleName: 'test-module',
91
+ props: {}
92
+ };
93
+ const result = manager.wrapComponent(TestComponent, context);
94
+ // plugin2 应该最后执行,所以返回 Wrapper2
95
+ expect(result).toBe(Wrapper2);
96
+ });
97
+ it('should handle wrapper errors gracefully', ()=>{
98
+ const TestComponent = ()=>React.createElement('div', null, 'test');
99
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(()=>{});
100
+ const plugin = {
101
+ name: 'error-plugin',
102
+ wrapComponent: ()=>{
103
+ throw new Error('Wrapper error');
104
+ }
105
+ };
106
+ manager.register(plugin);
107
+ const context = {
108
+ remoteName: 'test-remote',
109
+ moduleName: 'test-module',
110
+ props: {}
111
+ };
112
+ const result = manager.wrapComponent(TestComponent, context);
113
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Error applying wrapper from plugin "error-plugin"'), expect.any(Error));
114
+ expect(result).toBe(TestComponent); // 应该返回原始组件
115
+ consoleSpy.mockRestore();
116
+ });
117
+ });
118
+ describe('Props Injection', ()=>{
119
+ it('should inject props with single plugin', ()=>{
120
+ const mockInjector = vi.fn((props)=>_object_spread_props(_object_spread({}, props), {
121
+ injected: true
122
+ }));
123
+ const plugin = {
124
+ name: 'injector-plugin',
125
+ injectProps: mockInjector
126
+ };
127
+ manager.register(plugin);
128
+ const context = {
129
+ remoteName: 'test-remote',
130
+ moduleName: 'test-module'
131
+ };
132
+ const result = manager.injectProps({
133
+ original: true
134
+ }, context);
135
+ expect(mockInjector).toHaveBeenCalledWith({
136
+ original: true
137
+ }, context);
138
+ expect(result).toEqual({
139
+ original: true,
140
+ injected: true
141
+ });
142
+ });
143
+ it('should apply multiple injectors in order', ()=>{
144
+ const plugin1 = {
145
+ name: 'injector1',
146
+ injectProps: (props)=>_object_spread_props(_object_spread({}, props), {
147
+ step1: true
148
+ })
149
+ };
150
+ const plugin2 = {
151
+ name: 'injector2',
152
+ injectProps: (props)=>_object_spread_props(_object_spread({}, props), {
153
+ step2: true
154
+ })
155
+ };
156
+ manager.register(plugin1);
157
+ manager.register(plugin2);
158
+ const context = {
159
+ remoteName: 'test-remote',
160
+ moduleName: 'test-module'
161
+ };
162
+ const result = manager.injectProps({
163
+ original: true
164
+ }, context);
165
+ expect(result).toEqual({
166
+ original: true,
167
+ step1: true,
168
+ step2: true
169
+ });
170
+ });
171
+ it('should handle injection errors gracefully', ()=>{
172
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(()=>{});
173
+ const plugin = {
174
+ name: 'error-plugin',
175
+ injectProps: ()=>{
176
+ throw new Error('Injection error');
177
+ }
178
+ };
179
+ manager.register(plugin);
180
+ const context = {
181
+ remoteName: 'test-remote',
182
+ moduleName: 'test-module'
183
+ };
184
+ const result = manager.injectProps({
185
+ original: true
186
+ }, context);
187
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Error injecting props from plugin "error-plugin"'), expect.any(Error));
188
+ expect(result).toEqual({
189
+ original: true
190
+ }); // 应该返回原始props
191
+ consoleSpy.mockRestore();
192
+ });
193
+ });
194
+ describe('Plugin Management', ()=>{
195
+ it('should get plugin info', ()=>{
196
+ const wrapperPlugin = {
197
+ name: 'wrapper-plugin',
198
+ wrapComponent: (Component)=>Component
199
+ };
200
+ const injectorPlugin = {
201
+ name: 'injector-plugin',
202
+ injectProps: (props)=>props
203
+ };
204
+ const hybridPlugin = {
205
+ name: 'hybrid-plugin',
206
+ wrapComponent: (Component)=>Component,
207
+ injectProps: (props)=>props
208
+ };
209
+ manager.register(wrapperPlugin);
210
+ manager.register(injectorPlugin);
211
+ manager.register(hybridPlugin);
212
+ const info = manager.getPluginInfo();
213
+ expect(info).toHaveLength(3);
214
+ expect(info[0]).toEqual({
215
+ name: 'wrapper-plugin',
216
+ hasWrapper: true,
217
+ hasInjector: false
218
+ });
219
+ expect(info[1]).toEqual({
220
+ name: 'injector-plugin',
221
+ hasWrapper: false,
222
+ hasInjector: true
223
+ });
224
+ expect(info[2]).toEqual({
225
+ name: 'hybrid-plugin',
226
+ hasWrapper: true,
227
+ hasInjector: true
228
+ });
229
+ });
230
+ it('should remove plugin', ()=>{
231
+ const plugin = {
232
+ name: 'test-plugin',
233
+ wrapComponent: (Component)=>Component
234
+ };
235
+ manager.register(plugin);
236
+ expect(manager.getPlugins()).toHaveLength(1);
237
+ const removed = manager.removePlugin('test-plugin');
238
+ expect(removed).toBe(true);
239
+ expect(manager.getPlugins()).toHaveLength(0);
240
+ });
241
+ it('should return false when removing non-existent plugin', ()=>{
242
+ const removed = manager.removePlugin('non-existent');
243
+ expect(removed).toBe(false);
244
+ });
245
+ it('should clear all plugins', ()=>{
246
+ manager.register({
247
+ name: 'plugin1',
248
+ wrapComponent: (Component)=>Component
249
+ });
250
+ manager.register({
251
+ name: 'plugin2',
252
+ injectProps: (props)=>props
253
+ });
254
+ expect(manager.getPlugins()).toHaveLength(2);
255
+ manager.clear();
256
+ expect(manager.getPlugins()).toHaveLength(0);
257
+ });
258
+ });
259
+ describe('Global Enhanced Plugin Manager', ()=>{
260
+ beforeEach(()=>{
261
+ // 清理全局状态
262
+ getEnhancedPluginManager().clear();
263
+ });
264
+ it('should provide singleton instance', ()=>{
265
+ const manager1 = getEnhancedPluginManager();
266
+ const manager2 = getEnhancedPluginManager();
267
+ expect(manager1).toBe(manager2);
268
+ });
269
+ it('should register plugin via convenience function', ()=>{
270
+ const plugin = {
271
+ name: 'convenience-plugin',
272
+ wrapComponent: (Component)=>Component
273
+ };
274
+ registerEnhancedPlugin(plugin);
275
+ const manager = getEnhancedPluginManager();
276
+ expect(manager.getPlugins()).toContainEqual(plugin);
277
+ });
278
+ it('should register multiple plugins via convenience function', ()=>{
279
+ const plugins = [
280
+ {
281
+ name: 'plugin1',
282
+ wrapComponent: (Component)=>Component
283
+ },
284
+ {
285
+ name: 'plugin2',
286
+ injectProps: (props)=>props
287
+ }
288
+ ];
289
+ registerEnhancedPlugins(plugins);
290
+ const manager = getEnhancedPluginManager();
291
+ expect(manager.getPlugins()).toHaveLength(2);
292
+ });
293
+ });
294
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { vi } from 'vitest';
2
+ // Test setup for ice-mf-runtime
3
+ // Mock @ice/stark-app dependency
4
+ vi.mock('@ice/stark-app', ()=>({
5
+ getBasename: ()=>'/app'
6
+ }));
7
+ // Mock @module-federation/runtime
8
+ vi.mock('@module-federation/runtime', ()=>({
9
+ init: vi.fn(),
10
+ registerPlugins: vi.fn(),
11
+ loadRemote: vi.fn()
12
+ }));
13
+ // Mock performance API
14
+ Object.defineProperty(globalThis, 'performance', {
15
+ value: {
16
+ now: ()=>Date.now()
17
+ },
18
+ writable: true
19
+ });
20
+ // Suppress console warnings during tests
21
+ const originalWarn = console.warn;
22
+ console.warn = (...args)=>{
23
+ var _args__includes, _args_;
24
+ 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')) {
25
+ return;
26
+ }
27
+ originalWarn.call(console, ...args);
28
+ };
package/es2017/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;