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.
- package/.github/workflows/deploy-docs.yml +53 -0
- package/.head_config.mjs +68 -0
- package/CONFIG.md +38 -1
- package/README.md +595 -633
- package/bin/class2css.js +32 -4
- package/common.css +1 -1
- package/configs/typography.config.js +1 -0
- package/docs/.vitepress/config.mjs +68 -65
- package/docs/guide/cli.md +97 -97
- package/docs/guide/config-template.md +16 -1
- package/docs/guide/config.md +129 -64
- package/docs/guide/faq.md +202 -202
- package/docs/guide/getting-started.md +86 -83
- package/docs/guide/incremental.md +164 -162
- package/docs/guide/rules-reference.md +73 -1
- package/docs/index.md +71 -68
- package/examples/weapp/README.md +15 -0
- package/examples/weapp/class2css.config.js +70 -0
- package/examples/weapp/src/placeholder.wxml +0 -0
- package/examples/weapp/styles.config.js +201 -0
- package/examples/web/README.md +25 -0
- package/examples/web/class2css.config.js +70 -0
- package/examples/web/demo.html +105 -0
- package/examples/web/src/placeholder.html +5 -0
- package/examples/web/styles.config.js +201 -0
- package/package.json +7 -2
- package/src/README.md +99 -0
- package/src/core/ConfigManager.js +440 -431
- package/src/core/FullScanManager.js +438 -430
- package/src/generators/DynamicClassGenerator.js +270 -72
- package/src/index.js +1091 -1046
- package/src/parsers/ClassParser.js +8 -2
- package/src/utils/CssFormatter.js +400 -47
- package/src/utils/UnitProcessor.js +4 -3
- package/src/watchers/ConfigWatcher.js +413 -413
- package/src/watchers/FileWatcher.js +148 -133
- package/src/writers/FileWriter.js +444 -302
- package/src/writers/UnifiedWriter.js +413 -370
- package/class2css.config.js +0 -124
- 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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const contentToAppend = cssContent.trim();
|
|
206
|
-
if (contentToAppend) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
+
// 新实现:分区插入 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;
|