@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 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
 
@@ -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
  // 组件引用
@@ -24,7 +24,7 @@ export function useUnMountCloudComponentStyle(styleId) {
24
24
  * @note 用户自定义依赖项会覆盖同名的默认依赖项
25
25
  */
26
26
  export default function CloudComponent(props) {
27
- let styleId;
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
- styleId: _styleId,
110
- isNewVersion
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
- styleId = _styleId;
121
+
122
+ // 优先使用导入云组件时生成的styleId,如果导入云组件时没有生成styleId,则使用模块info中的cloudComponentStyleId
123
+ styleId.current = _styleId || module?.info?.cloudComponentStyleId;
118
124
  setComponent(() => {
119
125
  onLoadEndRef.current && onLoadEndRef.current();
120
- return isNewVersion ? module.default({
126
+ const $module = isNewVersion ? module.default({
121
127
  // 组件引用
122
128
  ref: componentRef,
123
129
  // 将依赖项穿透下去,用于在组件内部使用依赖项(支持云组件嵌套云组件)
124
130
  $$dependencies: stableDependencies
125
- }) : module.default;
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
- }, [from, cache, headers, shouldReloadDependencies]);
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
- return {
151
- detail: {
152
- componentKey
153
- },
154
- ...jjbCommonLib.tools.toObject(componentProps)
155
- };
156
- }, [componentKey, JSON.stringify(componentProps)]);
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 = tools.createOnlyKey();
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
- ${code.replace(/\{cloudComponentStyleId\}/g, styleId)}
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
- styleId,
110
- isNewVersion
109
+ isNewVersion,
110
+ styleId
111
111
  };
112
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cqsjjb/jjb-cloud-component",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "前端-云组件",
5
5
  "main": "index.js",
6
6
  "scripts": {