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,302 +1,444 @@
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;
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
+ // 新实现:分区插入 baseDelta mediaDelta 到对应 marker 区域
181
+ async appendCSS(cssContent, filePath, options = {}) {
182
+ try {
183
+ const multiFile = this.configManager.getMultiFile();
184
+ if (!multiFile) {
185
+ throw new Error('MultiFile configuration is required for CSS writing');
186
+ }
187
+
188
+ const outputConfig = multiFile.output;
189
+ const cssOutType = outputConfig.cssOutType || 'filePath';
190
+
191
+ let outputPath;
192
+
193
+ // 强制统一文件模式(由UnifiedWriter调用时)
194
+ if (options.forceUniFile || cssOutType === 'uniFile') {
195
+ const outputDir = options.outputPath || outputConfig.path || './';
196
+ const fileName = options.fileName || outputConfig.fileName || 'common.wxss';
197
+ outputPath = path.join(outputDir, fileName);
198
+ } else {
199
+ throw new Error('appendCSS is only supported for uniFile mode');
200
+ }
201
+
202
+ // 确保输出目录存在
203
+ await this.ensureDirectoryExists(path.dirname(outputPath));
204
+
205
+ const contentToAppend = cssContent.trim();
206
+ if (!contentToAppend) {
207
+ return {
208
+ success: true,
209
+ outputPath: outputPath,
210
+ cssLength: 0,
211
+ };
212
+ }
213
+
214
+ // 拆分 delta CSS 为 base 和 media
215
+ const CssFormatter = require('../utils/CssFormatter');
216
+ const cssFormat = this.configManager.getCssFormat();
217
+ const cssFormatter = new CssFormatter(cssFormat);
218
+ const { baseCss: baseDelta, mediaCss: mediaDelta, otherAtRulesPrefix: deltaPrefix } =
219
+ cssFormatter.splitBaseAndMedia(contentToAppend);
220
+
221
+ // 读取现有文件内容
222
+ let existingContent = '';
223
+ try {
224
+ existingContent = await fs.readFile(outputPath, 'utf-8');
225
+ } catch (error) {
226
+ // 文件不存在,使用分区插入写入新文件
227
+ if (error.code === 'ENOENT') {
228
+ return await this.writeBaseWithDeltaMarker(contentToAppend, filePath, options);
229
+ }
230
+ throw error;
231
+ }
232
+
233
+ // 检查是否存在分区 marker
234
+ const baseStartMarker = '/* CLASS2CSS:BASE_START */';
235
+ const baseEndMarker = '/* CLASS2CSS:BASE_END */';
236
+ const mediaStartMarker = '/* CLASS2CSS:MEDIA_START */';
237
+ const mediaEndMarker = '/* CLASS2CSS:MEDIA_END */';
238
+ const deltaStartMarker = '/* CLASS2CSS:DELTA_START */';
239
+
240
+ const hasPartitionMarkers =
241
+ existingContent.includes(baseStartMarker) &&
242
+ existingContent.includes(baseEndMarker) &&
243
+ existingContent.includes(mediaStartMarker) &&
244
+ existingContent.includes(mediaEndMarker);
245
+
246
+ if (!hasPartitionMarkers) {
247
+ // 回退策略:旧文件格式,先做一次全文件 normalize 并写入分区结构
248
+ this.eventBus.emit('file:css:append:fallback', {
249
+ message: 'Output file lacks partition markers, normalizing and rewriting',
250
+ outputPath,
251
+ });
252
+
253
+ // 合并旧内容和新内容,做 normalize
254
+ const mergedContent = existingContent + '\n' + contentToAppend;
255
+ const normalizedContent = cssFormatter.normalizeResponsiveOrder(mergedContent);
256
+ const { baseCss, mediaCss, otherAtRulesPrefix, suffix } =
257
+ cssFormatter.splitBaseAndMedia(normalizedContent);
258
+
259
+ const baseStartMarkerLine = '/* CLASS2CSS:BASE_START */\n';
260
+ const baseEndMarkerLine = '\n/* CLASS2CSS:BASE_END */\n';
261
+ const mediaStartMarkerLine = '/* CLASS2CSS:MEDIA_START */\n';
262
+ const mediaEndMarkerLine = '\n/* CLASS2CSS:MEDIA_END */\n';
263
+ const deltaStartMarkerLine = '\n/* CLASS2CSS:DELTA_START */\n';
264
+
265
+ const rewrittenContent =
266
+ otherAtRulesPrefix +
267
+ baseStartMarkerLine +
268
+ baseCss.trim() +
269
+ baseEndMarkerLine +
270
+ mediaStartMarkerLine +
271
+ mediaCss.trim() +
272
+ mediaEndMarkerLine +
273
+ deltaStartMarkerLine +
274
+ (suffix ? '\n' + suffix.trim() : '');
275
+
276
+ await this.fileUtils.writeFile(outputPath, rewrittenContent, 'utf-8');
277
+
278
+ this.eventBus.emit('file:css:appended', {
279
+ sourceFile: filePath,
280
+ outputFile: outputPath,
281
+ cssLength: cssContent.length,
282
+ });
283
+
284
+ return {
285
+ success: true,
286
+ outputPath: outputPath,
287
+ cssLength: cssContent.length,
288
+ };
289
+ }
290
+
291
+ // 分区插入:找到 marker 位置并插入对应内容
292
+ let newContent = existingContent;
293
+
294
+ // 插入 baseDelta 到 BASE_END 之前
295
+ if (baseDelta.trim()) {
296
+ const baseEndIdx = newContent.indexOf(baseEndMarker);
297
+ if (baseEndIdx !== -1) {
298
+ const beforeBaseEnd = newContent.slice(0, baseEndIdx);
299
+ const afterBaseEnd = newContent.slice(baseEndIdx);
300
+ // 如果 base 区域不为空,添加换行分隔
301
+ const separator = beforeBaseEnd.trim().endsWith('}') ? '\n' : '';
302
+ newContent = beforeBaseEnd + separator + baseDelta.trim() + '\n' + afterBaseEnd;
303
+ }
304
+ }
305
+
306
+ // 插入 mediaDelta 到 MEDIA_END 之前
307
+ if (mediaDelta.trim()) {
308
+ const mediaEndIdx = newContent.indexOf(mediaEndMarker);
309
+ if (mediaEndIdx !== -1) {
310
+ const beforeMediaEnd = newContent.slice(0, mediaEndIdx);
311
+ const afterMediaEnd = newContent.slice(mediaEndIdx);
312
+ // 如果 media 区域不为空,添加换行分隔
313
+ const separator = beforeMediaEnd.trim().endsWith('}') ? '\n' : '';
314
+ newContent = beforeMediaEnd + separator + mediaDelta.trim() + '\n' + afterMediaEnd;
315
+ }
316
+ }
317
+
318
+ // 插入 deltaPrefix(其他 at-rules)到 BASE_START 之前(如果有)
319
+ if (deltaPrefix.trim()) {
320
+ const baseStartIdx = newContent.indexOf(baseStartMarker);
321
+ if (baseStartIdx !== -1) {
322
+ const beforeBaseStart = newContent.slice(0, baseStartIdx);
323
+ const afterBaseStart = newContent.slice(baseStartIdx);
324
+ newContent = beforeBaseStart + deltaPrefix.trim() + '\n' + afterBaseStart;
325
+ }
326
+ }
327
+
328
+ // 写回文件
329
+ await this.fileUtils.writeFile(outputPath, newContent, 'utf-8');
330
+
331
+ this.eventBus.emit('file:css:appended', {
332
+ sourceFile: filePath,
333
+ outputFile: outputPath,
334
+ cssLength: cssContent.length,
335
+ });
336
+
337
+ return {
338
+ success: true,
339
+ outputPath: outputPath,
340
+ cssLength: cssContent.length,
341
+ };
342
+ } catch (error) {
343
+ this.eventBus.emit('file:css:append:error', {
344
+ sourceFile: filePath,
345
+ error: error.message,
346
+ });
347
+ throw error;
348
+ }
349
+ }
350
+
351
+ // 写入BASE区块并添加DELTA_START标记(用于 appendDelta 模式的启动重建)
352
+ async writeBaseWithDeltaMarker(baseCssContent, filePath, options = {}) {
353
+ try {
354
+ const multiFile = this.configManager.getMultiFile();
355
+ if (!multiFile) {
356
+ throw new Error('MultiFile configuration is required for CSS writing');
357
+ }
358
+
359
+ const outputConfig = multiFile.output;
360
+ const cssOutType = outputConfig.cssOutType || 'filePath';
361
+
362
+ let outputPath;
363
+ let fileName;
364
+
365
+ // 强制统一文件模式
366
+ if (options.forceUniFile || cssOutType === 'uniFile') {
367
+ const outputDir = options.outputPath || outputConfig.path || './';
368
+ fileName = options.fileName || outputConfig.fileName || 'common.wxss';
369
+ outputPath = path.join(outputDir, fileName);
370
+ } else {
371
+ throw new Error('writeBaseWithDeltaMarker is only supported for uniFile mode');
372
+ }
373
+
374
+ // 确保输出目录存在
375
+ await this.ensureDirectoryExists(path.dirname(outputPath));
376
+
377
+ // 拆分 CSS 为 base 和 media 两部分(用于分区写入)
378
+ // 注意:baseCssContent 已经是格式化后的内容(由 generateUnifiedCSS 处理)
379
+ const CssFormatter = require('../utils/CssFormatter');
380
+ const cssFormat = this.configManager.getCssFormat();
381
+ const cssFormatter = new CssFormatter(cssFormat);
382
+ const { baseCss, mediaCss, otherAtRulesPrefix, suffix } = cssFormatter.splitBaseAndMedia(baseCssContent);
383
+
384
+ // 构建分区标记结构
385
+ // 注意:在 compressed 格式下,标记会被保留(compressCSS 已更新以保留这些标记)
386
+ const baseStartMarker = '/* CLASS2CSS:BASE_START */\n';
387
+ const baseEndMarker = '\n/* CLASS2CSS:BASE_END */\n';
388
+ const mediaStartMarker = '/* CLASS2CSS:MEDIA_START */\n';
389
+ const mediaEndMarker = '\n/* CLASS2CSS:MEDIA_END */\n';
390
+ const deltaStartMarker = '\n/* CLASS2CSS:DELTA_START */\n';
391
+
392
+ // 按固定顺序写入:prefix -> BASE -> MEDIA -> DELTA(初始为空)
393
+ const fullContent =
394
+ (otherAtRulesPrefix ? otherAtRulesPrefix + '\n' : '') +
395
+ baseStartMarker +
396
+ baseCss.trim() +
397
+ baseEndMarker +
398
+ mediaStartMarker +
399
+ mediaCss.trim() +
400
+ mediaEndMarker +
401
+ deltaStartMarker +
402
+ (suffix ? '\n' + suffix.trim() : '');
403
+
404
+ // 覆盖写入文件(清空旧内容)
405
+ await this.fileUtils.writeFile(outputPath, fullContent, 'utf-8');
406
+
407
+ this.eventBus.emit('file:css:baseWritten', {
408
+ sourceFile: filePath,
409
+ outputFile: outputPath,
410
+ cssLength: baseCssContent.length,
411
+ });
412
+
413
+ return {
414
+ success: true,
415
+ outputPath: outputPath,
416
+ fileName: fileName,
417
+ cssLength: baseCssContent.length,
418
+ };
419
+ } catch (error) {
420
+ this.eventBus.emit('file:css:baseWrite:error', {
421
+ sourceFile: filePath,
422
+ error: error.message,
423
+ });
424
+ throw error;
425
+ }
426
+ }
427
+
428
+ // 获取输出配置信息
429
+ getOutputConfig() {
430
+ const multiFile = this.configManager.getMultiFile();
431
+ if (!multiFile || !multiFile.output) {
432
+ return null;
433
+ }
434
+
435
+ return {
436
+ cssOutType: multiFile.output.cssOutType || 'filePath',
437
+ outputPath: multiFile.output.path,
438
+ fileName: multiFile.output.fileName,
439
+ fileType: multiFile.output.fileType || 'wxss',
440
+ };
441
+ }
442
+ }
443
+
444
+ module.exports = FileWriter;