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.
- package/API.md +1143 -0
- package/CHANGELOG.md +291 -0
- package/CONFIG.md +1096 -0
- package/CONTRIBUTING.md +571 -0
- package/MIGRATION.md +402 -0
- package/README.md +634 -0
- package/bin/class2css.js +380 -0
- package/class2css.config.js +124 -0
- package/common.css +3 -0
- package/configs/colors.config.js +62 -0
- package/configs/layout.config.js +110 -0
- package/configs/spacing.config.js +37 -0
- package/configs/typography.config.js +41 -0
- package/docs/.vitepress/config.mjs +65 -0
- package/docs/.vitepress/theme/custom.css +74 -0
- package/docs/.vitepress/theme/index.js +7 -0
- package/docs/guide/cli.md +97 -0
- package/docs/guide/concepts.md +63 -0
- package/docs/guide/config-template.md +365 -0
- package/docs/guide/config.md +275 -0
- package/docs/guide/faq.md +202 -0
- package/docs/guide/getting-started.md +83 -0
- package/docs/guide/important-and-static.md +67 -0
- package/docs/guide/incremental.md +162 -0
- package/docs/guide/rules-reference.md +354 -0
- package/docs/guide/units.md +57 -0
- package/docs/index.md +68 -0
- package/package.json +49 -0
- package/run.js +90 -0
- package/src/README.md +571 -0
- package/src/core/CacheManager.js +650 -0
- package/src/core/CompatibilityAdapter.js +264 -0
- package/src/core/ConfigManager.js +431 -0
- package/src/core/ConfigValidator.js +350 -0
- package/src/core/EventBus.js +77 -0
- package/src/core/FullScanManager.js +430 -0
- package/src/core/StateManager.js +631 -0
- package/src/docs/DocsServer.js +179 -0
- package/src/example.js +106 -0
- package/src/generators/DynamicClassGenerator.js +674 -0
- package/src/index.js +1046 -0
- package/src/parsers/ClassParser.js +572 -0
- package/src/parsers/ImportantParser.js +279 -0
- package/src/parsers/RegexCompiler.js +200 -0
- package/src/utils/ClassChangeTracker.js +366 -0
- package/src/utils/ConfigDiagnostics.js +673 -0
- package/src/utils/CssFormatter.js +261 -0
- package/src/utils/FileUtils.js +230 -0
- package/src/utils/Logger.js +150 -0
- package/src/utils/Throttle.js +172 -0
- package/src/utils/UnitProcessor.js +334 -0
- package/src/utils/WxssClassExtractor.js +137 -0
- package/src/watchers/ConfigWatcher.js +413 -0
- package/src/watchers/FileWatcher.js +133 -0
- package/src/writers/FileWriter.js +302 -0
- package/src/writers/UnifiedWriter.js +370 -0
- package/styles.config.js +250 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
|
|
4
|
+
class FileWriter {
|
|
5
|
+
constructor(eventBus, configManager, fileUtils) {
|
|
6
|
+
this.eventBus = eventBus;
|
|
7
|
+
this.configManager = configManager;
|
|
8
|
+
this.fileUtils = fileUtils;
|
|
9
|
+
this.writeQueue = [];
|
|
10
|
+
this.isWriting = false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 写入CSS到文件
|
|
14
|
+
async writeCSS(cssContent, filePath, options = {}) {
|
|
15
|
+
try {
|
|
16
|
+
const multiFile = this.configManager.getMultiFile();
|
|
17
|
+
if (!multiFile) {
|
|
18
|
+
throw new Error('MultiFile configuration is required for CSS writing');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const outputConfig = multiFile.output;
|
|
22
|
+
const cssOutType = outputConfig.cssOutType || 'filePath';
|
|
23
|
+
|
|
24
|
+
let outputPath;
|
|
25
|
+
let fileName;
|
|
26
|
+
|
|
27
|
+
// 强制统一文件模式(由UnifiedWriter调用时)
|
|
28
|
+
if (options.forceUniFile || cssOutType === 'uniFile') {
|
|
29
|
+
const outputDir = options.outputPath || outputConfig.path || './';
|
|
30
|
+
fileName = options.fileName || outputConfig.fileName || 'common.wxss';
|
|
31
|
+
outputPath = path.join(outputDir, fileName);
|
|
32
|
+
|
|
33
|
+
this.eventBus.emit('fileWriter:uniFileMode', {
|
|
34
|
+
outputPath,
|
|
35
|
+
fileName,
|
|
36
|
+
cssLength: cssContent.length,
|
|
37
|
+
});
|
|
38
|
+
} else if (cssOutType === 'filePath') {
|
|
39
|
+
// 将CSS文件输出到监听文件对应的目录下
|
|
40
|
+
const entryRoots = this.configManager.getMultiFileEntryPaths();
|
|
41
|
+
const fileAbs = path.resolve(filePath);
|
|
42
|
+
|
|
43
|
+
// 选择“最匹配”的根目录(最长前缀),以支持多目录入口
|
|
44
|
+
let bestRootAbs = null;
|
|
45
|
+
let bestRel = null;
|
|
46
|
+
for (const root of entryRoots) {
|
|
47
|
+
const rootAbs = path.resolve(root);
|
|
48
|
+
const rel = path.relative(rootAbs, fileAbs);
|
|
49
|
+
const isInside = rel && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
50
|
+
if (isInside && (!bestRootAbs || rootAbs.length > bestRootAbs.length)) {
|
|
51
|
+
bestRootAbs = rootAbs;
|
|
52
|
+
bestRel = rel;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const relativePath = bestRel || path.basename(fileAbs);
|
|
57
|
+
const dirPath = path.dirname(relativePath);
|
|
58
|
+
const baseName = path.basename(filePath, path.extname(filePath));
|
|
59
|
+
|
|
60
|
+
// 构建输出路径
|
|
61
|
+
const outputDir = outputConfig.path || path.dirname(filePath);
|
|
62
|
+
const outputDirPath = path.join(outputDir, dirPath);
|
|
63
|
+
fileName = `${baseName}.${outputConfig.fileType || 'wxss'}`;
|
|
64
|
+
outputPath = path.join(outputDirPath, fileName);
|
|
65
|
+
|
|
66
|
+
this.eventBus.emit('fileWriter:filePathMode', {
|
|
67
|
+
sourceFile: filePath,
|
|
68
|
+
outputPath,
|
|
69
|
+
fileName,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 确保输出目录存在
|
|
74
|
+
await this.ensureDirectoryExists(path.dirname(outputPath));
|
|
75
|
+
|
|
76
|
+
// 写入文件
|
|
77
|
+
await this.fileUtils.writeFile(outputPath, cssContent, 'utf-8');
|
|
78
|
+
|
|
79
|
+
this.eventBus.emit('file:css:written', {
|
|
80
|
+
sourceFile: filePath,
|
|
81
|
+
outputFile: outputPath,
|
|
82
|
+
cssLength: cssContent.length,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
outputPath: outputPath,
|
|
88
|
+
fileName: fileName,
|
|
89
|
+
cssLength: cssContent.length,
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.eventBus.emit('file:css:write:error', {
|
|
93
|
+
sourceFile: filePath,
|
|
94
|
+
error: error.message,
|
|
95
|
+
});
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 批量写入CSS
|
|
101
|
+
async writeBatchCSS(cssMap) {
|
|
102
|
+
const results = [];
|
|
103
|
+
|
|
104
|
+
for (const [filePath, cssContent] of Object.entries(cssMap)) {
|
|
105
|
+
try {
|
|
106
|
+
const result = await this.writeCSS(cssContent, filePath);
|
|
107
|
+
results.push(result);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
results.push({
|
|
110
|
+
success: false,
|
|
111
|
+
sourceFile: filePath,
|
|
112
|
+
error: error.message,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.eventBus.emit('file:css:batch:written', {
|
|
118
|
+
totalFiles: cssMap.length,
|
|
119
|
+
successCount: results.filter((r) => r.success).length,
|
|
120
|
+
failedCount: results.filter((r) => !r.success).length,
|
|
121
|
+
results: results,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 确保目录存在
|
|
128
|
+
async ensureDirectoryExists(dirPath) {
|
|
129
|
+
try {
|
|
130
|
+
await fs.access(dirPath);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 获取写入统计信息
|
|
137
|
+
getWriteStats() {
|
|
138
|
+
return {
|
|
139
|
+
isWriting: this.isWriting,
|
|
140
|
+
queueLength: this.writeQueue.length,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 清理输出目录
|
|
145
|
+
async cleanOutputDirectory() {
|
|
146
|
+
try {
|
|
147
|
+
const multiFile = this.configManager.getMultiFile();
|
|
148
|
+
if (!multiFile || !multiFile.output || !multiFile.output.path) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const outputPath = path.resolve(multiFile.output.path);
|
|
153
|
+
|
|
154
|
+
// 检查目录是否存在
|
|
155
|
+
try {
|
|
156
|
+
await fs.access(outputPath);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// 目录不存在,无需清理
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 读取目录内容
|
|
163
|
+
const files = await fs.readdir(outputPath);
|
|
164
|
+
|
|
165
|
+
// 删除所有.wxss文件
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
if (file.endsWith('.wxss')) {
|
|
168
|
+
const filePath = path.join(outputPath, file);
|
|
169
|
+
await fs.unlink(filePath);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.eventBus.emit('file:output:cleaned', { outputPath });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
this.eventBus.emit('file:output:clean:error', { error: error.message });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 追加CSS到文件末尾(用于 appendDelta 模式)
|
|
180
|
+
async appendCSS(cssContent, filePath, options = {}) {
|
|
181
|
+
try {
|
|
182
|
+
const multiFile = this.configManager.getMultiFile();
|
|
183
|
+
if (!multiFile) {
|
|
184
|
+
throw new Error('MultiFile configuration is required for CSS writing');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const outputConfig = multiFile.output;
|
|
188
|
+
const cssOutType = outputConfig.cssOutType || 'filePath';
|
|
189
|
+
|
|
190
|
+
let outputPath;
|
|
191
|
+
|
|
192
|
+
// 强制统一文件模式(由UnifiedWriter调用时)
|
|
193
|
+
if (options.forceUniFile || cssOutType === 'uniFile') {
|
|
194
|
+
const outputDir = options.outputPath || outputConfig.path || './';
|
|
195
|
+
const fileName = options.fileName || outputConfig.fileName || 'common.wxss';
|
|
196
|
+
outputPath = path.join(outputDir, fileName);
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error('appendCSS is only supported for uniFile mode');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 确保输出目录存在
|
|
202
|
+
await this.ensureDirectoryExists(path.dirname(outputPath));
|
|
203
|
+
|
|
204
|
+
// 追加内容到文件末尾(确保前后有换行,避免粘连)
|
|
205
|
+
const contentToAppend = cssContent.trim();
|
|
206
|
+
if (contentToAppend) {
|
|
207
|
+
const appendContent = '\n' + contentToAppend + '\n';
|
|
208
|
+
await fs.appendFile(outputPath, appendContent, 'utf-8');
|
|
209
|
+
|
|
210
|
+
this.eventBus.emit('file:css:appended', {
|
|
211
|
+
sourceFile: filePath,
|
|
212
|
+
outputFile: outputPath,
|
|
213
|
+
cssLength: cssContent.length,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
outputPath: outputPath,
|
|
220
|
+
cssLength: cssContent.length,
|
|
221
|
+
};
|
|
222
|
+
} catch (error) {
|
|
223
|
+
this.eventBus.emit('file:css:append:error', {
|
|
224
|
+
sourceFile: filePath,
|
|
225
|
+
error: error.message,
|
|
226
|
+
});
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 写入BASE区块并添加DELTA_START标记(用于 appendDelta 模式的启动重建)
|
|
232
|
+
async writeBaseWithDeltaMarker(baseCssContent, filePath, options = {}) {
|
|
233
|
+
try {
|
|
234
|
+
const multiFile = this.configManager.getMultiFile();
|
|
235
|
+
if (!multiFile) {
|
|
236
|
+
throw new Error('MultiFile configuration is required for CSS writing');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const outputConfig = multiFile.output;
|
|
240
|
+
const cssOutType = outputConfig.cssOutType || 'filePath';
|
|
241
|
+
|
|
242
|
+
let outputPath;
|
|
243
|
+
let fileName;
|
|
244
|
+
|
|
245
|
+
// 强制统一文件模式
|
|
246
|
+
if (options.forceUniFile || cssOutType === 'uniFile') {
|
|
247
|
+
const outputDir = options.outputPath || outputConfig.path || './';
|
|
248
|
+
fileName = options.fileName || outputConfig.fileName || 'common.wxss';
|
|
249
|
+
outputPath = path.join(outputDir, fileName);
|
|
250
|
+
} else {
|
|
251
|
+
throw new Error('writeBaseWithDeltaMarker is only supported for uniFile mode');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 确保输出目录存在
|
|
255
|
+
await this.ensureDirectoryExists(path.dirname(outputPath));
|
|
256
|
+
|
|
257
|
+
// 构建包含BASE标记和DELTA_START标记的内容
|
|
258
|
+
const baseMarker = '/* CLASS2CSS:BASE */\n';
|
|
259
|
+
const deltaMarker = '\n/* CLASS2CSS:DELTA_START */\n';
|
|
260
|
+
const fullContent = baseMarker + baseCssContent.trim() + deltaMarker;
|
|
261
|
+
|
|
262
|
+
// 覆盖写入文件(清空旧内容)
|
|
263
|
+
await this.fileUtils.writeFile(outputPath, fullContent, 'utf-8');
|
|
264
|
+
|
|
265
|
+
this.eventBus.emit('file:css:baseWritten', {
|
|
266
|
+
sourceFile: filePath,
|
|
267
|
+
outputFile: outputPath,
|
|
268
|
+
cssLength: baseCssContent.length,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
outputPath: outputPath,
|
|
274
|
+
fileName: fileName,
|
|
275
|
+
cssLength: baseCssContent.length,
|
|
276
|
+
};
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.eventBus.emit('file:css:baseWrite:error', {
|
|
279
|
+
sourceFile: filePath,
|
|
280
|
+
error: error.message,
|
|
281
|
+
});
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 获取输出配置信息
|
|
287
|
+
getOutputConfig() {
|
|
288
|
+
const multiFile = this.configManager.getMultiFile();
|
|
289
|
+
if (!multiFile || !multiFile.output) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
cssOutType: multiFile.output.cssOutType || 'filePath',
|
|
295
|
+
outputPath: multiFile.output.path,
|
|
296
|
+
fileName: multiFile.output.fileName,
|
|
297
|
+
fileType: multiFile.output.fileType || 'wxss',
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = FileWriter;
|
|
@@ -0,0 +1,370 @@
|
|
|
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;
|