css2class 2.0.0 → 2.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.
Files changed (40) hide show
  1. package/.github/workflows/deploy-docs.yml +53 -0
  2. package/.head_config.mjs +68 -0
  3. package/CONFIG.md +38 -1
  4. package/README.md +595 -633
  5. package/bin/class2css.js +32 -4
  6. package/common.css +1 -1
  7. package/configs/typography.config.js +1 -0
  8. package/docs/.vitepress/config.mjs +68 -65
  9. package/docs/guide/cli.md +97 -97
  10. package/docs/guide/config-template.md +16 -1
  11. package/docs/guide/config.md +129 -64
  12. package/docs/guide/faq.md +202 -202
  13. package/docs/guide/getting-started.md +86 -83
  14. package/docs/guide/incremental.md +164 -162
  15. package/docs/guide/rules-reference.md +73 -1
  16. package/docs/index.md +71 -68
  17. package/examples/weapp/README.md +15 -0
  18. package/examples/weapp/class2css.config.js +70 -0
  19. package/examples/weapp/src/placeholder.wxml +0 -0
  20. package/examples/weapp/styles.config.js +201 -0
  21. package/examples/web/README.md +25 -0
  22. package/examples/web/class2css.config.js +70 -0
  23. package/examples/web/demo.html +105 -0
  24. package/examples/web/src/placeholder.html +5 -0
  25. package/examples/web/styles.config.js +201 -0
  26. package/package.json +7 -2
  27. package/src/README.md +99 -0
  28. package/src/core/ConfigManager.js +440 -431
  29. package/src/core/FullScanManager.js +438 -430
  30. package/src/generators/DynamicClassGenerator.js +270 -72
  31. package/src/index.js +1091 -1046
  32. package/src/parsers/ClassParser.js +8 -2
  33. package/src/utils/CssFormatter.js +400 -47
  34. package/src/utils/UnitProcessor.js +4 -3
  35. package/src/watchers/ConfigWatcher.js +413 -413
  36. package/src/watchers/FileWatcher.js +148 -133
  37. package/src/writers/FileWriter.js +444 -302
  38. package/src/writers/UnifiedWriter.js +413 -370
  39. package/class2css.config.js +0 -124
  40. package/styles.config.js +0 -250
@@ -1,370 +1,413 @@
1
- class UnifiedWriter {
2
- constructor(eventBus, configManager, dynamicClassGenerator) {
3
- this.eventBus = eventBus;
4
- this.configManager = configManager;
5
- this.dynamicClassGenerator = dynamicClassGenerator;
6
- this.writeTimer = null;
7
- this.debounceDelay = 300;
8
- this.pendingWrites = new Set();
9
-
10
- // 初始化CSS格式化器
11
- const CssFormatter = require('../utils/CssFormatter');
12
- const cssFormat = this.configManager.getCssFormat();
13
- this.cssFormatter = new CssFormatter(cssFormat);
14
- }
15
-
16
- // 防抖写入统一文件
17
- debouncedWrite(fullScanManager, fileWriter, triggerFile = null) {
18
- // 清除现有的定时器
19
- if (this.writeTimer) {
20
- clearTimeout(this.writeTimer);
21
- }
22
-
23
- // 记录触发文件
24
- if (triggerFile) {
25
- this.pendingWrites.add(triggerFile);
26
- }
27
-
28
- this.eventBus.emit('unifiedWriter:debounced', {
29
- triggerFile,
30
- pendingCount: this.pendingWrites.size,
31
- delay: this.debounceDelay,
32
- });
33
-
34
- // 设置新的定时器
35
- this.writeTimer = setTimeout(async () => {
36
- try {
37
- await this.executeWrite(fullScanManager, fileWriter);
38
- } catch (error) {
39
- this.eventBus.emit('unifiedWriter:error', {
40
- error: error.message,
41
- pendingWrites: Array.from(this.pendingWrites),
42
- });
43
- } finally {
44
- this.pendingWrites.clear();
45
- this.writeTimer = null;
46
- }
47
- }, this.debounceDelay);
48
- }
49
-
50
- // 执行实际的写入操作
51
- async executeWrite(fullScanManager, fileWriter) {
52
- this.eventBus.emit('unifiedWriter:started', {
53
- pendingFiles: Array.from(this.pendingWrites),
54
- });
55
-
56
- // 等待全量数据锁定
57
- const isLocked = await fullScanManager.waitForDataLock();
58
-
59
- if (!isLocked) {
60
- this.eventBus.emit('unifiedWriter:warning', {
61
- message: 'Full scan data not locked, using available data',
62
- });
63
- }
64
-
65
- // 获取合并后的完整数据
66
- const mergedData = fullScanManager.getMergedData();
67
-
68
- this.eventBus.emit('unifiedWriter:dataReady', {
69
- classCount: mergedData.classListSet.size,
70
- staticClassCount: mergedData.userStaticClassListSet.size,
71
- fileCount: mergedData.fileCount,
72
- isLocked: mergedData.isLocked,
73
- });
74
-
75
- // 生成统一CSS内容
76
- const cssContent = await this.generateUnifiedCSS(
77
- mergedData.classListSet,
78
- mergedData.userStaticClassListSet
79
- );
80
-
81
- // 获取统一文件配置
82
- const multiFile = this.configManager.getMultiFile();
83
- const outputConfig = multiFile.output;
84
- const dummyFilePath = 'unified-output'; // 用于触发写入的虚拟文件路径
85
-
86
- // 写入统一文件
87
- await fileWriter.writeCSS(cssContent, dummyFilePath, {
88
- forceUniFile: true,
89
- outputPath: outputConfig.path,
90
- fileName: outputConfig.fileName,
91
- });
92
-
93
- this.eventBus.emit('unifiedWriter:completed', {
94
- cssLength: cssContent.length,
95
- classCount: mergedData.classListSet.size,
96
- staticClassCount: mergedData.userStaticClassListSet.size,
97
- processedFiles: Array.from(this.pendingWrites),
98
- });
99
- }
100
-
101
- // 生成完整CSS内容
102
- async generateUnifiedCSS(classListSet, userStaticClassListSet) {
103
- try {
104
- this.eventBus.emit('unifiedWriter:cssGeneration:started', {
105
- classCount: classListSet.size,
106
- staticClassCount: userStaticClassListSet.size,
107
- });
108
-
109
- // 清空防重复写入缓存(类似原始代码中的 cssWrite.clear())
110
- this.clearGenerationCache();
111
-
112
- // 生成动态CSS
113
- const dynamicResult = this.dynamicClassGenerator.getClassList(Array.from(classListSet));
114
-
115
- // 生成用户基础类CSS
116
- const userBaseResult = this.dynamicClassGenerator.createUserBaseClassList(
117
- dynamicResult.userBaseClassArr
118
- );
119
-
120
- // 生成静态类CSS
121
- const staticResult = await this.generateStaticCSS(Array.from(userStaticClassListSet));
122
-
123
- // 获取共用CSS内容
124
- const commonCssContent = await this.configManager.getCommonCssContent();
125
-
126
- // 合并所有CSS内容(共用CSS前置)
127
- let cssContent = [commonCssContent, dynamicResult.cssStr, staticResult, userBaseResult]
128
- .filter(Boolean)
129
- .join('\n');
130
-
131
- // 如果启用了排序,对CSS规则进行字母排序(在格式化之前排序)
132
- const sortClasses = this.configManager.getSortClasses();
133
- if (sortClasses) {
134
- cssContent = this.cssFormatter.sortCSSRules(cssContent);
135
- }
136
-
137
- // 根据配置的格式对整个CSS进行格式化处理
138
- const cssFormat = this.configManager.getCssFormat();
139
- cssContent = this.cssFormatter.formatCSS(cssContent, cssFormat);
140
-
141
- this.eventBus.emit('unifiedWriter:cssGeneration:completed', {
142
- dynamicCssLength: dynamicResult.cssStr.length,
143
- staticCssLength: staticResult.length,
144
- userBaseCssLength: userBaseResult.length,
145
- totalCssLength: cssContent.length,
146
- cssFormat: cssFormat,
147
- });
148
-
149
- return cssContent;
150
- } catch (error) {
151
- this.eventBus.emit('unifiedWriter:cssGeneration:error', {
152
- error: error.message,
153
- });
154
- throw error;
155
- }
156
- }
157
-
158
- // 提取 base class(去除响应式前缀等变体前缀)
159
- extractBaseClass(className) {
160
- if (!className || typeof className !== 'string') {
161
- return className;
162
- }
163
-
164
- // 按 : 拆分,取最后一段作为 base class
165
- const parts = className.split(':');
166
- return parts[parts.length - 1];
167
- }
168
-
169
- // 解析响应式变体前缀(如 sm:, md: 等)
170
- parseResponsiveVariant(className) {
171
- if (!className || typeof className !== 'string') {
172
- return { variant: null, baseClass: className };
173
- }
174
-
175
- const variants = this.configManager.getVariants();
176
- const responsiveVariants = variants.responsive || [];
177
-
178
- // 按 : 拆分,检查第一部分是否是响应式变体
179
- const parts = className.split(':');
180
- if (parts.length >= 2) {
181
- const potentialVariant = parts[0];
182
- if (responsiveVariants.includes(potentialVariant)) {
183
- const baseClass = parts.slice(1).join(':');
184
- return { variant: potentialVariant, baseClass };
185
- }
186
- }
187
-
188
- return { variant: null, baseClass: className };
189
- }
190
-
191
- // @media 查询包裹 CSS 规则
192
- wrapWithMediaQuery(cssRule, variant) {
193
- const breakpoints = this.configManager.getBreakpoints();
194
- const breakpoint = breakpoints[variant];
195
-
196
- if (!breakpoint) {
197
- this.eventBus.emit('unifiedWriter:warning', {
198
- warning: `Breakpoint not found for variant: ${variant}`,
199
- });
200
- return cssRule; // 如果没有找到断点,返回原始规则
201
- }
202
-
203
- const cssFormat = this.configManager.getCssFormat();
204
- const isCompressed = cssFormat === 'compressed';
205
- const isSingleLine = cssFormat === 'singleLine';
206
-
207
- if (isCompressed) {
208
- // 压缩格式:@media(min-width:640px){.sm\:flex{display:flex}}
209
- return `@media(min-width:${breakpoint}){${cssRule.trim()}}`;
210
- } else if (isSingleLine) {
211
- // 单行格式:@media (min-width: 640px) { .sm\:flex { display: flex; } }
212
- return `@media (min-width: ${breakpoint}) { ${cssRule.trim()} }\n`;
213
- } else {
214
- // 多行格式
215
- return `@media (min-width: ${breakpoint}) {\n${cssRule.trim()}\n}\n`;
216
- }
217
- }
218
-
219
- // 生成静态类CSS(模拟原始代码中的 creatStaticClass)
220
- async generateStaticCSS(userStaticClassArr) {
221
- if (!Array.isArray(userStaticClassArr) || userStaticClassArr.length === 0) {
222
- return '';
223
- }
224
-
225
- try {
226
- const userStaticClass = this.configManager.getUserStaticClass();
227
- const cssWrite = new Set(); // 防重复集合
228
- let str = '';
229
-
230
- this.eventBus.emit('unifiedWriter:staticGeneration:started', {
231
- classCount: userStaticClassArr.length,
232
- });
233
-
234
- userStaticClassArr.forEach((className) => {
235
- if (cssWrite.has(className)) {
236
- return; // 跳过重复的类
237
- }
238
-
239
- // 解析响应式变体
240
- const { variant, baseClass } = this.parseResponsiveVariant(className);
241
-
242
- // 使用 base class 查找静态类定义
243
- const staticClassItem = userStaticClass.find(([k, v]) => k === baseClass);
244
-
245
- if (staticClassItem !== undefined) {
246
- cssWrite.add(className);
247
- // 使用原始 class 名(包含响应式前缀)生成 CSS
248
- const cssClassName = className.replaceAll('_', '-');
249
- // 使用格式化器格式化CSS
250
- let cssRule = this.cssFormatter.formatRule(cssClassName, staticClassItem[1]);
251
-
252
- // 如果有响应式变体,用 @media 包裹
253
- if (variant) {
254
- cssRule = this.wrapWithMediaQuery(cssRule, variant);
255
- }
256
-
257
- str += cssRule;
258
- }
259
- });
260
-
261
- this.eventBus.emit('unifiedWriter:staticGeneration:completed', {
262
- generatedCount: cssWrite.size,
263
- cssLength: str.length,
264
- });
265
-
266
- return str;
267
- } catch (error) {
268
- this.eventBus.emit('unifiedWriter:staticGeneration:error', {
269
- error: error.message,
270
- });
271
- return '';
272
- }
273
- }
274
-
275
- // 清空生成缓存
276
- clearGenerationCache() {
277
- // 这里可以清空任何需要重置的生成缓存
278
- // 例如在动态生成器中的重复检查缓存
279
- this.eventBus.emit('unifiedWriter:cacheCleared');
280
- }
281
-
282
- // 取消待处理的写入
283
- cancelPendingWrites() {
284
- if (this.writeTimer) {
285
- clearTimeout(this.writeTimer);
286
- this.writeTimer = null;
287
- }
288
-
289
- const cancelledCount = this.pendingWrites.size;
290
- this.pendingWrites.clear();
291
-
292
- this.eventBus.emit('unifiedWriter:cancelled', {
293
- cancelledCount,
294
- });
295
- }
296
-
297
- // 立即执行写入(跳过防抖)
298
- async immediateWrite(fullScanManager, fileWriter, triggerFile = null) {
299
- // 取消防抖定时器
300
- this.cancelPendingWrites();
301
-
302
- // 立即执行写入
303
- if (triggerFile) {
304
- this.pendingWrites.add(triggerFile);
305
- }
306
-
307
- await this.executeWrite(fullScanManager, fileWriter);
308
- }
309
-
310
- // 获取写入状态
311
- getWriteStats() {
312
- return {
313
- isWritePending: this.writeTimer !== null,
314
- pendingWriteCount: this.pendingWrites.size,
315
- pendingFiles: Array.from(this.pendingWrites),
316
- debounceDelay: this.debounceDelay,
317
- };
318
- }
319
-
320
- // 设置防抖延迟
321
- setDebounceDelay(delay) {
322
- this.debounceDelay = Math.max(100, Math.min(5000, delay)); // 限制在100ms-5s之间
323
- this.eventBus.emit('unifiedWriter:debounceDelayChanged', {
324
- newDelay: this.debounceDelay,
325
- });
326
- }
327
-
328
- // 验证统一写入配置
329
- validateConfig() {
330
- const errors = [];
331
- const warnings = [];
332
-
333
- const multiFile = this.configManager.getMultiFile();
334
- if (!multiFile) {
335
- errors.push('MultiFile configuration is required for unified writing');
336
- return { errors, warnings, isValid: false };
337
- }
338
-
339
- const outputConfig = multiFile.output;
340
- if (!outputConfig) {
341
- errors.push('Output configuration is required');
342
- return { errors, warnings, isValid: false };
343
- }
344
-
345
- if (outputConfig.cssOutType !== 'uniFile') {
346
- warnings.push(`CSS output type is '${outputConfig.cssOutType}', expected 'uniFile'`);
347
- }
348
-
349
- if (!outputConfig.path) {
350
- errors.push('Output path is required for unified file writing');
351
- }
352
-
353
- if (!outputConfig.fileName) {
354
- warnings.push('Output file name not specified, will use default');
355
- }
356
-
357
- return { errors, warnings, isValid: errors.length === 0 };
358
- }
359
-
360
- // 调试信息
361
- debug() {
362
- return {
363
- writeStats: this.getWriteStats(),
364
- configValidation: this.validateConfig(),
365
- config: this.configManager.getMultiFile()?.output,
366
- };
367
- }
368
- }
369
-
370
- module.exports = UnifiedWriter;
1
+ class UnifiedWriter {
2
+ constructor(eventBus, configManager, dynamicClassGenerator) {
3
+ this.eventBus = eventBus;
4
+ this.configManager = configManager;
5
+ this.dynamicClassGenerator = dynamicClassGenerator;
6
+ this.writeTimer = null;
7
+ this.debounceDelay = 300;
8
+ this.pendingWrites = new Set();
9
+
10
+ // 初始化CSS格式化器
11
+ const CssFormatter = require('../utils/CssFormatter');
12
+ const cssFormat = this.configManager.getCssFormat();
13
+ this.cssFormatter = new CssFormatter(cssFormat);
14
+ }
15
+
16
+ // 防抖写入统一文件
17
+ debouncedWrite(fullScanManager, fileWriter, triggerFile = null) {
18
+ // 清除现有的定时器
19
+ if (this.writeTimer) {
20
+ clearTimeout(this.writeTimer);
21
+ }
22
+
23
+ // 记录触发文件
24
+ if (triggerFile) {
25
+ this.pendingWrites.add(triggerFile);
26
+ }
27
+
28
+ this.eventBus.emit('unifiedWriter:debounced', {
29
+ triggerFile,
30
+ pendingCount: this.pendingWrites.size,
31
+ delay: this.debounceDelay,
32
+ });
33
+
34
+ // 设置新的定时器
35
+ this.writeTimer = setTimeout(async () => {
36
+ try {
37
+ await this.executeWrite(fullScanManager, fileWriter);
38
+ } catch (error) {
39
+ this.eventBus.emit('unifiedWriter:error', {
40
+ error: error.message,
41
+ pendingWrites: Array.from(this.pendingWrites),
42
+ });
43
+ } finally {
44
+ this.pendingWrites.clear();
45
+ this.writeTimer = null;
46
+ }
47
+ }, this.debounceDelay);
48
+ }
49
+
50
+ // 执行实际的写入操作
51
+ async executeWrite(fullScanManager, fileWriter) {
52
+ this.eventBus.emit('unifiedWriter:started', {
53
+ pendingFiles: Array.from(this.pendingWrites),
54
+ });
55
+
56
+ // 等待全量数据锁定
57
+ const isLocked = await fullScanManager.waitForDataLock();
58
+
59
+ if (!isLocked) {
60
+ this.eventBus.emit('unifiedWriter:warning', {
61
+ message: 'Full scan data not locked, using available data',
62
+ });
63
+ }
64
+
65
+ // 获取合并后的完整数据
66
+ const mergedData = fullScanManager.getMergedData();
67
+
68
+ this.eventBus.emit('unifiedWriter:dataReady', {
69
+ classCount: mergedData.classListSet.size,
70
+ staticClassCount: mergedData.userStaticClassListSet.size,
71
+ fileCount: mergedData.fileCount,
72
+ isLocked: mergedData.isLocked,
73
+ });
74
+
75
+ // 生成统一CSS内容
76
+ const cssContent = await this.generateUnifiedCSS(
77
+ mergedData.classListSet,
78
+ mergedData.userStaticClassListSet
79
+ );
80
+
81
+ // 获取统一文件配置
82
+ const multiFile = this.configManager.getMultiFile();
83
+ const outputConfig = multiFile.output;
84
+ const dummyFilePath = 'unified-output'; // 用于触发写入的虚拟文件路径
85
+
86
+ // 写入统一文件
87
+ await fileWriter.writeCSS(cssContent, dummyFilePath, {
88
+ forceUniFile: true,
89
+ outputPath: outputConfig.path,
90
+ fileName: outputConfig.fileName,
91
+ });
92
+
93
+ this.eventBus.emit('unifiedWriter:completed', {
94
+ cssLength: cssContent.length,
95
+ classCount: mergedData.classListSet.size,
96
+ staticClassCount: mergedData.userStaticClassListSet.size,
97
+ processedFiles: Array.from(this.pendingWrites),
98
+ });
99
+ }
100
+
101
+ // 生成完整CSS内容
102
+ async generateUnifiedCSS(classListSet, userStaticClassListSet) {
103
+ try {
104
+ this.eventBus.emit('unifiedWriter:cssGeneration:started', {
105
+ classCount: classListSet.size,
106
+ staticClassCount: userStaticClassListSet.size,
107
+ });
108
+
109
+ // 清空防重复写入缓存(类似原始代码中的 cssWrite.clear())
110
+ this.clearGenerationCache();
111
+
112
+ // 生成动态CSS
113
+ const dynamicResult = this.dynamicClassGenerator.getClassList(Array.from(classListSet));
114
+
115
+ // 生成用户基础类CSS
116
+ const userBaseResult = this.dynamicClassGenerator.createUserBaseClassList(
117
+ dynamicResult.userBaseClassArr
118
+ );
119
+
120
+ // 生成静态类CSS
121
+ const staticResult = await this.generateStaticCSS(Array.from(userStaticClassListSet));
122
+
123
+ // 获取共用CSS内容
124
+ const commonCssContent = await this.configManager.getCommonCssContent();
125
+
126
+ // 合并所有CSS内容(共用CSS前置)
127
+ let cssContent = [commonCssContent, dynamicResult.cssStr, staticResult, userBaseResult]
128
+ .filter(Boolean)
129
+ .join('\n');
130
+
131
+ // 规范化响应式顺序:保证 base 在前、@media 在后,避免覆盖导致“看起来失效”
132
+ cssContent = this.cssFormatter.normalizeResponsiveOrder(cssContent);
133
+
134
+ // 如果启用了排序,对CSS规则进行字母排序(在格式化之前排序)
135
+ const sortClasses = this.configManager.getSortClasses();
136
+ if (sortClasses) {
137
+ cssContent = this.cssFormatter.sortCSSRules(cssContent);
138
+ }
139
+
140
+ // 根据配置的格式对整个CSS进行格式化处理
141
+ const cssFormat = this.configManager.getCssFormat();
142
+ cssContent = this.cssFormatter.formatCSS(cssContent, cssFormat);
143
+
144
+ this.eventBus.emit('unifiedWriter:cssGeneration:completed', {
145
+ dynamicCssLength: dynamicResult.cssStr.length,
146
+ staticCssLength: staticResult.length,
147
+ userBaseCssLength: userBaseResult.length,
148
+ totalCssLength: cssContent.length,
149
+ cssFormat: cssFormat,
150
+ });
151
+
152
+ return cssContent;
153
+ } catch (error) {
154
+ this.eventBus.emit('unifiedWriter:cssGeneration:error', {
155
+ error: error.message,
156
+ });
157
+ throw error;
158
+ }
159
+ }
160
+
161
+ // 提取 base class(去除响应式前缀等变体前缀)
162
+ extractBaseClass(className) {
163
+ if (!className || typeof className !== 'string') {
164
+ return className;
165
+ }
166
+
167
+ // 按 : 拆分,取最后一段作为 base class
168
+ const parts = className.split(':');
169
+ return parts[parts.length - 1];
170
+ }
171
+
172
+ // 解析响应式变体前缀(如 sm:, md: 等)
173
+ // 保留向后兼容,内部调用 parseVariants
174
+ parseResponsiveVariant(className) {
175
+ const result = this.parseVariants(className);
176
+ return { variant: result.responsiveVariant, baseClass: result.baseClass };
177
+ }
178
+
179
+ // 解析所有变体(响应式 + 状态变体如 hover/focus/active)
180
+ // 返回 { responsiveVariant, stateVariants, baseClass }
181
+ parseVariants(className) {
182
+ if (!className || typeof className !== 'string') {
183
+ return { responsiveVariant: null, stateVariants: [], baseClass: className };
184
+ }
185
+
186
+ const variants = this.configManager.getVariants();
187
+ const responsiveVariants = variants.responsive || [];
188
+ const stateVariants = variants.states || [];
189
+
190
+ // 按 : 拆分前缀链
191
+ const parts = className.split(':');
192
+ if (parts.length < 2) {
193
+ return { responsiveVariant: null, stateVariants: [], baseClass: className };
194
+ }
195
+
196
+ let responsiveVariant = null;
197
+ const foundStateVariants = [];
198
+ let baseClass = className;
199
+
200
+ // 从前往后解析:最多一个响应式变体(通常在链的最前面),可以有多个状态变体
201
+ let i = 0;
202
+ while (i < parts.length - 1) {
203
+ const potentialVariant = parts[i];
204
+
205
+ // 检查是否是响应式变体(只取第一个)
206
+ if (!responsiveVariant && responsiveVariants.includes(potentialVariant)) {
207
+ responsiveVariant = potentialVariant;
208
+ i++;
209
+ continue;
210
+ }
211
+
212
+ // 检查是否是状态变体
213
+ if (stateVariants.includes(potentialVariant)) {
214
+ foundStateVariants.push(potentialVariant);
215
+ i++;
216
+ continue;
217
+ }
218
+
219
+ // 遇到未知变体,停止解析,剩余部分作为 baseClass
220
+ break;
221
+ }
222
+
223
+ // baseClass 是最后一部分(或剩余部分)
224
+ baseClass = parts.slice(i).join(':');
225
+
226
+ return {
227
+ responsiveVariant,
228
+ stateVariants: foundStateVariants,
229
+ baseClass,
230
+ };
231
+ }
232
+
233
+ // 用 @media 查询包裹 CSS 规则
234
+ wrapWithMediaQuery(cssRule, responsiveVariant) {
235
+ const breakpoints = this.configManager.getBreakpoints();
236
+ const breakpoint = breakpoints[responsiveVariant];
237
+
238
+ if (!breakpoint) {
239
+ this.eventBus.emit('unifiedWriter:warning', {
240
+ warning: `Breakpoint not found for variant: ${responsiveVariant}`,
241
+ });
242
+ return cssRule; // 如果没有找到断点,返回原始规则
243
+ }
244
+
245
+ const cssFormat = this.configManager.getCssFormat();
246
+ const isCompressed = cssFormat === 'compressed';
247
+ const isSingleLine = cssFormat === 'singleLine';
248
+
249
+ if (isCompressed) {
250
+ // 压缩格式:@media(min-width:640px){.sm\:flex{display:flex}}
251
+ return `@media(min-width:${breakpoint}){${cssRule.trim()}}`;
252
+ } else if (isSingleLine) {
253
+ // 单行格式:@media (min-width: 640px) { .sm\:flex { display: flex; } }
254
+ return `@media (min-width: ${breakpoint}) { ${cssRule.trim()} }\n`;
255
+ } else {
256
+ // 多行格式
257
+ return `@media (min-width: ${breakpoint}) {\n${cssRule.trim()}\n}\n`;
258
+ }
259
+ }
260
+
261
+ // 生成静态类CSS(模拟原始代码中的 creatStaticClass)
262
+ async generateStaticCSS(userStaticClassArr) {
263
+ if (!Array.isArray(userStaticClassArr) || userStaticClassArr.length === 0) {
264
+ return '';
265
+ }
266
+
267
+ try {
268
+ // 使用 Map 索引进行 O(1) 查找,避免每个 class 都线性 find
269
+ const baseClassNameMap = this.configManager.getBaseClassNameMap();
270
+ const cssWrite = new Set(); // 防重复集合
271
+ let str = '';
272
+
273
+ this.eventBus.emit('unifiedWriter:staticGeneration:started', {
274
+ classCount: userStaticClassArr.length,
275
+ });
276
+
277
+ userStaticClassArr.forEach((className) => {
278
+ if (cssWrite.has(className)) {
279
+ return; // 跳过重复的类
280
+ }
281
+
282
+ // 解析所有变体(响应式 + 状态)
283
+ const { responsiveVariant, stateVariants, baseClass } = this.parseVariants(className);
284
+
285
+ // 使用 base class 查找静态类定义(static 类的 value 应该是 string)
286
+ const staticValue = baseClassNameMap ? baseClassNameMap.get(baseClass) : undefined;
287
+
288
+ if (typeof staticValue === 'string') {
289
+ cssWrite.add(className);
290
+ // 使用原始 class 名(包含所有变体前缀)生成 CSS
291
+ const cssClassName = className.replaceAll('_', '-');
292
+ // 使用格式化器格式化CSS(传入状态变体用于生成伪类选择器)
293
+ let cssRule = this.cssFormatter.formatRule(cssClassName, staticValue, null, stateVariants);
294
+
295
+ // 如果有响应式变体,用 @media 包裹
296
+ if (responsiveVariant) {
297
+ cssRule = this.wrapWithMediaQuery(cssRule, responsiveVariant);
298
+ }
299
+
300
+ str += cssRule;
301
+ }
302
+ });
303
+
304
+ this.eventBus.emit('unifiedWriter:staticGeneration:completed', {
305
+ generatedCount: cssWrite.size,
306
+ cssLength: str.length,
307
+ });
308
+
309
+ return str;
310
+ } catch (error) {
311
+ this.eventBus.emit('unifiedWriter:staticGeneration:error', {
312
+ error: error.message,
313
+ });
314
+ return '';
315
+ }
316
+ }
317
+
318
+ // 清空生成缓存
319
+ clearGenerationCache() {
320
+ // 这里可以清空任何需要重置的生成缓存
321
+ // 例如在动态生成器中的重复检查缓存
322
+ this.eventBus.emit('unifiedWriter:cacheCleared');
323
+ }
324
+
325
+ // 取消待处理的写入
326
+ cancelPendingWrites() {
327
+ if (this.writeTimer) {
328
+ clearTimeout(this.writeTimer);
329
+ this.writeTimer = null;
330
+ }
331
+
332
+ const cancelledCount = this.pendingWrites.size;
333
+ this.pendingWrites.clear();
334
+
335
+ this.eventBus.emit('unifiedWriter:cancelled', {
336
+ cancelledCount,
337
+ });
338
+ }
339
+
340
+ // 立即执行写入(跳过防抖)
341
+ async immediateWrite(fullScanManager, fileWriter, triggerFile = null) {
342
+ // 取消防抖定时器
343
+ this.cancelPendingWrites();
344
+
345
+ // 立即执行写入
346
+ if (triggerFile) {
347
+ this.pendingWrites.add(triggerFile);
348
+ }
349
+
350
+ await this.executeWrite(fullScanManager, fileWriter);
351
+ }
352
+
353
+ // 获取写入状态
354
+ getWriteStats() {
355
+ return {
356
+ isWritePending: this.writeTimer !== null,
357
+ pendingWriteCount: this.pendingWrites.size,
358
+ pendingFiles: Array.from(this.pendingWrites),
359
+ debounceDelay: this.debounceDelay,
360
+ };
361
+ }
362
+
363
+ // 设置防抖延迟
364
+ setDebounceDelay(delay) {
365
+ this.debounceDelay = Math.max(100, Math.min(5000, delay)); // 限制在100ms-5s之间
366
+ this.eventBus.emit('unifiedWriter:debounceDelayChanged', {
367
+ newDelay: this.debounceDelay,
368
+ });
369
+ }
370
+
371
+ // 验证统一写入配置
372
+ validateConfig() {
373
+ const errors = [];
374
+ const warnings = [];
375
+
376
+ const multiFile = this.configManager.getMultiFile();
377
+ if (!multiFile) {
378
+ errors.push('MultiFile configuration is required for unified writing');
379
+ return { errors, warnings, isValid: false };
380
+ }
381
+
382
+ const outputConfig = multiFile.output;
383
+ if (!outputConfig) {
384
+ errors.push('Output configuration is required');
385
+ return { errors, warnings, isValid: false };
386
+ }
387
+
388
+ if (outputConfig.cssOutType !== 'uniFile') {
389
+ warnings.push(`CSS output type is '${outputConfig.cssOutType}', expected 'uniFile'`);
390
+ }
391
+
392
+ if (!outputConfig.path) {
393
+ errors.push('Output path is required for unified file writing');
394
+ }
395
+
396
+ if (!outputConfig.fileName) {
397
+ warnings.push('Output file name not specified, will use default');
398
+ }
399
+
400
+ return { errors, warnings, isValid: errors.length === 0 };
401
+ }
402
+
403
+ // 调试信息
404
+ debug() {
405
+ return {
406
+ writeStats: this.getWriteStats(),
407
+ configValidation: this.validateConfig(),
408
+ config: this.configManager.getMultiFile()?.output,
409
+ };
410
+ }
411
+ }
412
+
413
+ module.exports = UnifiedWriter;