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,264 @@
|
|
|
1
|
+
const ConfigValidator = require('./ConfigValidator');
|
|
2
|
+
|
|
3
|
+
class CompatibilityAdapter {
|
|
4
|
+
constructor(eventBus, configValidator) {
|
|
5
|
+
this.eventBus = eventBus;
|
|
6
|
+
this.configValidator = configValidator || new ConfigValidator(eventBus);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// 将旧配置格式转换为新格式
|
|
10
|
+
adaptConfig(config) {
|
|
11
|
+
try {
|
|
12
|
+
// 如果已经是新格式,直接返回
|
|
13
|
+
if (this.isNewFormat(config)) {
|
|
14
|
+
return config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 转换为新格式
|
|
18
|
+
const adaptedConfig = this.transformToNewFormat(config);
|
|
19
|
+
|
|
20
|
+
// 验证转换后的配置
|
|
21
|
+
const validation = this.configValidator.validateConfig(adaptedConfig);
|
|
22
|
+
if (!validation.isValid) {
|
|
23
|
+
this.eventBus.emit('compatibility:validation:failed', {
|
|
24
|
+
errors: validation.errors,
|
|
25
|
+
warnings: validation.warnings,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.eventBus.emit('compatibility:adapted', {
|
|
30
|
+
original: config,
|
|
31
|
+
adapted: adaptedConfig,
|
|
32
|
+
validation,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return adaptedConfig;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this.eventBus.emit('compatibility:error', error);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 检查是否为新格式
|
|
43
|
+
isNewFormat(config) {
|
|
44
|
+
// 检查是否有新的system配置结构
|
|
45
|
+
return !!(config.system || config.atomicRules || config.compatibility);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 转换为新格式
|
|
49
|
+
transformToNewFormat(config) {
|
|
50
|
+
const newConfig = {
|
|
51
|
+
// 系统配置
|
|
52
|
+
system: {
|
|
53
|
+
baseUnit: config.baseUnit || 'px',
|
|
54
|
+
unitConversion: config.unitConversion || 1,
|
|
55
|
+
compression: config.compression !== undefined ? config.compression : true,
|
|
56
|
+
unitStrategy: {
|
|
57
|
+
autoDetect: true,
|
|
58
|
+
propertyUnits: {
|
|
59
|
+
'font-size': config.baseUnit || 'px',
|
|
60
|
+
'width|height': config.baseUnit || 'px',
|
|
61
|
+
opacity: '',
|
|
62
|
+
'z-index': '',
|
|
63
|
+
'line-height': '',
|
|
64
|
+
'border-radius': config.baseUnit || 'px',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// 保留现有配置
|
|
70
|
+
output: config.output,
|
|
71
|
+
importantFlags: config.importantFlags,
|
|
72
|
+
multiFile: config.multiFile,
|
|
73
|
+
|
|
74
|
+
// 保留原有配置结构以确保兼容性
|
|
75
|
+
cssName: config.cssName,
|
|
76
|
+
baseClassName: config.baseClassName,
|
|
77
|
+
atomicClassMap: config.atomicClassMap,
|
|
78
|
+
variants: config.variants,
|
|
79
|
+
|
|
80
|
+
// 兼容性配置
|
|
81
|
+
compatibility: {
|
|
82
|
+
legacy: {
|
|
83
|
+
cssName: config.cssName,
|
|
84
|
+
baseClassName: config.baseClassName,
|
|
85
|
+
atomicClassMap: config.atomicClassMap,
|
|
86
|
+
},
|
|
87
|
+
unified: {
|
|
88
|
+
enabled: false, // 默认关闭新功能
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return newConfig;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 合并配置
|
|
97
|
+
mergeConfigs(baseConfig, overrideConfig) {
|
|
98
|
+
const merged = JSON.parse(JSON.stringify(baseConfig));
|
|
99
|
+
|
|
100
|
+
// 深度合并
|
|
101
|
+
this.deepMerge(merged, overrideConfig);
|
|
102
|
+
|
|
103
|
+
return merged;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 深度合并对象
|
|
107
|
+
deepMerge(target, source) {
|
|
108
|
+
for (const key in source) {
|
|
109
|
+
if (source.hasOwnProperty(key)) {
|
|
110
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
111
|
+
if (!target[key] || typeof target[key] !== 'object') {
|
|
112
|
+
target[key] = {};
|
|
113
|
+
}
|
|
114
|
+
this.deepMerge(target[key], source[key]);
|
|
115
|
+
} else {
|
|
116
|
+
target[key] = source[key];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 解决配置冲突
|
|
123
|
+
resolveConflicts(config) {
|
|
124
|
+
const resolved = JSON.parse(JSON.stringify(config));
|
|
125
|
+
let hasChanges = false;
|
|
126
|
+
|
|
127
|
+
// 解决text属性冲突
|
|
128
|
+
if (resolved.cssName?.text && resolved.atomicClassMap?.text) {
|
|
129
|
+
// 保留atomicClassMap中的定义,在cssName中重命名
|
|
130
|
+
resolved.cssName.textSize = resolved.cssName.text;
|
|
131
|
+
delete resolved.cssName.text;
|
|
132
|
+
hasChanges = true;
|
|
133
|
+
|
|
134
|
+
this.eventBus.emit('compatibility:conflict:resolved', {
|
|
135
|
+
type: 'text_property_conflict',
|
|
136
|
+
action: 'renamed cssName.text to cssName.textSize',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 解决重复的间距定义
|
|
141
|
+
const spacingConflicts = this.findSpacingConflicts(resolved);
|
|
142
|
+
if (spacingConflicts.length > 0) {
|
|
143
|
+
this.resolveSpacingConflicts(resolved, spacingConflicts);
|
|
144
|
+
hasChanges = true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { config: resolved, hasChanges };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 查找间距配置冲突
|
|
151
|
+
findSpacingConflicts(config) {
|
|
152
|
+
const conflicts = [];
|
|
153
|
+
const spacingProps = [
|
|
154
|
+
'm',
|
|
155
|
+
'mt',
|
|
156
|
+
'mr',
|
|
157
|
+
'mb',
|
|
158
|
+
'ml',
|
|
159
|
+
'mx',
|
|
160
|
+
'my',
|
|
161
|
+
'p',
|
|
162
|
+
'pt',
|
|
163
|
+
'pr',
|
|
164
|
+
'pb',
|
|
165
|
+
'pl',
|
|
166
|
+
'px',
|
|
167
|
+
'py',
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
spacingProps.forEach((prop) => {
|
|
171
|
+
const sources = [];
|
|
172
|
+
if (config.cssName?.[prop]) sources.push('cssName');
|
|
173
|
+
if (config.atomicClassMap?.[prop]) sources.push('atomicClassMap');
|
|
174
|
+
|
|
175
|
+
if (sources.length > 1) {
|
|
176
|
+
conflicts.push({ property: prop, sources });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return conflicts;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 解决间距配置冲突
|
|
184
|
+
resolveSpacingConflicts(config, conflicts) {
|
|
185
|
+
conflicts.forEach((conflict) => {
|
|
186
|
+
// 优先使用atomicClassMap的定义
|
|
187
|
+
if (config.cssName[conflict.property] && config.atomicClassMap[conflict.property]) {
|
|
188
|
+
delete config.cssName[conflict.property];
|
|
189
|
+
|
|
190
|
+
this.eventBus.emit('compatibility:conflict:resolved', {
|
|
191
|
+
type: 'spacing_conflict',
|
|
192
|
+
property: conflict.property,
|
|
193
|
+
action: 'removed from cssName, kept in atomicClassMap',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 创建向后兼容的配置视图
|
|
200
|
+
createLegacyView(modernConfig) {
|
|
201
|
+
return {
|
|
202
|
+
baseUnit: modernConfig.system?.baseUnit || modernConfig.baseUnit,
|
|
203
|
+
unitConversion: modernConfig.system?.unitConversion || modernConfig.unitConversion,
|
|
204
|
+
compression: modernConfig.system?.compression || modernConfig.compression,
|
|
205
|
+
cssName: modernConfig.cssName || modernConfig.compatibility?.legacy?.cssName,
|
|
206
|
+
baseClassName:
|
|
207
|
+
modernConfig.baseClassName || modernConfig.compatibility?.legacy?.baseClassName,
|
|
208
|
+
atomicClassMap:
|
|
209
|
+
modernConfig.atomicClassMap || modernConfig.compatibility?.legacy?.atomicClassMap,
|
|
210
|
+
importantFlags: modernConfig.importantFlags,
|
|
211
|
+
multiFile: modernConfig.multiFile,
|
|
212
|
+
output: modernConfig.output,
|
|
213
|
+
variants: modernConfig.variants,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 生成兼容性报告
|
|
218
|
+
generateCompatibilityReport(originalConfig, adaptedConfig) {
|
|
219
|
+
const report = {
|
|
220
|
+
originalFormat: this.isNewFormat(originalConfig) ? 'modern' : 'legacy',
|
|
221
|
+
adaptedFormat: 'modern',
|
|
222
|
+
conflicts: [],
|
|
223
|
+
warnings: [],
|
|
224
|
+
recommendations: [],
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// 检查配置冲突
|
|
228
|
+
const validation = this.configValidator.validateConfig(adaptedConfig);
|
|
229
|
+
report.warnings = validation.warnings;
|
|
230
|
+
|
|
231
|
+
// 检查text属性冲突
|
|
232
|
+
if (originalConfig.cssName?.text && originalConfig.atomicClassMap?.text) {
|
|
233
|
+
report.conflicts.push({
|
|
234
|
+
type: 'property_conflict',
|
|
235
|
+
property: 'text',
|
|
236
|
+
description: 'text property defined in both cssName and atomicClassMap',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 生成建议
|
|
241
|
+
if (originalConfig.baseUnit !== adaptedConfig.system?.baseUnit) {
|
|
242
|
+
report.recommendations.push('Consider using consistent base unit across all configurations');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!originalConfig.compression && adaptedConfig.system?.compression) {
|
|
246
|
+
report.recommendations.push(
|
|
247
|
+
'Compression has been enabled in the adapted configuration for better performance'
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return report;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 获取适配器统计信息
|
|
255
|
+
getStats() {
|
|
256
|
+
return {
|
|
257
|
+
adaptationsCount: this.adaptationsCount || 0,
|
|
258
|
+
conflictsResolved: this.conflictsResolved || 0,
|
|
259
|
+
lastAdaptation: this.lastAdaptation || null,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = CompatibilityAdapter;
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
class ConfigManager {
|
|
4
|
+
constructor(eventBus, configPath = './class2css.config.js') {
|
|
5
|
+
this.eventBus = eventBus;
|
|
6
|
+
this.configPath = configPath;
|
|
7
|
+
this.config = null;
|
|
8
|
+
this.importantFlags = null;
|
|
9
|
+
this.cssNameMap = null;
|
|
10
|
+
this.baseClassNameMap = null;
|
|
11
|
+
this.userStaticClassSet = null;
|
|
12
|
+
this.userBaseClass = null;
|
|
13
|
+
this.userStaticClass = null;
|
|
14
|
+
this._fileUtils = null;
|
|
15
|
+
this._logger = null;
|
|
16
|
+
this._commonCssCache = null;
|
|
17
|
+
this.breakpoints = null;
|
|
18
|
+
|
|
19
|
+
this.loadConfig();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
loadConfig() {
|
|
23
|
+
try {
|
|
24
|
+
// 解析绝对路径
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const absoluteConfigPath = path.resolve(this.configPath);
|
|
27
|
+
this.configPath = absoluteConfigPath; // 保存绝对路径
|
|
28
|
+
|
|
29
|
+
// 清除require缓存以确保重新加载
|
|
30
|
+
delete require.cache[require.resolve(absoluteConfigPath)];
|
|
31
|
+
|
|
32
|
+
// 清除样式配置文件的缓存(如果存在)
|
|
33
|
+
try {
|
|
34
|
+
const stylesConfigPath = path.resolve(path.dirname(absoluteConfigPath), 'styles.config.js');
|
|
35
|
+
if (require.cache[stylesConfigPath]) {
|
|
36
|
+
delete require.cache[stylesConfigPath];
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// 样式配置文件不存在或无法解析,忽略
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.config = require(absoluteConfigPath);
|
|
43
|
+
|
|
44
|
+
this.updateConfigReferences();
|
|
45
|
+
this.eventBus.emit('config:loaded', this.config);
|
|
46
|
+
|
|
47
|
+
return this.config;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
this.eventBus.emit('config:error', error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
updateConfigReferences() {
|
|
55
|
+
// 更新基础配置 - 支持新的system配置结构
|
|
56
|
+
this.baseUnit = this.config.system?.baseUnit || this.config.baseUnit || 'px';
|
|
57
|
+
this.multiFile = this.config.multiFile;
|
|
58
|
+
|
|
59
|
+
// 更新自定义 important 标识
|
|
60
|
+
this.importantFlags = {
|
|
61
|
+
prefix: this.config.importantFlags?.prefix || ['!'],
|
|
62
|
+
suffix: this.config.importantFlags?.suffix || ['_i', '-i'],
|
|
63
|
+
custom: this.config.importantFlags?.custom || [],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// 从atomicRules构建类名映射
|
|
67
|
+
const cssNameMap = this.buildCssNameMapFromAtomicRules();
|
|
68
|
+
|
|
69
|
+
// 获取baseClassName配置
|
|
70
|
+
const baseClassName = this.config.baseClassName || {};
|
|
71
|
+
|
|
72
|
+
// 更新类名配置
|
|
73
|
+
this.userBaseClass = Object.entries(baseClassName).filter(([k, v]) => typeof v === 'object');
|
|
74
|
+
this.userStaticClass = Object.entries(baseClassName).filter(([k, v]) => typeof v === 'string');
|
|
75
|
+
|
|
76
|
+
// 重建索引和缓存
|
|
77
|
+
this.cssNameMap = cssNameMap;
|
|
78
|
+
this.baseClassNameMap = new Map(Object.entries(baseClassName));
|
|
79
|
+
this.userStaticClassSet = new Set(this.userStaticClass.map(([k]) => k));
|
|
80
|
+
|
|
81
|
+
// 清空共用CSS缓存,确保配置变更时重新读取
|
|
82
|
+
this._commonCssCache = null;
|
|
83
|
+
|
|
84
|
+
// 更新 breakpoints 配置(响应式断点)
|
|
85
|
+
this.updateBreakpoints();
|
|
86
|
+
|
|
87
|
+
this.eventBus.emit('config:references:updated');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 更新 breakpoints 配置
|
|
91
|
+
updateBreakpoints() {
|
|
92
|
+
// 默认 Tailwind 断点值
|
|
93
|
+
const defaultBreakpoints = {
|
|
94
|
+
sm: '640px',
|
|
95
|
+
md: '768px',
|
|
96
|
+
lg: '1024px',
|
|
97
|
+
xl: '1280px',
|
|
98
|
+
'2xl': '1536px',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// 如果配置中有 breakpoints,则合并(用户配置优先)
|
|
102
|
+
const configBreakpoints = this.config.breakpoints || {};
|
|
103
|
+
this.breakpoints = { ...defaultBreakpoints, ...configBreakpoints };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 从atomicRules构建cssName格式的映射
|
|
107
|
+
buildCssNameMapFromAtomicRules() {
|
|
108
|
+
const cssNameMap = new Map();
|
|
109
|
+
const atomicRules = this.config.atomicRules || {};
|
|
110
|
+
|
|
111
|
+
// 遍历所有规则类别
|
|
112
|
+
Object.values(atomicRules).forEach((category) => {
|
|
113
|
+
Object.entries(category).forEach(([className, rule]) => {
|
|
114
|
+
// 转换新格式到旧格式以保持兼容
|
|
115
|
+
const legacyFormat = {
|
|
116
|
+
classArr: rule.properties || [],
|
|
117
|
+
unit: rule.defaultUnit || '',
|
|
118
|
+
skipConversion: rule.skipConversion || false,
|
|
119
|
+
};
|
|
120
|
+
cssNameMap.set(className, legacyFormat);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return cssNameMap;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 配置获取方法
|
|
128
|
+
getConfig() {
|
|
129
|
+
return this.config;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getImportantFlags() {
|
|
133
|
+
return this.importantFlags;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getCssNameMap() {
|
|
137
|
+
return this.cssNameMap;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getBaseClassNameMap() {
|
|
141
|
+
return this.baseClassNameMap;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getUserStaticClassSet() {
|
|
145
|
+
return this.userStaticClassSet;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getUserBaseClass() {
|
|
149
|
+
return this.userBaseClass;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getUserStaticClass() {
|
|
153
|
+
return this.userStaticClass;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getBaseUnit() {
|
|
157
|
+
return this.baseUnit;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getMultiFile() {
|
|
161
|
+
return this.multiFile;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 获取 multiFile.entry 下的所有监听/扫描入口路径(支持多目录/多文件)
|
|
166
|
+
* - 兼容:entry.path 可以是 string 或 string[]
|
|
167
|
+
* - 推荐:entry.paths: string[]
|
|
168
|
+
* @returns {string[]}
|
|
169
|
+
*/
|
|
170
|
+
getMultiFileEntryPaths() {
|
|
171
|
+
const entry = this.multiFile?.entry;
|
|
172
|
+
const pathsValue = entry?.paths ?? entry?.path;
|
|
173
|
+
|
|
174
|
+
if (Array.isArray(pathsValue)) {
|
|
175
|
+
return pathsValue.filter((p) => typeof p === 'string' && p.trim()).map((p) => p.trim());
|
|
176
|
+
}
|
|
177
|
+
if (typeof pathsValue === 'string' && pathsValue.trim()) {
|
|
178
|
+
return [pathsValue.trim()];
|
|
179
|
+
}
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getUnitConversion() {
|
|
184
|
+
return Number(this.config.system?.unitConversion || this.config.unitConversion) || 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getCompression() {
|
|
188
|
+
return this.config.system?.compression || this.config.compression || false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getCssFormat() {
|
|
192
|
+
// 获取CSS格式配置,默认返回 'multiLine'
|
|
193
|
+
return this.config.system?.cssFormat || 'multiLine';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getSortClasses() {
|
|
197
|
+
// 获取排序配置,默认返回 false
|
|
198
|
+
return this.config.system?.sortClasses || false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getBreakpoints() {
|
|
202
|
+
// 获取响应式断点配置
|
|
203
|
+
return this.breakpoints || {};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getVariants() {
|
|
207
|
+
// 获取变体配置(responsive, states, darkMode 等)
|
|
208
|
+
return this.config.variants || {};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getOutput() {
|
|
212
|
+
return this.config.output;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 设置依赖(FileUtils和Logger)
|
|
216
|
+
setDependencies(fileUtils, logger) {
|
|
217
|
+
this._fileUtils = fileUtils;
|
|
218
|
+
this._logger = logger;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 获取共用CSS文件路径
|
|
222
|
+
getCommonCssPath() {
|
|
223
|
+
// 优先从 multiFile.output.commonCssPath 读取
|
|
224
|
+
let commonCssPath = this.multiFile?.output?.commonCssPath;
|
|
225
|
+
|
|
226
|
+
// 如果没有,尝试从 output.commonCssPath 读取(向后兼容)
|
|
227
|
+
if (!commonCssPath && this.config?.output?.commonCssPath) {
|
|
228
|
+
commonCssPath = this.config.output.commonCssPath;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return commonCssPath || null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 读取共用CSS文件内容
|
|
235
|
+
async getCommonCssContent() {
|
|
236
|
+
// 检查依赖是否已设置
|
|
237
|
+
if (!this._fileUtils || !this._logger) {
|
|
238
|
+
return '';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 获取共用CSS文件路径
|
|
242
|
+
const commonCssPath = this.getCommonCssPath();
|
|
243
|
+
if (!commonCssPath) {
|
|
244
|
+
return '';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 如果缓存存在,直接返回
|
|
248
|
+
if (this._commonCssCache !== null) {
|
|
249
|
+
return this._commonCssCache;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// 解析路径:支持绝对路径和相对路径
|
|
254
|
+
// 如果是绝对路径,直接使用;如果是相对路径,基于配置文件所在目录解析
|
|
255
|
+
let absolutePath;
|
|
256
|
+
if (path.isAbsolute(commonCssPath)) {
|
|
257
|
+
// 绝对路径:直接使用并规范化
|
|
258
|
+
absolutePath = path.normalize(commonCssPath);
|
|
259
|
+
} else {
|
|
260
|
+
// 相对路径:基于配置文件所在目录解析
|
|
261
|
+
const configDir = path.dirname(this.configPath);
|
|
262
|
+
absolutePath = path.resolve(configDir, commonCssPath);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 检查文件是否存在
|
|
266
|
+
const exists = await this._fileUtils.fileExists(absolutePath);
|
|
267
|
+
if (!exists) {
|
|
268
|
+
this._logger.warn(`共用CSS文件不存在: ${absolutePath}`);
|
|
269
|
+
this._commonCssCache = '';
|
|
270
|
+
return '';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 读取文件内容
|
|
274
|
+
const content = await this._fileUtils.readFile(absolutePath, 'utf-8');
|
|
275
|
+
if (content === null) {
|
|
276
|
+
this._logger.warn(`读取共用CSS文件失败: ${absolutePath}`);
|
|
277
|
+
this._commonCssCache = '';
|
|
278
|
+
return '';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 缓存内容
|
|
282
|
+
this._commonCssCache = content;
|
|
283
|
+
this._logger.info(`成功读取共用CSS文件: ${absolutePath} (${content.length} 字符)`);
|
|
284
|
+
return content;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
this._logger.warn(`读取共用CSS文件时出错: ${commonCssPath}`, error.message);
|
|
287
|
+
this._commonCssCache = '';
|
|
288
|
+
return '';
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 配置验证
|
|
293
|
+
validateConfig() {
|
|
294
|
+
const errors = [];
|
|
295
|
+
|
|
296
|
+
if (!this.config) {
|
|
297
|
+
errors.push('Configuration is null or undefined');
|
|
298
|
+
return errors;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 检查系统配置
|
|
302
|
+
if (!this.config.system?.baseUnit && !this.config.baseUnit) {
|
|
303
|
+
errors.push('baseUnit configuration is required (either in system.baseUnit or baseUnit)');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 检查原子规则配置
|
|
307
|
+
const atomicRules = this.config.atomicRules;
|
|
308
|
+
const baseClassName = this.config.baseClassName;
|
|
309
|
+
|
|
310
|
+
if (!atomicRules || typeof atomicRules !== 'object') {
|
|
311
|
+
errors.push('atomicRules configuration is required and must be an object');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!baseClassName || typeof baseClassName !== 'object') {
|
|
315
|
+
errors.push('baseClassName configuration is required and must be an object');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (this.config.multiFile) {
|
|
319
|
+
const entry = this.config.multiFile.entry;
|
|
320
|
+
const pathsValue = entry?.paths ?? entry?.path;
|
|
321
|
+
const hasValidString = typeof pathsValue === 'string' && pathsValue.trim().length > 0;
|
|
322
|
+
const hasValidArray =
|
|
323
|
+
Array.isArray(pathsValue) &&
|
|
324
|
+
pathsValue.some((p) => typeof p === 'string' && p.trim().length > 0);
|
|
325
|
+
|
|
326
|
+
if (!hasValidString && !hasValidArray) {
|
|
327
|
+
errors.push('multiFile.entry.path (string|string[]) or multiFile.entry.paths (string[]) is required when multiFile is enabled');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (this.config.output) {
|
|
332
|
+
if (!this.config.output.path || !this.config.output.fileName) {
|
|
333
|
+
errors.push('output.path and output.fileName are required');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return errors;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 配置重载
|
|
341
|
+
async reloadConfig() {
|
|
342
|
+
try {
|
|
343
|
+
this.eventBus.emit('config:reloading');
|
|
344
|
+
this.loadConfig();
|
|
345
|
+
this.eventBus.emit('config:reloaded', this.config);
|
|
346
|
+
return true;
|
|
347
|
+
} catch (error) {
|
|
348
|
+
this.eventBus.emit('config:reload:error', error);
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 配置信息输出
|
|
354
|
+
getConfigInfo() {
|
|
355
|
+
return {
|
|
356
|
+
userBaseClassCount: this.userBaseClass.length,
|
|
357
|
+
userStaticClassCount: this.userStaticClass.length,
|
|
358
|
+
importantFlags: this.importantFlags,
|
|
359
|
+
baseUnit: this.baseUnit,
|
|
360
|
+
multiFile: !!this.multiFile,
|
|
361
|
+
compression: this.getCompression(),
|
|
362
|
+
unitConversion: this.getUnitConversion(),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 运行时覆盖配置
|
|
367
|
+
overrideConfig(overrides) {
|
|
368
|
+
if (!overrides || typeof overrides !== 'object') {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 确保 config 对象存在
|
|
373
|
+
if (!this.config) {
|
|
374
|
+
this.config = {};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 覆盖输入目录(multiFile.entry.path)
|
|
378
|
+
if (overrides.inputPath) {
|
|
379
|
+
if (!this.config.multiFile) {
|
|
380
|
+
this.config.multiFile = {};
|
|
381
|
+
}
|
|
382
|
+
if (!this.config.multiFile.entry) {
|
|
383
|
+
this.config.multiFile.entry = {};
|
|
384
|
+
}
|
|
385
|
+
this.config.multiFile.entry.path = overrides.inputPath;
|
|
386
|
+
this.eventBus.emit('config:override:inputPath', overrides.inputPath);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 覆盖输出目录(multiFile.output.path)
|
|
390
|
+
if (overrides.outputPath) {
|
|
391
|
+
if (!this.config.multiFile) {
|
|
392
|
+
this.config.multiFile = {};
|
|
393
|
+
}
|
|
394
|
+
if (!this.config.multiFile.output) {
|
|
395
|
+
this.config.multiFile.output = {};
|
|
396
|
+
}
|
|
397
|
+
this.config.multiFile.output.path = overrides.outputPath;
|
|
398
|
+
this.eventBus.emit('config:override:outputPath', overrides.outputPath);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 覆盖输出文件名(multiFile.output.fileName)
|
|
402
|
+
if (overrides.outputFileName) {
|
|
403
|
+
if (!this.config.multiFile) {
|
|
404
|
+
this.config.multiFile = {};
|
|
405
|
+
}
|
|
406
|
+
if (!this.config.multiFile.output) {
|
|
407
|
+
this.config.multiFile.output = {};
|
|
408
|
+
}
|
|
409
|
+
this.config.multiFile.output.fileName = overrides.outputFileName;
|
|
410
|
+
this.eventBus.emit('config:override:outputFileName', overrides.outputFileName);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 覆盖输出类型(multiFile.output.cssOutType)
|
|
414
|
+
if (overrides.outputType) {
|
|
415
|
+
if (!this.config.multiFile) {
|
|
416
|
+
this.config.multiFile = {};
|
|
417
|
+
}
|
|
418
|
+
if (!this.config.multiFile.output) {
|
|
419
|
+
this.config.multiFile.output = {};
|
|
420
|
+
}
|
|
421
|
+
this.config.multiFile.output.cssOutType = overrides.outputType;
|
|
422
|
+
this.eventBus.emit('config:override:outputType', overrides.outputType);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 重新更新配置引用
|
|
426
|
+
this.updateConfigReferences();
|
|
427
|
+
this.eventBus.emit('config:overridden', overrides);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
module.exports = ConfigManager;
|