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,172 @@
|
|
|
1
|
+
class SmartThrottle {
|
|
2
|
+
constructor(eventBus) {
|
|
3
|
+
this.eventBus = eventBus;
|
|
4
|
+
this.timers = new Map();
|
|
5
|
+
this.pending = new Set();
|
|
6
|
+
this.priorities = new Map();
|
|
7
|
+
this.taskCount = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
throttle(key, fn, delay = 200, priority = 0) {
|
|
11
|
+
this.taskCount++;
|
|
12
|
+
const taskId = `${key}_${this.taskCount}`;
|
|
13
|
+
|
|
14
|
+
// 如果有更高优先级的任务,取消当前任务
|
|
15
|
+
if (this.pending.has(key)) {
|
|
16
|
+
const currentPriority = this.priorities.get(key) || 0;
|
|
17
|
+
if (priority <= currentPriority) {
|
|
18
|
+
this.eventBus.emit('throttle:task:ignored', { key, priority, currentPriority });
|
|
19
|
+
return; // 当前任务优先级不够高,忽略
|
|
20
|
+
} else {
|
|
21
|
+
// 取消当前较低优先级的任务
|
|
22
|
+
this.cancel(key);
|
|
23
|
+
this.eventBus.emit('throttle:task:cancelled', { key, reason: 'higher_priority' });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.pending.add(key);
|
|
28
|
+
this.priorities.set(key, priority);
|
|
29
|
+
|
|
30
|
+
if (this.timers.has(key)) {
|
|
31
|
+
clearTimeout(this.timers.get(key));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
this.pending.delete(key);
|
|
36
|
+
this.timers.delete(key);
|
|
37
|
+
this.priorities.delete(key);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
this.eventBus.emit('throttle:task:executing', { key, priority });
|
|
41
|
+
fn();
|
|
42
|
+
this.eventBus.emit('throttle:task:completed', { key, priority });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.eventBus.emit('throttle:task:error', { key, error });
|
|
45
|
+
}
|
|
46
|
+
}, delay);
|
|
47
|
+
|
|
48
|
+
this.timers.set(key, timer);
|
|
49
|
+
this.eventBus.emit('throttle:task:scheduled', { key, delay, priority });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cancel(key) {
|
|
53
|
+
if (this.timers.has(key)) {
|
|
54
|
+
clearTimeout(this.timers.get(key));
|
|
55
|
+
this.timers.delete(key);
|
|
56
|
+
this.pending.delete(key);
|
|
57
|
+
this.priorities.delete(key);
|
|
58
|
+
this.eventBus.emit('throttle:task:cancelled', { key, reason: 'manual' });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cancelAll() {
|
|
63
|
+
const cancelledCount = this.timers.size;
|
|
64
|
+
|
|
65
|
+
this.timers.forEach((timer, key) => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.timers.clear();
|
|
70
|
+
this.pending.clear();
|
|
71
|
+
this.priorities.clear();
|
|
72
|
+
|
|
73
|
+
this.eventBus.emit('throttle:all:cancelled', { cancelledCount });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 批量节流处理
|
|
77
|
+
batchThrottle(tasks, delay = 200, priority = 0) {
|
|
78
|
+
const batchId = `batch_${Date.now()}`;
|
|
79
|
+
const results = [];
|
|
80
|
+
|
|
81
|
+
this.throttle(
|
|
82
|
+
batchId,
|
|
83
|
+
() => {
|
|
84
|
+
tasks.forEach((task, index) => {
|
|
85
|
+
try {
|
|
86
|
+
const result = task();
|
|
87
|
+
results[index] = { success: true, result };
|
|
88
|
+
} catch (error) {
|
|
89
|
+
results[index] = { success: false, error };
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.eventBus.emit('throttle:batch:completed', { batchId, results });
|
|
94
|
+
},
|
|
95
|
+
delay,
|
|
96
|
+
priority
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return batchId;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 优先级队列处理
|
|
103
|
+
addToPriorityQueue(key, fn, priority = 0) {
|
|
104
|
+
if (!this.priorityQueue) {
|
|
105
|
+
this.priorityQueue = [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.priorityQueue.push({ key, fn, priority });
|
|
109
|
+
this.priorityQueue.sort((a, b) => b.priority - a.priority); // 高优先级在前
|
|
110
|
+
|
|
111
|
+
this.eventBus.emit('throttle:queue:added', {
|
|
112
|
+
key,
|
|
113
|
+
priority,
|
|
114
|
+
queueSize: this.priorityQueue.length,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
processPriorityQueue(delay = 100) {
|
|
119
|
+
if (!this.priorityQueue || this.priorityQueue.length === 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const task = this.priorityQueue.shift();
|
|
124
|
+
this.throttle(task.key, task.fn, delay, task.priority);
|
|
125
|
+
|
|
126
|
+
this.eventBus.emit('throttle:queue:processed', {
|
|
127
|
+
key: task.key,
|
|
128
|
+
priority: task.priority,
|
|
129
|
+
remainingTasks: this.priorityQueue.length,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 状态查询
|
|
134
|
+
isPending(key) {
|
|
135
|
+
return this.pending.has(key);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getPendingCount() {
|
|
139
|
+
return this.pending.size;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getTimerCount() {
|
|
143
|
+
return this.timers.size;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getPriority(key) {
|
|
147
|
+
return this.priorities.get(key) || 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getStats() {
|
|
151
|
+
return {
|
|
152
|
+
pendingCount: this.pending.size,
|
|
153
|
+
timerCount: this.timers.size,
|
|
154
|
+
priorityQueueSize: this.priorityQueue ? this.priorityQueue.length : 0,
|
|
155
|
+
taskCount: this.taskCount,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 清理过期任务
|
|
160
|
+
cleanup() {
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
const cleanedCount = 0;
|
|
163
|
+
|
|
164
|
+
// 这里可以添加更复杂的清理逻辑
|
|
165
|
+
// 比如清理长时间未执行的任务
|
|
166
|
+
|
|
167
|
+
this.eventBus.emit('throttle:cleanup:completed', { cleanedCount });
|
|
168
|
+
return cleanedCount;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = SmartThrottle;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
class UnitProcessor {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.config = config;
|
|
4
|
+
this.baseUnit = config.system?.baseUnit || config.baseUnit || 'px';
|
|
5
|
+
this.unitConversion = parseFloat(config.system?.unitConversion || config.unitConversion || 1);
|
|
6
|
+
|
|
7
|
+
// 特殊属性映射(不需要单位的属性)
|
|
8
|
+
this.unitlessProperties = new Set([
|
|
9
|
+
'opacity',
|
|
10
|
+
'z-index',
|
|
11
|
+
'line-height',
|
|
12
|
+
'font-weight',
|
|
13
|
+
'flex',
|
|
14
|
+
'flex-grow',
|
|
15
|
+
'flex-shrink',
|
|
16
|
+
'order',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
// 属性默认单位映射
|
|
20
|
+
this.propertyUnits = {
|
|
21
|
+
'font-size': this.baseUnit,
|
|
22
|
+
width: this.baseUnit,
|
|
23
|
+
height: this.baseUnit,
|
|
24
|
+
'max-width': this.baseUnit,
|
|
25
|
+
'max-height': this.baseUnit,
|
|
26
|
+
margin: this.baseUnit,
|
|
27
|
+
'margin-top': this.baseUnit,
|
|
28
|
+
'margin-right': this.baseUnit,
|
|
29
|
+
'margin-bottom': this.baseUnit,
|
|
30
|
+
'margin-left': this.baseUnit,
|
|
31
|
+
padding: this.baseUnit,
|
|
32
|
+
'padding-top': this.baseUnit,
|
|
33
|
+
'padding-right': this.baseUnit,
|
|
34
|
+
'padding-bottom': this.baseUnit,
|
|
35
|
+
'padding-left': this.baseUnit,
|
|
36
|
+
'border-width': this.baseUnit,
|
|
37
|
+
'border-top-width': this.baseUnit,
|
|
38
|
+
'border-right-width': this.baseUnit,
|
|
39
|
+
'border-bottom-width': this.baseUnit,
|
|
40
|
+
'border-left-width': this.baseUnit,
|
|
41
|
+
'border-radius': this.baseUnit,
|
|
42
|
+
top: this.baseUnit,
|
|
43
|
+
right: this.baseUnit,
|
|
44
|
+
bottom: this.baseUnit,
|
|
45
|
+
left: this.baseUnit,
|
|
46
|
+
gap: this.baseUnit,
|
|
47
|
+
'letter-spacing': this.baseUnit,
|
|
48
|
+
transition: 'ms',
|
|
49
|
+
opacity: '',
|
|
50
|
+
'z-index': '',
|
|
51
|
+
'line-height': '',
|
|
52
|
+
'font-weight': '',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 智能解析数值和单位
|
|
57
|
+
parseValue(value, property, defaultUnit = null) {
|
|
58
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const valueStr = String(value).trim();
|
|
63
|
+
|
|
64
|
+
// 处理特殊值
|
|
65
|
+
if (this.isSpecialValue(valueStr)) {
|
|
66
|
+
return valueStr;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 检测用户是否已指定单位
|
|
70
|
+
const hasUnit = this.detectUnit(valueStr);
|
|
71
|
+
if (hasUnit) {
|
|
72
|
+
return valueStr; // 保持用户指定的单位
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 提取数值部分
|
|
76
|
+
const numericValue = this.extractNumericValue(valueStr);
|
|
77
|
+
if (numericValue === null) {
|
|
78
|
+
return valueStr; // 无法解析,保持原值
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 处理特殊属性
|
|
82
|
+
if (this.isSpecialProperty(property, numericValue)) {
|
|
83
|
+
return this.processSpecialProperty(property, numericValue);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 应用单位转换
|
|
87
|
+
const convertedValue = this.applyUnitConversion(numericValue, property, defaultUnit);
|
|
88
|
+
|
|
89
|
+
return convertedValue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 检测特殊值
|
|
93
|
+
isSpecialValue(value) {
|
|
94
|
+
const specialValues = [
|
|
95
|
+
'auto',
|
|
96
|
+
'none',
|
|
97
|
+
'inherit',
|
|
98
|
+
'initial',
|
|
99
|
+
'unset',
|
|
100
|
+
'100%',
|
|
101
|
+
'50%',
|
|
102
|
+
'25%',
|
|
103
|
+
'75%',
|
|
104
|
+
'100vw',
|
|
105
|
+
'100vh',
|
|
106
|
+
'50vw',
|
|
107
|
+
'50vh',
|
|
108
|
+
'full',
|
|
109
|
+
'screen',
|
|
110
|
+
'min',
|
|
111
|
+
'max',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
specialValues.includes(value) ||
|
|
116
|
+
value.includes('%') ||
|
|
117
|
+
value.includes('vw') ||
|
|
118
|
+
value.includes('vh')
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 检测单位
|
|
123
|
+
detectUnit(value) {
|
|
124
|
+
const unitPattern = /[a-zA-Z%]+$/;
|
|
125
|
+
return unitPattern.test(value);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 提取数值
|
|
129
|
+
extractNumericValue(value) {
|
|
130
|
+
const numericPattern = /^-?\d*\.?\d+/;
|
|
131
|
+
const match = value.match(numericPattern);
|
|
132
|
+
return match ? parseFloat(match[0]) : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 检查特殊属性
|
|
136
|
+
isSpecialProperty(property, value) {
|
|
137
|
+
return (
|
|
138
|
+
this.unitlessProperties.has(property) || property === 'opacity' || property === 'line-height'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 处理特殊属性
|
|
143
|
+
processSpecialProperty(property, value) {
|
|
144
|
+
switch (property) {
|
|
145
|
+
case 'opacity':
|
|
146
|
+
// 如果值大于1,假设是百分比,转换为0-1范围
|
|
147
|
+
return value > 1 ? value / 100 : value;
|
|
148
|
+
|
|
149
|
+
case 'line-height':
|
|
150
|
+
// 如果值大于10,假设是像素值,转换为无单位值
|
|
151
|
+
return value > 10 ? value / 16 : value; // 假设基准字体大小为16px
|
|
152
|
+
|
|
153
|
+
case 'z-index':
|
|
154
|
+
case 'font-weight':
|
|
155
|
+
return value; // 保持原值
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 应用单位转换
|
|
163
|
+
applyUnitConversion(numericValue, property, defaultUnit = null) {
|
|
164
|
+
// 确定使用的单位
|
|
165
|
+
const unit = defaultUnit || this.getPropertyUnit(property);
|
|
166
|
+
|
|
167
|
+
// 如果属性不需要单位,直接返回数值
|
|
168
|
+
if (!unit) {
|
|
169
|
+
return numericValue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 应用转换比例
|
|
173
|
+
const convertedValue = numericValue * this.unitConversion;
|
|
174
|
+
|
|
175
|
+
// 格式化数值(避免过多小数位)
|
|
176
|
+
const formattedValue = this.formatNumericValue(convertedValue);
|
|
177
|
+
|
|
178
|
+
return `${formattedValue}${unit}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 获取属性默认单位
|
|
182
|
+
getPropertyUnit(property) {
|
|
183
|
+
// 检查属性映射
|
|
184
|
+
if (this.propertyUnits[property]) {
|
|
185
|
+
return this.propertyUnits[property];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 检查属性类型
|
|
189
|
+
if (property.includes('width') || property.includes('height')) {
|
|
190
|
+
return this.baseUnit;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (property.includes('margin') || property.includes('padding')) {
|
|
194
|
+
return this.baseUnit;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (property.includes('border')) {
|
|
198
|
+
return this.baseUnit;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (property.includes('font-size')) {
|
|
202
|
+
return this.baseUnit;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
property.includes('top') ||
|
|
207
|
+
property.includes('right') ||
|
|
208
|
+
property.includes('bottom') ||
|
|
209
|
+
property.includes('left')
|
|
210
|
+
) {
|
|
211
|
+
return this.baseUnit;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 默认返回基础单位
|
|
215
|
+
return this.baseUnit;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 格式化数值
|
|
219
|
+
formatNumericValue(value) {
|
|
220
|
+
// 如果是整数,返回整数
|
|
221
|
+
if (Number.isInteger(value)) {
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 如果是小数,保留最多3位小数
|
|
226
|
+
return parseFloat(value.toFixed(3));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 批量处理值
|
|
230
|
+
processValues(values, property, defaultUnit = null) {
|
|
231
|
+
if (Array.isArray(values)) {
|
|
232
|
+
return values.map((value) => this.parseValue(value, property, defaultUnit));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return this.parseValue(values, property, defaultUnit);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 处理规则配置
|
|
239
|
+
processRuleConfig(ruleConfig, value) {
|
|
240
|
+
const { properties, defaultUnit, value: fixedValue, skipConversion } = ruleConfig;
|
|
241
|
+
|
|
242
|
+
// 如果有固定值,直接返回
|
|
243
|
+
if (fixedValue !== undefined) {
|
|
244
|
+
return fixedValue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 如果跳过转换,直接拼接单位
|
|
248
|
+
if (skipConversion === true && defaultUnit) {
|
|
249
|
+
const numericValue = this.extractNumericValue(value);
|
|
250
|
+
if (numericValue !== null) {
|
|
251
|
+
// 处理小数点(保持与传统方式一致)
|
|
252
|
+
let processedValue = numericValue;
|
|
253
|
+
if (String(value).startsWith('0') && String(value).length > 1) {
|
|
254
|
+
processedValue = parseFloat('0.' + String(value).substring(1));
|
|
255
|
+
}
|
|
256
|
+
return `${processedValue}${defaultUnit}`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 处理多个属性
|
|
261
|
+
if (Array.isArray(properties)) {
|
|
262
|
+
return properties.map((property) => this.parseValue(value, property, defaultUnit));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 处理单个属性
|
|
266
|
+
return this.parseValue(value, properties, defaultUnit);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 验证单位一致性
|
|
270
|
+
validateUnitConsistency(property, value, expectedUnit) {
|
|
271
|
+
if (!expectedUnit) {
|
|
272
|
+
return true; // 无单位属性
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const detectedUnit = this.extractUnit(value);
|
|
276
|
+
if (!detectedUnit) {
|
|
277
|
+
return true; // 无单位值,使用默认单位
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return detectedUnit === expectedUnit;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 提取单位
|
|
284
|
+
extractUnit(value) {
|
|
285
|
+
const unitPattern = /[a-zA-Z%]+$/;
|
|
286
|
+
const match = String(value).match(unitPattern);
|
|
287
|
+
return match ? match[0] : null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 获取配置信息
|
|
291
|
+
getConfig() {
|
|
292
|
+
return {
|
|
293
|
+
baseUnit: this.baseUnit,
|
|
294
|
+
unitConversion: this.unitConversion,
|
|
295
|
+
propertyUnits: this.propertyUnits,
|
|
296
|
+
unitlessProperties: Array.from(this.unitlessProperties),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 更新配置
|
|
301
|
+
updateConfig(newConfig) {
|
|
302
|
+
if (newConfig.system?.baseUnit) {
|
|
303
|
+
this.baseUnit = newConfig.system.baseUnit;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (newConfig.system?.unitConversion) {
|
|
307
|
+
this.unitConversion = parseFloat(newConfig.system.unitConversion);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 更新属性单位映射
|
|
311
|
+
if (newConfig.system?.propertyUnits) {
|
|
312
|
+
this.propertyUnits = { ...this.propertyUnits, ...newConfig.system.propertyUnits };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 创建CSS属性值
|
|
317
|
+
createCSSValue(property, value, unit = null) {
|
|
318
|
+
const processedValue = this.parseValue(value, property, unit);
|
|
319
|
+
|
|
320
|
+
// 如果是数组(多个属性),返回CSS字符串
|
|
321
|
+
if (Array.isArray(processedValue)) {
|
|
322
|
+
return processedValue
|
|
323
|
+
.map((val, index) => {
|
|
324
|
+
const prop = Array.isArray(property) ? property[index] : property;
|
|
325
|
+
return `${prop}: ${val};`;
|
|
326
|
+
})
|
|
327
|
+
.join('');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return `${property}: ${processedValue};`;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = UnitProcessor;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 从 WXSS/CSS 文件中提取 class 选择器集合
|
|
6
|
+
* 主要用于从输出文件中解析已存在的 class,作为增量模式的基线
|
|
7
|
+
*/
|
|
8
|
+
class WxssClassExtractor {
|
|
9
|
+
constructor(eventBus) {
|
|
10
|
+
this.eventBus = eventBus;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 从 CSS/WXSS 文件中提取所有 class 选择器
|
|
15
|
+
* @param {string} filePath - CSS/WXSS 文件路径
|
|
16
|
+
* @returns {Promise<{classList: Set<string>, staticClassList: Set<string>}>}
|
|
17
|
+
*/
|
|
18
|
+
async extractClassesFromFile(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
21
|
+
return this.extractClassesFromContent(content);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// 文件不存在或读取失败时返回空集合
|
|
24
|
+
if (error.code === 'ENOENT') {
|
|
25
|
+
this.eventBus?.emit('wxssExtractor:fileNotFound', { filePath });
|
|
26
|
+
return { classList: new Set(), staticClassList: new Set() };
|
|
27
|
+
}
|
|
28
|
+
this.eventBus?.emit('wxssExtractor:error', { filePath, error: error.message });
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 从 CSS 内容中提取 class 选择器
|
|
35
|
+
* @param {string} cssContent - CSS 内容
|
|
36
|
+
* @returns {{classList: Set<string>, staticClassList: Set<string>}}
|
|
37
|
+
*/
|
|
38
|
+
extractClassesFromContent(cssContent) {
|
|
39
|
+
const classList = new Set();
|
|
40
|
+
const staticClassList = new Set();
|
|
41
|
+
|
|
42
|
+
if (!cssContent || typeof cssContent !== 'string') {
|
|
43
|
+
return { classList, staticClassList };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 匹配 CSS 选择器中的 class
|
|
47
|
+
// 支持:
|
|
48
|
+
// - .className { ... }
|
|
49
|
+
// - .className1, .className2 { ... }
|
|
50
|
+
// - .className:hover { ... } (提取 .className)
|
|
51
|
+
// - .className1.className2 { ... } (提取两个 class)
|
|
52
|
+
// 不处理复杂选择器如 .a .b(后代选择器),只提取直接出现的 class
|
|
53
|
+
const selectorRegex = /\.([a-zA-Z0-9_-]+)(?::[a-zA-Z-]+|\[[^\]]+\])?(?:\.([a-zA-Z0-9_-]+))?(?=\s*[,\{])/g;
|
|
54
|
+
|
|
55
|
+
let match;
|
|
56
|
+
while ((match = selectorRegex.exec(cssContent)) !== null) {
|
|
57
|
+
// 提取第一个 class
|
|
58
|
+
const className1 = match[1];
|
|
59
|
+
if (className1 && this.isValidClassName(className1)) {
|
|
60
|
+
// 判断是否是静态类(通常是不带连字符的简单类名,或特定前缀)
|
|
61
|
+
// 这里使用简单启发式:如果包含连字符且符合动态类模式,认为是动态类
|
|
62
|
+
if (this.isDynamicClass(className1)) {
|
|
63
|
+
classList.add(className1);
|
|
64
|
+
} else {
|
|
65
|
+
staticClassList.add(className1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 提取第二个 class(如果有,如 .a.b)
|
|
70
|
+
const className2 = match[2];
|
|
71
|
+
if (className2 && this.isValidClassName(className2)) {
|
|
72
|
+
if (this.isDynamicClass(className2)) {
|
|
73
|
+
classList.add(className2);
|
|
74
|
+
} else {
|
|
75
|
+
staticClassList.add(className2);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 处理逗号分隔的选择器列表
|
|
81
|
+
// 例如:.class1, .class2, .class3 { ... }
|
|
82
|
+
const commaSeparatedRegex = /\.([a-zA-Z0-9_-]+)(?=\s*[,{])/g;
|
|
83
|
+
let commaMatch;
|
|
84
|
+
while ((commaMatch = commaSeparatedRegex.exec(cssContent)) !== null) {
|
|
85
|
+
const className = commaMatch[1];
|
|
86
|
+
if (className && this.isValidClassName(className)) {
|
|
87
|
+
if (this.isDynamicClass(className)) {
|
|
88
|
+
classList.add(className);
|
|
89
|
+
} else {
|
|
90
|
+
staticClassList.add(className);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.eventBus?.emit('wxssExtractor:extracted', {
|
|
96
|
+
classCount: classList.size,
|
|
97
|
+
staticClassCount: staticClassList.size,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return { classList, staticClassList };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 验证 class 名是否有效
|
|
105
|
+
* @param {string} className
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
isValidClassName(className) {
|
|
109
|
+
if (!className || typeof className !== 'string') {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
// 基本验证:不能为空,不能以数字开头(CSS 规范)
|
|
113
|
+
return className.length > 0 && !/^\d/.test(className);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 判断是否是动态类(通常包含连字符,如 w-100, m-10)
|
|
118
|
+
* @param {string} className
|
|
119
|
+
* @returns {boolean}
|
|
120
|
+
*/
|
|
121
|
+
isDynamicClass(className) {
|
|
122
|
+
// 简单启发式:包含连字符的类名通常是动态类
|
|
123
|
+
// 例如:w-100, m-10, text-16 等
|
|
124
|
+
return className.includes('-');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 从文件路径读取并提取(便捷方法)
|
|
129
|
+
* @param {string} filePath
|
|
130
|
+
* @returns {Promise<{classList: Set<string>, staticClassList: Set<string>}>}
|
|
131
|
+
*/
|
|
132
|
+
async extractFromFile(filePath) {
|
|
133
|
+
return this.extractClassesFromFile(filePath);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = WxssClassExtractor;
|