py-test-component 1.0.11 → 2.0.2

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/DEVELOPER.md ADDED
@@ -0,0 +1,541 @@
1
+ # PyComponent 组件库开发指南
2
+
3
+ 本文档面向 AI 模型和开发者,指导如何开发、维护和扩展 `py-test-component` 组件库。
4
+
5
+ ## 目录
6
+
7
+ 1. [架构概述](#架构概述)
8
+ 2. [核心原理](#核心原理)
9
+ 3. [目录结构](#目录结构)
10
+ 4. [添加新组件步骤](#添加新组件步骤)
11
+ 5. [代码规范](#代码规范)
12
+ 6. [构建流程](#构建流程)
13
+ 7. [常见问题](#常见问题)
14
+
15
+ ---
16
+
17
+ ## 架构概述
18
+
19
+ ### 设计目标
20
+
21
+ - **跨框架**:Vue 2 组件可被 React 使用
22
+ - **零配置**:React 用户无需安装 Vue 和 ElementUI
23
+ - **单一入口**:所有组件统一使用 `propData` 接收参数
24
+ - **状态隔离**:组件库内部 store 不暴露给外部
25
+
26
+ ### 技术栈
27
+
28
+ | 技术 | 用途 |
29
+ |------|------|
30
+ | Vue 2.7 | 组件开发框架 |
31
+ | ElementUI 2.15 | UI 组件库 |
32
+ | vue-custom-element | Vue 组件转 Web Components |
33
+ | Webpack 5 | 构建工具 |
34
+
35
+ ---
36
+
37
+ ## 核心原理
38
+
39
+ ### 1. Web Components 转换
40
+
41
+ ```
42
+ Vue 组件 --[vue-custom-element]--> 自定义元素 --[React 包装器]--> React 组件
43
+ ```
44
+
45
+ **关键点**:
46
+ - `src/index.js` 使用 `Vue.customElement()` 将 Vue 组件注册为 Web Components
47
+ - Shadow DOM 必须禁用(`shadow: false`),否则 ElementUI 样式无法穿透
48
+
49
+ ### 2. React 适配机制
50
+
51
+ ```javascript
52
+ // src/react/index.js
53
+ function wrapVueComponent(tagName, dataProp) {
54
+ return forwardRef(function WrappedComponent({ propData, loading, ...props }, ref) {
55
+ // 1. 动态加载组件库构建产物
56
+ // 2. 自动注入 ElementUI CSS
57
+ // 3. 将 propData 设置到自定义元素的 property
58
+ return <tagName ref={ref} {...props} />;
59
+ });
60
+ }
61
+ ```
62
+
63
+ **关键点**:
64
+ - React 通过动态 `import()` 加载 `dist/py-component.esm.js`
65
+ - 数据通过直接设置 DOM property(而非 attribute)传递,避免 JSON 序列化
66
+
67
+ ### 3. Store 隔离设计
68
+
69
+ ```
70
+ 外部工程 --[initStore]--> 组件库内部 store
71
+
72
+ 外部无法:
73
+ - 读取 store 状态
74
+ - 订阅 store 变化
75
+ - 获取 store 实例
76
+ ```
77
+
78
+ **实现方式**:
79
+ - `initStore()` 只接受配置对象,不返回任何可操作对象
80
+ - React 版本中 `initStore` 返回 `Promise<void>`,而非 store 实例
81
+ - `window.PyComponent` 仅暴露 `initStore` 方法
82
+
83
+ ---
84
+
85
+ ## 目录结构
86
+
87
+ ```
88
+ py-component/
89
+ ├── dist/ # 构建产物(自动生成)
90
+ │ ├── py-component.js # UMD 格式
91
+ │ └── py-component.esm.js # ESM 格式
92
+
93
+ ├── src/
94
+ │ ├── components/ # Vue 组件源码
95
+ │ │ ├── PyTable.vue
96
+ │ │ └── PyWeather.vue
97
+ │ │
98
+ │ ├── react/ # React 适配器
99
+ │ │ └── index.js # 组件包装器 + initStore
100
+ │ │
101
+ │ ├── vue/ # Vue 入口
102
+ │ │ └── index.js # 直接导出 Vue 组件
103
+ │ │
104
+ │ ├── store/ # 全局状态(内部使用)
105
+ │ │ └── index.js # Vue.observable + get/set/subscribe
106
+ │ │
107
+ │ ├── utils/ # 工具函数
108
+ │ │ ├── request.js # fetch 封装
109
+ │ │ └── api.js # API 请求封装
110
+ │ │
111
+ │ └── index.js # 主入口
112
+ │ # - 注册 Web Components
113
+ │ # - 导出 initStore
114
+ │ # - 挂载到 window.PyComponent
115
+
116
+ ├── package.json
117
+ ├── webpack.config.js # 双格式构建配置
118
+ ├── README.md # 用户文档
119
+ ├── USAGE.md # 使用教程
120
+ └── DEVELOPER.md # 本文件
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 添加新组件步骤
126
+
127
+ ### 步骤 1:创建 Vue 组件
128
+
129
+ **文件**:`src/components/PyNewComponent.vue`
130
+
131
+ ```vue
132
+ <template>
133
+ <div class="py-new-component">
134
+ <!-- 使用 ElementUI 组件 -->
135
+ <el-input v-model="innerValue" />
136
+ </div>
137
+ </template>
138
+
139
+ <script>
140
+ // 从 store 获取配置(内部使用)
141
+ import store from '../store';
142
+
143
+ export default {
144
+ name: 'PyNewComponent',
145
+
146
+ // 统一使用 propData 接收外部数据
147
+ props: {
148
+ propData: {
149
+ default: null // 不限制类型
150
+ }
151
+ },
152
+
153
+ data() {
154
+ return {
155
+ innerValue: ''
156
+ };
157
+ },
158
+
159
+ computed: {
160
+ // 根据 propData 类型做适配
161
+ config() {
162
+ return this.propData || {};
163
+ }
164
+ },
165
+
166
+ mounted() {
167
+ // 读取 store 中的配置(内部使用)
168
+ const apiKey = store.get('apiKey');
169
+ console.log('apiKey:', apiKey);
170
+ },
171
+
172
+ methods: {
173
+ handleClick() {
174
+ // 触发事件供外部监听
175
+ this.$emit('change', this.innerValue);
176
+ }
177
+ }
178
+ };
179
+ </script>
180
+
181
+ <style scoped>
182
+ .py-new-component {
183
+ padding: 20px;
184
+ }
185
+ </style>
186
+ ```
187
+
188
+ **要点**:
189
+ - 组件名必须以 `Py` 开头
190
+ - 必须定义 `propData` prop,且不限制类型
191
+ - 使用 `scoped` 样式避免污染
192
+
193
+ ### 步骤 2:注册 Web Component
194
+
195
+ **文件**:`src/index.js`
196
+
197
+ ```javascript
198
+ import PyNewComponent from './components/PyNewComponent.vue';
199
+
200
+ // 注册为自定义元素
201
+ Vue.customElement('py-new-component', PyNewComponent, {
202
+ shadow: false // 必须禁用 Shadow DOM
203
+ });
204
+
205
+ // 如有需要,在 window.PyComponent 中保持引用(可选)
206
+ if (typeof window !== 'undefined') {
207
+ window.PyComponent = {
208
+ initStore,
209
+ // 不暴露 store 实例
210
+ };
211
+ }
212
+ ```
213
+
214
+ ### 步骤 3:Vue 入口导出
215
+
216
+ **文件**:`src/vue/index.js`
217
+
218
+ ```javascript
219
+ import PyNewComponent from '../components/PyNewComponent.vue';
220
+
221
+ export { PyTable, PyWeather, PyNewComponent, initStore };
222
+ export default { PyTable, PyWeather, PyNewComponent, initStore };
223
+ ```
224
+
225
+ ### 步骤 4:React 入口导出
226
+
227
+ **文件**:`src/react/index.js`
228
+
229
+ ```javascript
230
+ // 在已有组件后添加
231
+ export const PyNewComponent = wrapVueComponent('py-new-component', 'propData');
232
+ ```
233
+
234
+ **参数说明**:
235
+ - 第一个参数:自定义元素标签名(必须与 `Vue.customElement` 注册时一致)
236
+ - 第二个参数:数据 property 名(统一为 `'propData'`)
237
+
238
+ ### 步骤 5:构建验证
239
+
240
+ ```bash
241
+ npm run build
242
+ ```
243
+
244
+ 检查 `dist/` 目录下是否生成新的构建产物。
245
+
246
+ ### 步骤 6:更新文档
247
+
248
+ - `README.md`:组件列表中添加新组件
249
+ - `USAGE.md`:添加新组件的参数说明和使用示例
250
+
251
+ ---
252
+
253
+ ## 代码规范
254
+
255
+ ### 1. 组件命名
256
+
257
+ ```
258
+ Py + PascalCase
259
+
260
+ ✅ PyTable
261
+ ✅ PyWeather
262
+ ✅ PyUserForm
263
+
264
+ ❌ py-table (Vue 组件名用大驼峰)
265
+ ❌ PYTABLE (不要全大写)
266
+ ❌ NewComponent (缺少 Py 前缀)
267
+ ```
268
+
269
+ ### 2. Props 规范
270
+
271
+ ```javascript
272
+ // ✅ 正确:统一使用 propData
273
+ props: {
274
+ propData: {
275
+ default: null // 不限制类型,灵活使用
276
+ }
277
+ }
278
+
279
+ // ❌ 错误:不要使用多个 props
280
+ props: {
281
+ data: Array,
282
+ config: Object,
283
+ value: String
284
+ }
285
+ ```
286
+
287
+ ### 3. Store 使用规范
288
+
289
+ ```javascript
290
+ // ✅ 正确:组件内部读取 store
291
+ import store from '../store';
292
+
293
+ export default {
294
+ mounted() {
295
+ const config = store.get('apiKey'); // 读取
296
+ },
297
+ methods: {
298
+ update() {
299
+ store.set('key', value); // 内部组件也可以设置
300
+ }
301
+ }
302
+ };
303
+
304
+ // ❌ 错误:不要在外部暴露 store
305
+ export { store }; // 禁止!
306
+ ```
307
+
308
+ ### 4. CSS 规范
309
+
310
+ ```vue
311
+ <style scoped>
312
+ /* ✅ 使用 scoped */
313
+ .py-component-name {
314
+ /* 组件名作为前缀 */
315
+ }
316
+
317
+ /* ❌ 不要污染全局 */
318
+ .el-input {
319
+ /* 这会覆盖 ElementUI 默认样式 */
320
+ }
321
+ </style>
322
+ ```
323
+
324
+ ### 5. 事件规范
325
+
326
+ ```javascript
327
+ // ✅ 使用自定义事件与外部通信
328
+ this.$emit('change', value);
329
+ this.$emit('submit', formData);
330
+
331
+ // ❌ 不要直接操作外部状态
332
+ externalStore.data = value; // 禁止!
333
+ ```
334
+
335
+ ---
336
+
337
+ ## 构建流程
338
+
339
+ ### 开发模式
340
+
341
+ ```bash
342
+ npm run dev
343
+ ```
344
+
345
+ - Webpack 监听文件变化
346
+ - 自动重新构建到 `dist/`
347
+ - 不压缩,便于调试
348
+
349
+ ### 生产构建
350
+
351
+ ```bash
352
+ npm run build
353
+ ```
354
+
355
+ - 生成两种格式:
356
+ - `py-component.js`(UMD,浏览器直接使用)
357
+ - `py-component.esm.js`(ESM,构建工具使用)
358
+ - 代码压缩,体积优化
359
+
360
+ ### 构建配置要点
361
+
362
+ **`webpack.config.js`**:
363
+
364
+ ```javascript
365
+ // 关键配置
366
+ const baseConfig = {
367
+ module: {
368
+ rules: [
369
+ { test: /\.vue$/, loader: 'vue-loader' },
370
+ { test: /\.css$/, use: ['style-loader', 'css-loader'] }
371
+ ]
372
+ },
373
+ resolve: {
374
+ alias: {
375
+ 'vue$': 'vue/dist/vue.esm.js' // 使用完整版 Vue
376
+ }
377
+ }
378
+ };
379
+ ```
380
+
381
+ ---
382
+
383
+ ## 常见问题
384
+
385
+ ### Q1: React 中组件不显示
386
+
387
+ **排查步骤**:
388
+ 1. 检查 `dist/py-component.esm.js` 是否存在
389
+ 2. 检查浏览器控制台是否有加载错误
390
+ 3. 确认 CSS 是否已注入(`<link>` 标签是否出现在 `<head>`)
391
+ 4. 检查 `propData` 是否正确设置到 DOM property
392
+
393
+ ### Q2: ElementUI 样式不生效
394
+
395
+ **原因**:Shadow DOM 隔离了样式
396
+ **解决**:确保 `shadow: false`
397
+
398
+ ```javascript
399
+ Vue.customElement('py-component', Component, {
400
+ shadow: false // 必须
401
+ });
402
+ ```
403
+
404
+ ### Q3: propData 传递后不更新
405
+
406
+ **原因**:通过 attribute 传递了对象/数组,导致序列化问题
407
+ **解决**:使用 DOM property 而非 attribute
408
+
409
+ ```javascript
410
+ // ✅ 正确:直接设置 property
411
+ elRef.current.propData = data;
412
+
413
+ // ❌ 错误:通过 attribute(会导致 JSON 序列化)
414
+ elRef.current.setAttribute('propData', JSON.stringify(data));
415
+ ```
416
+
417
+ ### Q4: store 数据在 React 中无法响应
418
+
419
+ **原因**:Vue.observable 与 React 响应式系统不兼容
420
+ **设计如此**:store 完全内部化,React 只能设置不能读取
421
+ **替代方案**:通过组件事件与外部通信
422
+
423
+ ```javascript
424
+ // Vue 组件内
425
+ this.$emit('update', newValue);
426
+
427
+ // React 中使用
428
+ <PyComponent onUpdate={(e) => console.log(e.detail)} />
429
+ ```
430
+
431
+ ### Q5: 如何调试 Web Component
432
+
433
+ **浏览器控制台**:
434
+
435
+ ```javascript
436
+ // 查看已注册的自定义元素
437
+ customElements.get('py-table');
438
+
439
+ // 获取组件实例(Vue 内部)
440
+ document.querySelector('py-table').__vue__;
441
+
442
+ // 手动设置数据
443
+ document.querySelector('py-table').propData = [...];
444
+ ```
445
+
446
+ ---
447
+
448
+ ## 扩展建议
449
+
450
+ ### 添加 TypeScript 支持
451
+
452
+ 如需支持 TypeScript:
453
+
454
+ 1. 创建 `index.d.ts`:
455
+
456
+ ```typescript
457
+ declare module 'py-test-component' {
458
+ export function initStore(config: Record<string, any>): void;
459
+ }
460
+
461
+ declare module 'py-test-component/vue' {
462
+ import { Component } from 'vue';
463
+ export const PyTable: Component;
464
+ export const PyWeather: Component;
465
+ export function initStore(config: Record<string, any>): void;
466
+ }
467
+
468
+ declare module 'py-test-component/react' {
469
+ import { ForwardRefExoticComponent, RefAttributes } from 'react';
470
+
471
+ interface PyComponentProps {
472
+ propData?: any;
473
+ loading?: React.ReactNode;
474
+ }
475
+
476
+ export const PyTable: ForwardRefExoticComponent<PyComponentProps & RefAttributes<any>>;
477
+ export const PyWeather: ForwardRefExoticComponent<PyComponentProps & RefAttributes<any>>;
478
+ export function initStore(config: Record<string, any>): Promise<void>;
479
+ }
480
+ ```
481
+
482
+ 2. 在 `package.json` 中添加:
483
+
484
+ ```json
485
+ {
486
+ "types": "index.d.ts"
487
+ }
488
+ ```
489
+
490
+ ### 优化构建体积
491
+
492
+ 当前构建产物包含完整 Vue 和 ElementUI(约 1.1MB)。如需优化:
493
+
494
+ 1. **按需加载 ElementUI 组件**:
495
+
496
+ ```javascript
497
+ // 不使用完整导入
498
+ // import ElementUI from 'element-ui'; // 太大
499
+
500
+ // 改为按需导入
501
+ import { Table, TableColumn } from 'element-ui';
502
+ Vue.use(Table);
503
+ Vue.use(TableColumn);
504
+ ```
505
+
506
+ 2. **配置 Externals**(如果外部工程已引入 Vue):
507
+
508
+ ```javascript
509
+ // webpack.config.js
510
+ module.exports = {
511
+ externals: {
512
+ vue: 'Vue',
513
+ 'element-ui': 'ELEMENT'
514
+ }
515
+ };
516
+ ```
517
+
518
+ ---
519
+
520
+ ## 总结
521
+
522
+ 开发新组件的核心流程:
523
+
524
+ 1. **Vue 组件** → `src/components/PyXxx.vue`
525
+ 2. **Web Component 注册** → `src/index.js`
526
+ 3. **Vue 入口导出** → `src/vue/index.js`
527
+ 4. **React 入口导出** → `src/react/index.js`
528
+ 5. **构建验证** → `npm run build`
529
+ 6. **更新文档** → `README.md` + `USAGE.md`
530
+
531
+ **必须遵守的原则**:
532
+ - ✅ 统一使用 `propData` 传递数据
533
+ - ✅ Store 完全内部化,外部只能设置
534
+ - ✅ Web Component 禁用 Shadow DOM
535
+ - ✅ 组件名以 `Py` 开头
536
+
537
+ ---
538
+
539
+ **维护者备注**:
540
+
541
+ 本文档应与代码同步更新。如修改了核心机制(如 store 设计、数据传递方式),必须同步更新本文档。