@empjs/adapter-react 0.0.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 +182 -0
- package/dist/index.cjs +109 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +72 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @empjs/adapter-react
|
|
2
|
+
|
|
3
|
+
一个轻量级的 React 适配器,用于 EMP 微前端框架中跨 React 版本渲染组件。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 🚀 简洁高效的实现
|
|
8
|
+
- 📦 支持 React 16/17/18/19 多版本兼容
|
|
9
|
+
- 🔄 支持异步组件加载和动态导入
|
|
10
|
+
- 🎯 完整的 TypeScript 类型支持
|
|
11
|
+
- 🛡️ 稳定的跨版本渲染机制
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @empjs/adapter-react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 使用
|
|
20
|
+
|
|
21
|
+
[DEMO](https://github.com/empjs/emp/blob/main/projects/adapter-app/src/App.tsx)
|
|
22
|
+
|
|
23
|
+
### 基础用法
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { reactAdapter } from '@empjs/adapter-react'
|
|
27
|
+
import MyComponent from './MyComponent'
|
|
28
|
+
|
|
29
|
+
// 适配一个组件
|
|
30
|
+
const WrappedComponent = reactAdapter.adapter(MyComponent)
|
|
31
|
+
|
|
32
|
+
// 使用适配后的组件
|
|
33
|
+
<WrappedComponent {...props} />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 自定义配置
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { ReactAdapter } from '@empjs/adapter-react'
|
|
40
|
+
import React from 'react'
|
|
41
|
+
import ReactDOM from 'react-dom'
|
|
42
|
+
|
|
43
|
+
// 创建自定义适配器实例
|
|
44
|
+
const adapter = new ReactAdapter({
|
|
45
|
+
React,
|
|
46
|
+
ReactDOM,
|
|
47
|
+
createRoot: ReactDOM.createRoot, // React 18+ 需要
|
|
48
|
+
scope: 'default'
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// 适配组件
|
|
52
|
+
const WrappedComponent = adapter.adapter(MyComponent)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 从全局对象获取配置
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { ReactAdapter } from '@empjs/adapter-react'
|
|
59
|
+
|
|
60
|
+
// 从全局对象获取 React18 的对象
|
|
61
|
+
const { EMP_ADAPTER_REACT } = window as any
|
|
62
|
+
const react18 = new ReactAdapter(EMP_ADAPTER_REACT)
|
|
63
|
+
const RemoteApp = react18.adapter(remoteComponent)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 适配异步组件
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { reactAdapter } from '@empjs/adapter-react'
|
|
70
|
+
|
|
71
|
+
// 适配动态导入的组件
|
|
72
|
+
const AsyncComponent = import('./RemoteComponent')
|
|
73
|
+
const WrappedAsyncComponent = reactAdapter.adapter(AsyncComponent, 'default')
|
|
74
|
+
|
|
75
|
+
// 在 JSX 中使用
|
|
76
|
+
<WrappedAsyncComponent {...props} />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### ReactAdapter
|
|
82
|
+
|
|
83
|
+
#### constructor(options?)
|
|
84
|
+
|
|
85
|
+
创建一个新的适配器实例。
|
|
86
|
+
|
|
87
|
+
**参数:**
|
|
88
|
+
- `options` (可选): `ReactAdapterOptions` - 适配器配置选项
|
|
89
|
+
- `React?`: 自定义的 React 库实例
|
|
90
|
+
- `ReactDOM?`: 自定义的 ReactDOM 库实例
|
|
91
|
+
- `createRoot?`: React 18+ 的 createRoot 方法
|
|
92
|
+
- `scope?`: 组件导出的作用域名称,默认为 'default'
|
|
93
|
+
|
|
94
|
+
**示例:**
|
|
95
|
+
```typescript
|
|
96
|
+
// 使用默认配置
|
|
97
|
+
const adapter = new ReactAdapter()
|
|
98
|
+
|
|
99
|
+
// 使用自定义配置
|
|
100
|
+
const adapter = new ReactAdapter({
|
|
101
|
+
React: CustomReact,
|
|
102
|
+
ReactDOM: CustomReactDOM,
|
|
103
|
+
createRoot: CustomReactDOM.createRoot
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### adapter(component, scope?, React?, ReactDOM?)
|
|
108
|
+
|
|
109
|
+
适配一个 React 组件,返回包装后的组件。
|
|
110
|
+
|
|
111
|
+
**参数:**
|
|
112
|
+
- `component`: 要适配的 React 组件或异步组件
|
|
113
|
+
- `scope?` (可选): 组件的作用域名称,默认使用实例配置的 scope
|
|
114
|
+
- `React?` (可选): 要使用的 React 库,默认使用实例配置的 React
|
|
115
|
+
- `ReactDOM?` (可选): 要使用的 ReactDOM 库,默认使用实例配置的 ReactDOM
|
|
116
|
+
|
|
117
|
+
**返回值:**
|
|
118
|
+
- 包装后的 React 组件
|
|
119
|
+
|
|
120
|
+
**示例:**
|
|
121
|
+
```typescript
|
|
122
|
+
// 基本用法
|
|
123
|
+
const WrappedComponent = reactAdapter.adapter(MyComponent)
|
|
124
|
+
|
|
125
|
+
// 指定作用域
|
|
126
|
+
const WrappedWithScope = reactAdapter.adapter(MyComponent, 'customScope')
|
|
127
|
+
|
|
128
|
+
// 使用自定义 React 和 ReactDOM
|
|
129
|
+
const CustomWrapped = reactAdapter.adapter(
|
|
130
|
+
MyComponent,
|
|
131
|
+
'default',
|
|
132
|
+
CustomReact,
|
|
133
|
+
CustomReactDOM
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 工作原理
|
|
138
|
+
|
|
139
|
+
`ReactAdapter` 通过以下机制实现跨 React 版本的组件渲染:
|
|
140
|
+
|
|
141
|
+
1. 使用类组件包装原始组件,提供统一的渲染容器
|
|
142
|
+
2. 自动检测 React 版本并使用对应的渲染方法:
|
|
143
|
+
- React 16/17: 使用 `ReactDOM.render` 和 `ReactDOM.unmountComponentAtNode`
|
|
144
|
+
- React 18+: 使用 `createRoot` 创建根节点并调用 `render` 和 `unmount` 方法
|
|
145
|
+
3. 支持异步组件加载,等待 Promise 解析后再渲染
|
|
146
|
+
4. 处理组件的生命周期,确保正确挂载、更新和卸载
|
|
147
|
+
|
|
148
|
+
## 实际应用示例
|
|
149
|
+
|
|
150
|
+
### 在微前端环境中使用
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// 主应用
|
|
154
|
+
import { ReactAdapter } from '@empjs/adapter-react'
|
|
155
|
+
import remoteApp from 'remote/App'
|
|
156
|
+
import React from 'react'
|
|
157
|
+
|
|
158
|
+
// 从全局配置初始化适配器
|
|
159
|
+
const { EMP_ADAPTER_REACT } = window as any
|
|
160
|
+
const react18 = new ReactAdapter(EMP_ADAPTER_REACT)
|
|
161
|
+
|
|
162
|
+
// 适配远程应用组件
|
|
163
|
+
const RemoteApp = react18.adapter(remoteApp)
|
|
164
|
+
|
|
165
|
+
// 在 React 16 环境中使用 React 18 组件
|
|
166
|
+
const App = () => (
|
|
167
|
+
<div>
|
|
168
|
+
<h1>主应用 (React 16)</h1>
|
|
169
|
+
<RemoteApp>
|
|
170
|
+
<div>这是来自主应用的子内容</div>
|
|
171
|
+
</RemoteApp>
|
|
172
|
+
</div>
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
export default App
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 注意事项
|
|
179
|
+
|
|
180
|
+
- 确保为 React 18+ 提供 `createRoot` 方法
|
|
181
|
+
- 异步组件需要通过 `scope` 参数指定导出的组件名称
|
|
182
|
+
- 适配器会自动处理不同 React 版本的渲染差异,无需手动判断
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
ReactAdapter: ()=>ReactAdapter,
|
|
28
|
+
reactAdapter: ()=>reactAdapter
|
|
29
|
+
});
|
|
30
|
+
const checkVersion = (version)=>version ? Number(version.split('.')[0]) : 0;
|
|
31
|
+
const isPromise = (p)=>p && '[object Promise]' === Object.prototype.toString.call(p);
|
|
32
|
+
class ReactAdapter {
|
|
33
|
+
libs = {
|
|
34
|
+
scope: 'default'
|
|
35
|
+
};
|
|
36
|
+
constructor(op = {}){
|
|
37
|
+
this.libs = {
|
|
38
|
+
...this.libs,
|
|
39
|
+
...op
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
adapter(component, scope = this.libs.scope || 'default', React = this.libs.React, ReactDOM = this.libs.ReactDOM) {
|
|
43
|
+
const reactVersion = checkVersion(React?.version || '18.0.0');
|
|
44
|
+
const self = this;
|
|
45
|
+
class WrappedComponent extends React.Component {
|
|
46
|
+
containerRef;
|
|
47
|
+
root;
|
|
48
|
+
resolvedComponent = null;
|
|
49
|
+
constructor(props){
|
|
50
|
+
super(props);
|
|
51
|
+
this.containerRef = React.createRef();
|
|
52
|
+
}
|
|
53
|
+
componentDidMount() {
|
|
54
|
+
this.mountOriginalComponent(true);
|
|
55
|
+
}
|
|
56
|
+
componentDidUpdate() {
|
|
57
|
+
this.mountOriginalComponent(false);
|
|
58
|
+
}
|
|
59
|
+
componentWillUnmount() {
|
|
60
|
+
this.unmountOriginalComponent();
|
|
61
|
+
}
|
|
62
|
+
unmountOriginalComponent() {
|
|
63
|
+
if (!this.containerRef.current) return;
|
|
64
|
+
try {
|
|
65
|
+
if (reactVersion < 18) ReactDOM.unmountComponentAtNode(this.containerRef.current);
|
|
66
|
+
else if (this.root) this.root.unmount();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error("[ReactAdapter] \u5378\u8F7D\u7EC4\u4EF6\u65F6\u51FA\u9519:", error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async mountOriginalComponent(shouldRender = false) {
|
|
72
|
+
try {
|
|
73
|
+
if (!this.resolvedComponent) {
|
|
74
|
+
let resolvedComp = component;
|
|
75
|
+
if (isPromise(component)) resolvedComp = await component.then((m)=>m[scope]);
|
|
76
|
+
this.resolvedComponent = resolvedComp;
|
|
77
|
+
}
|
|
78
|
+
const element = React.createElement(this.resolvedComponent, this.props);
|
|
79
|
+
if (reactVersion < 18) {
|
|
80
|
+
const renderMethod = shouldRender ? ReactDOM.render : ReactDOM.hydrate;
|
|
81
|
+
renderMethod(element, this.containerRef.current);
|
|
82
|
+
} else if (shouldRender) {
|
|
83
|
+
const { createRoot } = self.libs;
|
|
84
|
+
this.root = createRoot(this.containerRef.current);
|
|
85
|
+
this.root.render(element);
|
|
86
|
+
} else if (this.root) this.root.render(element);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("[ReactAdapter] \u6302\u8F7D\u7EC4\u4EF6\u65F6\u51FA\u9519:", error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
render() {
|
|
92
|
+
return React.createElement('div', {
|
|
93
|
+
ref: this.containerRef
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return WrappedComponent;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const reactAdapter = new ReactAdapter();
|
|
101
|
+
exports.ReactAdapter = __webpack_exports__.ReactAdapter;
|
|
102
|
+
exports.reactAdapter = __webpack_exports__.reactAdapter;
|
|
103
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
104
|
+
"ReactAdapter",
|
|
105
|
+
"reactAdapter"
|
|
106
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
107
|
+
Object.defineProperty(exports, '__esModule', {
|
|
108
|
+
value: true
|
|
109
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React 适配器
|
|
3
|
+
* 这个模块提供了一个适配器,用于在不同版本的 React 环境中渲染组件
|
|
4
|
+
* 支持 React 16/17 和 React 18+
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* React 适配器配置选项
|
|
8
|
+
*/
|
|
9
|
+
export interface ReactAdapterOptions {
|
|
10
|
+
/** React 库实例 */
|
|
11
|
+
React?: any;
|
|
12
|
+
/** ReactDOM 库实例 */
|
|
13
|
+
ReactDOM?: any;
|
|
14
|
+
/** React 18+ createRoot 方法 */
|
|
15
|
+
createRoot?: any;
|
|
16
|
+
/** 组件导出的作用域名称 */
|
|
17
|
+
scope?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* React 适配器类
|
|
21
|
+
* 用于在不同版本的 React 环境中适配组件渲染
|
|
22
|
+
*/
|
|
23
|
+
export declare class ReactAdapter {
|
|
24
|
+
/** 适配器配置 */
|
|
25
|
+
libs: ReactAdapterOptions;
|
|
26
|
+
/**
|
|
27
|
+
* 构造函数
|
|
28
|
+
* @param op - 适配器配置选项
|
|
29
|
+
*/
|
|
30
|
+
constructor(op?: ReactAdapterOptions);
|
|
31
|
+
/**
|
|
32
|
+
* 适配器核心方法 - 使用类组件实现
|
|
33
|
+
* @param component - 要适配的组件
|
|
34
|
+
* @param scope - 组件导出的作用域名称
|
|
35
|
+
* @param React - React 库实例
|
|
36
|
+
* @param ReactDOM - ReactDOM 库实例
|
|
37
|
+
* @returns 包装后的组件
|
|
38
|
+
*/
|
|
39
|
+
adapter<P = any>(component: any, scope?: string, React?: any, ReactDOM?: any): P;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 默认导出的 React 适配器实例
|
|
43
|
+
*/
|
|
44
|
+
export declare const reactAdapter: ReactAdapter;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const checkVersion = (version)=>version ? Number(version.split('.')[0]) : 0;
|
|
2
|
+
const isPromise = (p)=>p && '[object Promise]' === Object.prototype.toString.call(p);
|
|
3
|
+
class ReactAdapter {
|
|
4
|
+
libs = {
|
|
5
|
+
scope: 'default'
|
|
6
|
+
};
|
|
7
|
+
constructor(op = {}){
|
|
8
|
+
this.libs = {
|
|
9
|
+
...this.libs,
|
|
10
|
+
...op
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
adapter(component, scope = this.libs.scope || 'default', React = this.libs.React, ReactDOM = this.libs.ReactDOM) {
|
|
14
|
+
const reactVersion = checkVersion(React?.version || '18.0.0');
|
|
15
|
+
const self = this;
|
|
16
|
+
class WrappedComponent extends React.Component {
|
|
17
|
+
containerRef;
|
|
18
|
+
root;
|
|
19
|
+
resolvedComponent = null;
|
|
20
|
+
constructor(props){
|
|
21
|
+
super(props);
|
|
22
|
+
this.containerRef = React.createRef();
|
|
23
|
+
}
|
|
24
|
+
componentDidMount() {
|
|
25
|
+
this.mountOriginalComponent(true);
|
|
26
|
+
}
|
|
27
|
+
componentDidUpdate() {
|
|
28
|
+
this.mountOriginalComponent(false);
|
|
29
|
+
}
|
|
30
|
+
componentWillUnmount() {
|
|
31
|
+
this.unmountOriginalComponent();
|
|
32
|
+
}
|
|
33
|
+
unmountOriginalComponent() {
|
|
34
|
+
if (!this.containerRef.current) return;
|
|
35
|
+
try {
|
|
36
|
+
if (reactVersion < 18) ReactDOM.unmountComponentAtNode(this.containerRef.current);
|
|
37
|
+
else if (this.root) this.root.unmount();
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("[ReactAdapter] \u5378\u8F7D\u7EC4\u4EF6\u65F6\u51FA\u9519:", error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async mountOriginalComponent(shouldRender = false) {
|
|
43
|
+
try {
|
|
44
|
+
if (!this.resolvedComponent) {
|
|
45
|
+
let resolvedComp = component;
|
|
46
|
+
if (isPromise(component)) resolvedComp = await component.then((m)=>m[scope]);
|
|
47
|
+
this.resolvedComponent = resolvedComp;
|
|
48
|
+
}
|
|
49
|
+
const element = React.createElement(this.resolvedComponent, this.props);
|
|
50
|
+
if (reactVersion < 18) {
|
|
51
|
+
const renderMethod = shouldRender ? ReactDOM.render : ReactDOM.hydrate;
|
|
52
|
+
renderMethod(element, this.containerRef.current);
|
|
53
|
+
} else if (shouldRender) {
|
|
54
|
+
const { createRoot } = self.libs;
|
|
55
|
+
this.root = createRoot(this.containerRef.current);
|
|
56
|
+
this.root.render(element);
|
|
57
|
+
} else if (this.root) this.root.render(element);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("[ReactAdapter] \u6302\u8F7D\u7EC4\u4EF6\u65F6\u51FA\u9519:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
render() {
|
|
63
|
+
return React.createElement('div', {
|
|
64
|
+
ref: this.containerRef
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return WrappedComponent;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const reactAdapter = new ReactAdapter();
|
|
72
|
+
export { ReactAdapter, reactAdapter };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@empjs/adapter-react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "emp react adapter",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"maintainers": [
|
|
11
|
+
"xuhongbin",
|
|
12
|
+
"ckken",
|
|
13
|
+
"doerme",
|
|
14
|
+
"ron0115"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/empjs/emp.git",
|
|
19
|
+
"directory": "packages/adapter-react"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.cjs"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=16.0.0"
|
|
40
|
+
},
|
|
41
|
+
"author": "Ken",
|
|
42
|
+
"dependencies": {},
|
|
43
|
+
"devDependencies": {},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"dev": "rslib build --watch --env-mode development",
|
|
46
|
+
"build": "rslib build"
|
|
47
|
+
}
|
|
48
|
+
}
|