@cqsjjb/jjb-cloud-component 0.0.9 → 0.0.11
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 +51 -4
- package/cloud-component.d.ts +2 -0
- package/cloud-component.js +92 -16
- package/import-cloud-component.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,28 @@ function App() {
|
|
|
92
92
|
}
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
### 旧版云组件支持
|
|
96
|
+
|
|
97
|
+
对于旧版云组件(没有导出 `info` 的组件),可以使用 `moduleKey` 指定要使用的导出键:
|
|
98
|
+
|
|
99
|
+
```jsx
|
|
100
|
+
import React from 'react';
|
|
101
|
+
import { CloudComponent } from '@cqsjjb/jjb-cloud-component';
|
|
102
|
+
|
|
103
|
+
function App() {
|
|
104
|
+
return (
|
|
105
|
+
<CloudComponent
|
|
106
|
+
from="https://example.com/legacy-component.js"
|
|
107
|
+
componentKey="legacy-component"
|
|
108
|
+
moduleKey="MyComponent" // 指定从模块中导出的键名
|
|
109
|
+
componentProps={{
|
|
110
|
+
title: "旧版组件"
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
95
117
|
## API 文档
|
|
96
118
|
|
|
97
119
|
### CloudComponent Props
|
|
@@ -100,6 +122,7 @@ function App() {
|
|
|
100
122
|
|------|------|------|------|
|
|
101
123
|
| `from` | `string` | ✅ | 云组件资源地址 |
|
|
102
124
|
| `componentKey` | `string` | ✅ | 组件唯一标识 |
|
|
125
|
+
| `moduleKey` | `string` | ❌ | 模块导出键名,用于指定从模块中导出的键(旧版云组件使用),默认为 `'default'` |
|
|
103
126
|
| `dependencies` | `Record<string, unknown>` | ❌ | 组件依赖 |
|
|
104
127
|
| `cache` | `CacheStrategy` | ❌ | 缓存策略 |
|
|
105
128
|
| `headers` | `Record<string, string>` | ❌ | 请求头 |
|
|
@@ -158,10 +181,12 @@ const { module, styleId, isNewVersion } = await ImportCloudComponent({
|
|
|
158
181
|
|
|
159
182
|
### 组件结构
|
|
160
183
|
|
|
161
|
-
|
|
184
|
+
#### 新版云组件(推荐)
|
|
185
|
+
|
|
186
|
+
新版云组件需要导出 `info` 对象和 `default` 组件:
|
|
162
187
|
|
|
163
188
|
```javascript
|
|
164
|
-
//
|
|
189
|
+
// 组件信息(必需,用于版本检测)
|
|
165
190
|
const info = {
|
|
166
191
|
name: 'MyComponent',
|
|
167
192
|
version: '1.0.0',
|
|
@@ -186,13 +211,32 @@ function MyComponent(props) {
|
|
|
186
211
|
}, props.title);
|
|
187
212
|
}
|
|
188
213
|
|
|
189
|
-
//
|
|
214
|
+
// 导出(新版必须导出 info 和 default)
|
|
190
215
|
module.exports = {
|
|
191
216
|
info,
|
|
192
217
|
default: MyComponent
|
|
193
218
|
};
|
|
194
219
|
```
|
|
195
220
|
|
|
221
|
+
#### 旧版云组件(兼容)
|
|
222
|
+
|
|
223
|
+
旧版云组件可以不导出 `info`,但需要指定导出键名(通过 `moduleKey` 属性):
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
// 组件实现
|
|
227
|
+
function MyComponent(props) {
|
|
228
|
+
return React.createElement('div', {
|
|
229
|
+
className: 'my-component'
|
|
230
|
+
}, props.title);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 导出(旧版可以导出任意键名)
|
|
234
|
+
module.exports = {
|
|
235
|
+
MyComponent, // 使用 moduleKey="MyComponent" 来指定
|
|
236
|
+
AnotherComponent // 或者导出多个组件
|
|
237
|
+
};
|
|
238
|
+
```
|
|
239
|
+
|
|
196
240
|
### 样式处理
|
|
197
241
|
|
|
198
242
|
云组件中的样式会自动注入到页面中,使用 `{cloudComponentStyleId}` 占位符会被替换为实际的样式ID。
|
|
@@ -211,7 +255,10 @@ const style = `
|
|
|
211
255
|
2. **依赖管理**: 正确配置组件所需的依赖
|
|
212
256
|
3. **样式隔离**: 使用样式ID避免样式冲突
|
|
213
257
|
4. **错误处理**: 实现适当的错误处理机制
|
|
214
|
-
5. **版本兼容**:
|
|
258
|
+
5. **版本兼容**:
|
|
259
|
+
- 新版云组件(有 `info` 导出):使用 `module.default`,无需指定 `moduleKey`
|
|
260
|
+
- 旧版云组件(无 `info` 导出):需要指定 `moduleKey` 来指定导出的键名,默认为 `'default'`
|
|
261
|
+
6. **模块键名**: 如果旧版组件导出的键名不是 `'default'`,必须通过 `moduleKey` 属性指定正确的键名
|
|
215
262
|
|
|
216
263
|
## 许可证
|
|
217
264
|
|
package/cloud-component.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ interface CloudComponentProps extends ComponentProps {
|
|
|
36
36
|
initialize?: boolean;
|
|
37
37
|
// 组件唯一key
|
|
38
38
|
componentKey: string;
|
|
39
|
+
// 模块导出键名,用于指定从模块中导出的键(旧版云组件使用),默认为 'default'
|
|
40
|
+
moduleKey?: string;
|
|
39
41
|
// 组件的额外Props
|
|
40
42
|
componentProps?: Record<string, unknown>;
|
|
41
43
|
// 组件引用
|
package/cloud-component.js
CHANGED
|
@@ -24,7 +24,7 @@ export function useUnMountCloudComponentStyle(styleId) {
|
|
|
24
24
|
* @note 用户自定义依赖项会覆盖同名的默认依赖项
|
|
25
25
|
*/
|
|
26
26
|
export default function CloudComponent(props) {
|
|
27
|
-
|
|
27
|
+
const styleId = React.useRef(null);
|
|
28
28
|
const [Component, setComponent] = React.useState(null);
|
|
29
29
|
const {
|
|
30
30
|
from,
|
|
@@ -35,6 +35,7 @@ export default function CloudComponent(props) {
|
|
|
35
35
|
componentRef,
|
|
36
36
|
componentKey,
|
|
37
37
|
componentProps,
|
|
38
|
+
moduleKey = 'default',
|
|
38
39
|
onLoadStart,
|
|
39
40
|
onLoadEnd,
|
|
40
41
|
onDestroy,
|
|
@@ -46,7 +47,10 @@ export default function CloudComponent(props) {
|
|
|
46
47
|
const defaultDependencies = {
|
|
47
48
|
'react': React,
|
|
48
49
|
'react-dom': ReactDOM,
|
|
50
|
+
'React': React,
|
|
51
|
+
'ReactDOM': ReactDOM,
|
|
49
52
|
'jjbCommonLib': jjbCommonLib,
|
|
53
|
+
'jjb-common-lib-types': jjbCommonLib,
|
|
50
54
|
'@cqsjjb/jjb-common-lib': jjbCommonLib
|
|
51
55
|
};
|
|
52
56
|
|
|
@@ -106,23 +110,26 @@ export default function CloudComponent(props) {
|
|
|
106
110
|
onLoadStartRef.current && onLoadStartRef.current();
|
|
107
111
|
const {
|
|
108
112
|
module,
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
isNewVersion,
|
|
114
|
+
styleId: _styleId
|
|
111
115
|
} = await ImportCloudComponent({
|
|
112
116
|
from,
|
|
113
117
|
cache,
|
|
114
118
|
headers,
|
|
115
119
|
dependencies: stableDependencies
|
|
116
120
|
});
|
|
117
|
-
|
|
121
|
+
|
|
122
|
+
// 优先使用导入云组件时生成的styleId,如果导入云组件时没有生成styleId,则使用模块info中的cloudComponentStyleId
|
|
123
|
+
styleId.current = _styleId || module?.info?.cloudComponentStyleId;
|
|
118
124
|
setComponent(() => {
|
|
119
125
|
onLoadEndRef.current && onLoadEndRef.current();
|
|
120
|
-
|
|
126
|
+
const $module = isNewVersion ? module.default({
|
|
121
127
|
// 组件引用
|
|
122
128
|
ref: componentRef,
|
|
123
129
|
// 将依赖项穿透下去,用于在组件内部使用依赖项(支持云组件嵌套云组件)
|
|
124
130
|
$$dependencies: stableDependencies
|
|
125
|
-
}) : module
|
|
131
|
+
}) : module[moduleKey];
|
|
132
|
+
return $module;
|
|
126
133
|
});
|
|
127
134
|
} catch (error) {
|
|
128
135
|
console.error('云组件加载失败:', error);
|
|
@@ -137,23 +144,92 @@ export default function CloudComponent(props) {
|
|
|
137
144
|
}
|
|
138
145
|
}
|
|
139
146
|
load().then(() => null);
|
|
147
|
+
}, [from, cache, headers, shouldReloadDependencies, moduleKey]);
|
|
148
|
+
React.useEffect(() => {
|
|
140
149
|
return () => {
|
|
141
|
-
if (styleId) {
|
|
142
|
-
useUnMountCloudComponentStyle(styleId);
|
|
150
|
+
if (styleId.current) {
|
|
151
|
+
useUnMountCloudComponentStyle(styleId.current);
|
|
152
|
+
styleId.current = null;
|
|
143
153
|
}
|
|
144
154
|
onDestroyRef.current && onDestroyRef.current();
|
|
145
155
|
};
|
|
146
|
-
}, [
|
|
156
|
+
}, [styleId]);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 深度比较两个值是否相等,支持 ReactNode
|
|
160
|
+
* @param {any} a 第一个值
|
|
161
|
+
* @param {any} b 第二个值
|
|
162
|
+
* @returns {boolean} 是否相等
|
|
163
|
+
*/
|
|
164
|
+
function deepEqual(a, b) {
|
|
165
|
+
// 引用相等
|
|
166
|
+
if (a === b) return true;
|
|
167
|
+
|
|
168
|
+
// null 或 undefined 处理
|
|
169
|
+
if (a == null || b == null) return a === b;
|
|
170
|
+
|
|
171
|
+
// 类型不同
|
|
172
|
+
if (typeof a !== typeof b) return false;
|
|
173
|
+
|
|
174
|
+
// 基本类型
|
|
175
|
+
if (typeof a !== 'object') return a === b;
|
|
176
|
+
|
|
177
|
+
// React 元素(ReactNode)比较引用
|
|
178
|
+
// 如果只有一个是 ReactNode,肯定不相等
|
|
179
|
+
if (/*#__PURE__*/React.isValidElement(a) && ! /*#__PURE__*/React.isValidElement(b)) return false;
|
|
180
|
+
if (/*#__PURE__*/React.isValidElement(b) && ! /*#__PURE__*/React.isValidElement(a)) return false;
|
|
181
|
+
// 如果两个都是 ReactNode,比较引用
|
|
182
|
+
if (/*#__PURE__*/React.isValidElement(a) && /*#__PURE__*/React.isValidElement(b)) {
|
|
183
|
+
return a === b;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 数组比较
|
|
187
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
188
|
+
if (a.length !== b.length) return false;
|
|
189
|
+
for (let i = 0; i < a.length; i++) {
|
|
190
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 对象比较
|
|
196
|
+
if (Array.isArray(a) || Array.isArray(b)) return false;
|
|
197
|
+
const keysA = Object.keys(a);
|
|
198
|
+
const keysB = Object.keys(b);
|
|
199
|
+
if (keysA.length !== keysB.length) return false;
|
|
200
|
+
for (const key of keysA) {
|
|
201
|
+
if (!keysB.includes(key)) return false;
|
|
202
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 使用 useRef 来跟踪 componentProps 和 componentKey 的变化
|
|
208
|
+
const prevComponentPropsRef = React.useRef(componentProps);
|
|
209
|
+
const prevComponentKeyRef = React.useRef(componentKey);
|
|
210
|
+
const stableComponentPropsRef = React.useRef({
|
|
211
|
+
detail: {
|
|
212
|
+
componentKey
|
|
213
|
+
},
|
|
214
|
+
...jjbCommonLib.tools.toObject(componentProps)
|
|
215
|
+
});
|
|
147
216
|
|
|
148
217
|
// 使用 useMemo 来稳定化 componentProps,避免因对象重新创建导致的重新渲染
|
|
149
218
|
const stableComponentProps = React.useMemo(() => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
219
|
+
const propsChanged = !deepEqual(prevComponentPropsRef.current, componentProps);
|
|
220
|
+
const keyChanged = prevComponentKeyRef.current !== componentKey;
|
|
221
|
+
if (propsChanged || keyChanged) {
|
|
222
|
+
prevComponentPropsRef.current = componentProps;
|
|
223
|
+
prevComponentKeyRef.current = componentKey;
|
|
224
|
+
stableComponentPropsRef.current = {
|
|
225
|
+
detail: {
|
|
226
|
+
componentKey
|
|
227
|
+
},
|
|
228
|
+
...jjbCommonLib.tools.toObject(componentProps)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return stableComponentPropsRef.current;
|
|
232
|
+
}, [componentKey, componentProps]);
|
|
157
233
|
|
|
158
234
|
// 使用 useMemo 来稳定化 initialize 函数
|
|
159
235
|
const stableInitialize = React.useMemo(() => initialize, [initialize]);
|
|
@@ -8,7 +8,8 @@ import { tools } from '@cqsjjb/jjb-common-lib';
|
|
|
8
8
|
export function genCommonJSRuntime({
|
|
9
9
|
from
|
|
10
10
|
}, code) {
|
|
11
|
-
const styleId =
|
|
11
|
+
const styleId = Math.random().toString(36).substring(2, 15);
|
|
12
|
+
const newCode = code.replace(/\{cloudComponentStyleId\}/g, styleId);
|
|
12
13
|
return {
|
|
13
14
|
styleId,
|
|
14
15
|
useModule: new Function(`
|
|
@@ -23,7 +24,7 @@ export function genCommonJSRuntime({
|
|
|
23
24
|
}
|
|
24
25
|
};
|
|
25
26
|
/* cloud-component-code */
|
|
26
|
-
${
|
|
27
|
+
${newCode}
|
|
27
28
|
/* cloud-component-code */
|
|
28
29
|
return Object.assign(module.exports, exports);
|
|
29
30
|
`)
|
|
@@ -69,15 +70,14 @@ export default async function ImportCloudComponent(options) {
|
|
|
69
70
|
__dependencies[key] = value;
|
|
70
71
|
});
|
|
71
72
|
let module;
|
|
72
|
-
let styleId;
|
|
73
73
|
let useModule;
|
|
74
|
+
let styleId;
|
|
74
75
|
try {
|
|
75
76
|
const runtime = genCommonJSRuntime({
|
|
76
77
|
from
|
|
77
78
|
}, responseText);
|
|
78
|
-
styleId = runtime.styleId;
|
|
79
79
|
useModule = runtime.useModule;
|
|
80
|
-
|
|
80
|
+
styleId = runtime.styleId;
|
|
81
81
|
// 获取云组件导出CJS模块
|
|
82
82
|
module = useModule(__dependencies);
|
|
83
83
|
if (!module || typeof module !== 'object') {
|
|
@@ -106,7 +106,7 @@ export default async function ImportCloudComponent(options) {
|
|
|
106
106
|
// 返回组件CSJ模块
|
|
107
107
|
return {
|
|
108
108
|
module,
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
isNewVersion,
|
|
110
|
+
styleId
|
|
111
111
|
};
|
|
112
112
|
}
|