css2class 2.0.0

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.
Files changed (57) hide show
  1. package/API.md +1143 -0
  2. package/CHANGELOG.md +291 -0
  3. package/CONFIG.md +1096 -0
  4. package/CONTRIBUTING.md +571 -0
  5. package/MIGRATION.md +402 -0
  6. package/README.md +634 -0
  7. package/bin/class2css.js +380 -0
  8. package/class2css.config.js +124 -0
  9. package/common.css +3 -0
  10. package/configs/colors.config.js +62 -0
  11. package/configs/layout.config.js +110 -0
  12. package/configs/spacing.config.js +37 -0
  13. package/configs/typography.config.js +41 -0
  14. package/docs/.vitepress/config.mjs +65 -0
  15. package/docs/.vitepress/theme/custom.css +74 -0
  16. package/docs/.vitepress/theme/index.js +7 -0
  17. package/docs/guide/cli.md +97 -0
  18. package/docs/guide/concepts.md +63 -0
  19. package/docs/guide/config-template.md +365 -0
  20. package/docs/guide/config.md +275 -0
  21. package/docs/guide/faq.md +202 -0
  22. package/docs/guide/getting-started.md +83 -0
  23. package/docs/guide/important-and-static.md +67 -0
  24. package/docs/guide/incremental.md +162 -0
  25. package/docs/guide/rules-reference.md +354 -0
  26. package/docs/guide/units.md +57 -0
  27. package/docs/index.md +68 -0
  28. package/package.json +49 -0
  29. package/run.js +90 -0
  30. package/src/README.md +571 -0
  31. package/src/core/CacheManager.js +650 -0
  32. package/src/core/CompatibilityAdapter.js +264 -0
  33. package/src/core/ConfigManager.js +431 -0
  34. package/src/core/ConfigValidator.js +350 -0
  35. package/src/core/EventBus.js +77 -0
  36. package/src/core/FullScanManager.js +430 -0
  37. package/src/core/StateManager.js +631 -0
  38. package/src/docs/DocsServer.js +179 -0
  39. package/src/example.js +106 -0
  40. package/src/generators/DynamicClassGenerator.js +674 -0
  41. package/src/index.js +1046 -0
  42. package/src/parsers/ClassParser.js +572 -0
  43. package/src/parsers/ImportantParser.js +279 -0
  44. package/src/parsers/RegexCompiler.js +200 -0
  45. package/src/utils/ClassChangeTracker.js +366 -0
  46. package/src/utils/ConfigDiagnostics.js +673 -0
  47. package/src/utils/CssFormatter.js +261 -0
  48. package/src/utils/FileUtils.js +230 -0
  49. package/src/utils/Logger.js +150 -0
  50. package/src/utils/Throttle.js +172 -0
  51. package/src/utils/UnitProcessor.js +334 -0
  52. package/src/utils/WxssClassExtractor.js +137 -0
  53. package/src/watchers/ConfigWatcher.js +413 -0
  54. package/src/watchers/FileWatcher.js +133 -0
  55. package/src/writers/FileWriter.js +302 -0
  56. package/src/writers/UnifiedWriter.js +370 -0
  57. package/styles.config.js +250 -0
@@ -0,0 +1,366 @@
1
+ class ClassChangeTracker {
2
+ constructor(eventBus) {
3
+ this.eventBus = eventBus;
4
+ this.changeHistory = new Map(); // 存储文件的变化历史
5
+ this.maxHistorySize = 100; // 最大历史记录数
6
+ }
7
+
8
+ // 对比class变化
9
+ compareClasses(oldClasses, newClasses, filePath) {
10
+ const oldSet = new Set(oldClasses || []);
11
+ const newSet = new Set(newClasses || []);
12
+
13
+ const added = [...newSet].filter((cls) => !oldSet.has(cls));
14
+ const removed = [...oldSet].filter((cls) => !newSet.has(cls));
15
+ const unchanged = [...oldSet].filter((cls) => newSet.has(cls));
16
+
17
+ const changes = {
18
+ added,
19
+ removed,
20
+ unchanged,
21
+ total: {
22
+ old: oldSet.size,
23
+ new: newSet.size,
24
+ added: added.length,
25
+ removed: removed.length,
26
+ unchanged: unchanged.length,
27
+ },
28
+ timestamp: Date.now(),
29
+ filePath,
30
+ };
31
+
32
+ // 分析变化类型
33
+ changes.categories = this.categorizeChanges(changes);
34
+
35
+ // 分析变化原因
36
+ changes.reasons = this.analyzeChangeReasons(changes);
37
+
38
+ // 保存到历史记录
39
+ this.saveChangeHistory(changes, filePath);
40
+
41
+ // 发射变化事件
42
+ this.eventBus.emit('classChange:detected', changes);
43
+
44
+ return changes;
45
+ }
46
+
47
+ // 分类变化类型
48
+ categorizeChanges(changes) {
49
+ const categories = {
50
+ structural: [], // 结构变化(新增/删除class)
51
+ content: [], // 内容变化(class值修改)
52
+ style: [], // 样式相关变化
53
+ layout: [], // 布局相关变化
54
+ utility: [], // 工具类变化
55
+ };
56
+
57
+ const allChanged = [...changes.added, ...changes.removed];
58
+
59
+ allChanged.forEach((className) => {
60
+ if (this.isLayoutClass(className)) {
61
+ categories.layout.push(className);
62
+ } else if (this.isStyleClass(className)) {
63
+ categories.style.push(className);
64
+ } else if (this.isUtilityClass(className)) {
65
+ categories.utility.push(className);
66
+ } else {
67
+ categories.structural.push(className);
68
+ }
69
+ });
70
+
71
+ return categories;
72
+ }
73
+
74
+ // 分析变化原因
75
+ analyzeChangeReasons(changes) {
76
+ const reasons = {
77
+ layoutAdjustment: false,
78
+ styleUpdate: false,
79
+ responsiveDesign: false,
80
+ componentRefactor: false,
81
+ bugFix: false,
82
+ featureAddition: false,
83
+ };
84
+
85
+ const { added, removed } = changes;
86
+ const allChanged = [...added, ...removed];
87
+
88
+ // 检测布局调整
89
+ if (this.hasLayoutClasses(allChanged)) {
90
+ reasons.layoutAdjustment = true;
91
+ }
92
+
93
+ // 检测样式更新
94
+ if (this.hasStyleClasses(allChanged)) {
95
+ reasons.styleUpdate = true;
96
+ }
97
+
98
+ // 检测响应式设计
99
+ if (this.hasResponsiveClasses(allChanged)) {
100
+ reasons.responsiveDesign = true;
101
+ }
102
+
103
+ // 检测组件重构
104
+ if (added.length > 5 || removed.length > 5) {
105
+ reasons.componentRefactor = true;
106
+ }
107
+
108
+ // 检测功能添加
109
+ if (added.length > 0 && removed.length === 0) {
110
+ reasons.featureAddition = true;
111
+ }
112
+
113
+ return reasons;
114
+ }
115
+
116
+ // 判断是否为布局类
117
+ isLayoutClass(className) {
118
+ const layoutPatterns = [
119
+ /^(m|p|w|h|max-w|min-w|max-h|min-h|top|right|bottom|left|inset)/,
120
+ /^(flex|grid|block|inline|hidden|visible)/,
121
+ /^(absolute|relative|fixed|sticky)/,
122
+ /^(z-|gap-|space-)/,
123
+ /^(container|wrapper|layout)/,
124
+ ];
125
+
126
+ return layoutPatterns.some((pattern) => pattern.test(className));
127
+ }
128
+
129
+ // 判断是否为样式类
130
+ isStyleClass(className) {
131
+ const stylePatterns = [
132
+ /^(text-|font-|color-|bg-|border-)/,
133
+ /^(rounded|shadow|opacity|transition)/,
134
+ /^(hover:|focus:|active:|disabled:)/,
135
+ /^(transform|scale|rotate|translate)/,
136
+ ];
137
+
138
+ return stylePatterns.some((pattern) => pattern.test(className));
139
+ }
140
+
141
+ // 判断是否为工具类
142
+ isUtilityClass(className) {
143
+ const utilityPatterns = [
144
+ /^(cursor-|pointer-events|user-select)/,
145
+ /^(overflow|resize|select)/,
146
+ /^(whitespace|break|hyphens)/,
147
+ /^(list-|table-|caption)/,
148
+ ];
149
+
150
+ return utilityPatterns.some((pattern) => pattern.test(className));
151
+ }
152
+
153
+ // 检测布局类
154
+ hasLayoutClasses(classes) {
155
+ return classes.some((cls) => this.isLayoutClass(cls));
156
+ }
157
+
158
+ // 检测样式类
159
+ hasStyleClasses(classes) {
160
+ return classes.some((cls) => this.isStyleClass(cls));
161
+ }
162
+
163
+ // 检测响应式类
164
+ hasResponsiveClasses(classes) {
165
+ const responsivePatterns = [/^(sm:|md:|lg:|xl:|2xl:)/, /^(mobile:|tablet:|desktop:)/];
166
+
167
+ return classes.some((cls) => responsivePatterns.some((pattern) => pattern.test(cls)));
168
+ }
169
+
170
+ // 生成变化报告
171
+ generateReport(changes, options = {}) {
172
+ const {
173
+ includeDetails = true,
174
+ includeStatistics = true,
175
+ includeCategories = true,
176
+ includeReasons = true,
177
+ maxDisplayItems = 10,
178
+ } = options;
179
+
180
+ const report = {
181
+ summary: this.generateSummary(changes),
182
+ timestamp: changes.timestamp,
183
+ filePath: changes.filePath,
184
+ };
185
+
186
+ if (includeDetails) {
187
+ report.details = {
188
+ added: changes.added.slice(0, maxDisplayItems),
189
+ removed: changes.removed.slice(0, maxDisplayItems),
190
+ unchanged: changes.unchanged.slice(0, maxDisplayItems),
191
+ hasMore: {
192
+ added: changes.added.length > maxDisplayItems,
193
+ removed: changes.removed.length > maxDisplayItems,
194
+ unchanged: changes.unchanged.length > maxDisplayItems,
195
+ },
196
+ };
197
+ }
198
+
199
+ if (includeStatistics) {
200
+ report.statistics = changes.total;
201
+ }
202
+
203
+ if (includeCategories) {
204
+ report.categories = changes.categories;
205
+ }
206
+
207
+ if (includeReasons) {
208
+ report.reasons = changes.reasons;
209
+ }
210
+
211
+ return report;
212
+ }
213
+
214
+ // 生成摘要
215
+ generateSummary(changes) {
216
+ const { added, removed, total } = changes;
217
+
218
+ if (added.length === 0 && removed.length === 0) {
219
+ return 'No class changes detected';
220
+ }
221
+
222
+ let summary = '';
223
+
224
+ if (added.length > 0) {
225
+ summary += `Added ${added.length} class${added.length > 1 ? 'es' : ''}`;
226
+ }
227
+
228
+ if (removed.length > 0) {
229
+ if (summary) summary += ', ';
230
+ summary += `removed ${removed.length} class${removed.length > 1 ? 'es' : ''}`;
231
+ }
232
+
233
+ summary += ` (${total.old} → ${total.new})`;
234
+
235
+ return summary;
236
+ }
237
+
238
+ // 格式化变化显示
239
+ formatChanges(changes, options = {}) {
240
+ const { useColors = true, showDetails = true, maxItems = 5 } = options;
241
+
242
+ const format = {
243
+ summary: this.generateSummary(changes),
244
+ details: {},
245
+ };
246
+
247
+ if (showDetails) {
248
+ if (changes.added.length > 0) {
249
+ format.details.added = {
250
+ count: changes.added.length,
251
+ items: changes.added.slice(0, maxItems),
252
+ hasMore: changes.added.length > maxItems,
253
+ };
254
+ }
255
+
256
+ if (changes.removed.length > 0) {
257
+ format.details.removed = {
258
+ count: changes.removed.length,
259
+ items: changes.removed.slice(0, maxItems),
260
+ hasMore: changes.removed.length > maxItems,
261
+ };
262
+ }
263
+ }
264
+
265
+ if (useColors) {
266
+ format.colors = {
267
+ added: '\x1b[32m', // 绿色
268
+ removed: '\x1b[31m', // 红色
269
+ unchanged: '\x1b[37m', // 白色
270
+ reset: '\x1b[0m', // 重置
271
+ };
272
+ }
273
+
274
+ return format;
275
+ }
276
+
277
+ // 获取变化统计
278
+ getChangeStatistics(changes) {
279
+ return {
280
+ totalChanges: changes.added.length + changes.removed.length,
281
+ netChange: changes.added.length - changes.removed.length,
282
+ changeRatio:
283
+ changes.total.old > 0
284
+ ? (((changes.added.length + changes.removed.length) / changes.total.old) * 100).toFixed(
285
+ 2
286
+ ) + '%'
287
+ : '0%',
288
+ categories: Object.keys(changes.categories).filter(
289
+ (key) => changes.categories[key].length > 0
290
+ ),
291
+ reasons: Object.keys(changes.reasons).filter((key) => changes.reasons[key]),
292
+ };
293
+ }
294
+
295
+ // 保存变化历史
296
+ saveChangeHistory(changes, filePath) {
297
+ if (!this.changeHistory.has(filePath)) {
298
+ this.changeHistory.set(filePath, []);
299
+ }
300
+
301
+ const history = this.changeHistory.get(filePath);
302
+ history.push({
303
+ ...changes,
304
+ id: this.generateChangeId(),
305
+ timestamp: Date.now(),
306
+ });
307
+
308
+ // 限制历史记录大小
309
+ if (history.length > this.maxHistorySize) {
310
+ history.shift();
311
+ }
312
+
313
+ this.eventBus.emit('classChange:history:updated', {
314
+ filePath,
315
+ historySize: history.length,
316
+ });
317
+ }
318
+
319
+ // 获取变化历史
320
+ getChangeHistory(filePath, limit = 10) {
321
+ const history = this.changeHistory.get(filePath) || [];
322
+ return history.slice(-limit);
323
+ }
324
+
325
+ // 生成变化ID
326
+ generateChangeId() {
327
+ return `change_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
328
+ }
329
+
330
+ // 清理历史记录
331
+ clearHistory(filePath = null) {
332
+ if (filePath) {
333
+ this.changeHistory.delete(filePath);
334
+ } else {
335
+ this.changeHistory.clear();
336
+ }
337
+
338
+ this.eventBus.emit('classChange:history:cleared', { filePath });
339
+ }
340
+
341
+ // 获取统计信息
342
+ getStats() {
343
+ const totalFiles = this.changeHistory.size;
344
+ let totalChanges = 0;
345
+ let totalAdded = 0;
346
+ let totalRemoved = 0;
347
+
348
+ for (const [filePath, history] of this.changeHistory.entries()) {
349
+ totalChanges += history.length;
350
+ history.forEach((change) => {
351
+ totalAdded += change.added.length;
352
+ totalRemoved += change.removed.length;
353
+ });
354
+ }
355
+
356
+ return {
357
+ totalFiles,
358
+ totalChanges,
359
+ totalAdded,
360
+ totalRemoved,
361
+ averageChangesPerFile: totalFiles > 0 ? (totalChanges / totalFiles).toFixed(2) : 0,
362
+ };
363
+ }
364
+ }
365
+
366
+ module.exports = ClassChangeTracker;