@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 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
+ });
@@ -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
+ }