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,413 @@
|
|
|
1
|
+
const chokidar = require('chokidar');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 配置文件监听器
|
|
6
|
+
* 负责监听配置文件变化并触发热重载
|
|
7
|
+
*/
|
|
8
|
+
class ConfigWatcher {
|
|
9
|
+
constructor(eventBus, configManager, logger) {
|
|
10
|
+
this.eventBus = eventBus;
|
|
11
|
+
this.configManager = configManager;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
|
|
14
|
+
// 监听器实例
|
|
15
|
+
this.watcher = null;
|
|
16
|
+
|
|
17
|
+
// 监听状态
|
|
18
|
+
this.isWatching = false;
|
|
19
|
+
|
|
20
|
+
// 配置文件路径
|
|
21
|
+
this.configPath = null;
|
|
22
|
+
|
|
23
|
+
// 防抖延迟时间(毫秒)
|
|
24
|
+
this.debounceDelay = 300;
|
|
25
|
+
|
|
26
|
+
// 防抖定时器
|
|
27
|
+
this.debounceTimer = null;
|
|
28
|
+
|
|
29
|
+
// 统计信息
|
|
30
|
+
this.stats = {
|
|
31
|
+
watchStartTime: null,
|
|
32
|
+
reloadCount: 0,
|
|
33
|
+
lastReloadTime: null,
|
|
34
|
+
successCount: 0,
|
|
35
|
+
errorCount: 0,
|
|
36
|
+
lastError: null,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
this.logger.info('配置监听器已初始化');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 开始监听配置文件
|
|
44
|
+
* @param {string} configPath - 配置文件路径
|
|
45
|
+
*/
|
|
46
|
+
startWatching(configPath) {
|
|
47
|
+
try {
|
|
48
|
+
if (this.isWatching) {
|
|
49
|
+
this.logger.warn('配置监听器已在监听中');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.configPath = path.resolve(configPath);
|
|
54
|
+
// this.logger.info(`开始监听配置文件: ${this.configPath}`);
|
|
55
|
+
|
|
56
|
+
// 创建文件监听器
|
|
57
|
+
this.watcher = chokidar.watch(this.configPath, {
|
|
58
|
+
ignoreInitial: true,
|
|
59
|
+
persistent: true,
|
|
60
|
+
usePolling: true,
|
|
61
|
+
interval: 300,
|
|
62
|
+
binaryInterval: 600,
|
|
63
|
+
awaitWriteFinish: {
|
|
64
|
+
stabilityThreshold: 200,
|
|
65
|
+
pollInterval: 100,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 监听文件变化事件
|
|
70
|
+
this.watcher.on('change', (filePath) => {
|
|
71
|
+
this.logger.debug(`配置文件已更改: ${filePath}`);
|
|
72
|
+
this.debouncedReload();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 监听文件删除事件
|
|
76
|
+
this.watcher.on('unlink', (filePath) => {
|
|
77
|
+
this.logger.warn(`配置文件已删除: ${filePath}`);
|
|
78
|
+
this.eventBus.emit('config:file:deleted', { filePath });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// 监听错误事件
|
|
82
|
+
this.watcher.on('error', (error) => {
|
|
83
|
+
this.logger.errorWithContext('配置监听器错误', error);
|
|
84
|
+
this.stats.errorCount++;
|
|
85
|
+
this.stats.lastError = error.message;
|
|
86
|
+
this.eventBus.emit('config:watcher:error', { error: error.message });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// 监听准备就绪事件
|
|
90
|
+
this.watcher.on('ready', () => {
|
|
91
|
+
this.isWatching = true;
|
|
92
|
+
this.stats.watchStartTime = new Date();
|
|
93
|
+
this.logger.info(`配置监听器就绪: ${this.configPath}`);
|
|
94
|
+
this.eventBus.emit('config:watcher:ready', { configPath: this.configPath });
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this.logger.errorWithContext('启动配置监听器失败', error);
|
|
98
|
+
this.stats.errorCount++;
|
|
99
|
+
this.stats.lastError = error.message;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 停止监听配置文件
|
|
106
|
+
*/
|
|
107
|
+
stopWatching() {
|
|
108
|
+
try {
|
|
109
|
+
if (!this.isWatching || !this.watcher) {
|
|
110
|
+
this.logger.warn('配置监听器未在监听');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 清除防抖定时器
|
|
115
|
+
if (this.debounceTimer) {
|
|
116
|
+
clearTimeout(this.debounceTimer);
|
|
117
|
+
this.debounceTimer = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 关闭监听器
|
|
121
|
+
this.watcher.close();
|
|
122
|
+
this.watcher = null;
|
|
123
|
+
this.isWatching = false;
|
|
124
|
+
|
|
125
|
+
this.logger.info('配置监听器已停止');
|
|
126
|
+
this.eventBus.emit('config:watcher:stopped', {
|
|
127
|
+
configPath: this.configPath,
|
|
128
|
+
stats: this.getWatcherStats(),
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.logger.errorWithContext('停止配置监听器失败', error);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 防抖重载配置
|
|
138
|
+
*/
|
|
139
|
+
debouncedReload() {
|
|
140
|
+
// 清除之前的定时器
|
|
141
|
+
if (this.debounceTimer) {
|
|
142
|
+
clearTimeout(this.debounceTimer);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 设置新的防抖定时器
|
|
146
|
+
this.debounceTimer = setTimeout(() => {
|
|
147
|
+
this.reloadConfig();
|
|
148
|
+
}, this.debounceDelay);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 重新加载配置文件
|
|
153
|
+
*/
|
|
154
|
+
async reloadConfig() {
|
|
155
|
+
try {
|
|
156
|
+
this.logger.info('正在重载配置...');
|
|
157
|
+
|
|
158
|
+
// 更新统计信息
|
|
159
|
+
this.stats.reloadCount++;
|
|
160
|
+
this.stats.lastReloadTime = new Date();
|
|
161
|
+
|
|
162
|
+
// 通知开始重载
|
|
163
|
+
this.eventBus.emit('config:reload:start', {
|
|
164
|
+
configPath: this.configPath,
|
|
165
|
+
reloadCount: this.stats.reloadCount,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 清除Node.js模块缓存
|
|
169
|
+
this.clearModuleCache();
|
|
170
|
+
|
|
171
|
+
// 重新加载配置
|
|
172
|
+
const oldConfig = this.configManager.getConfig();
|
|
173
|
+
await this.configManager.reloadConfig();
|
|
174
|
+
const newConfig = this.configManager.getConfig();
|
|
175
|
+
|
|
176
|
+
// 验证新配置
|
|
177
|
+
const validation = this.validateConfigChange(oldConfig, newConfig);
|
|
178
|
+
|
|
179
|
+
if (!validation.isValid) {
|
|
180
|
+
throw new Error(`Config validation failed: ${validation.errors.join(', ')}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 更新成功统计
|
|
184
|
+
this.stats.successCount++;
|
|
185
|
+
|
|
186
|
+
this.logger.info('配置重载成功');
|
|
187
|
+
|
|
188
|
+
// 通知重载成功
|
|
189
|
+
this.eventBus.emit('config:reload:success', {
|
|
190
|
+
configPath: this.configPath,
|
|
191
|
+
changes: validation.changes,
|
|
192
|
+
reloadCount: this.stats.reloadCount,
|
|
193
|
+
oldConfig,
|
|
194
|
+
newConfig,
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.stats.errorCount++;
|
|
198
|
+
this.stats.lastError = error.message;
|
|
199
|
+
|
|
200
|
+
this.logger.errorWithContext('重载配置失败', error);
|
|
201
|
+
|
|
202
|
+
// 通知重载失败
|
|
203
|
+
this.eventBus.emit('config:reload:error', {
|
|
204
|
+
configPath: this.configPath,
|
|
205
|
+
error: error.message,
|
|
206
|
+
reloadCount: this.stats.reloadCount,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 清除Node.js模块缓存
|
|
213
|
+
*/
|
|
214
|
+
clearModuleCache() {
|
|
215
|
+
try {
|
|
216
|
+
// 清除配置文件的缓存
|
|
217
|
+
if (this.configPath && require.cache[this.configPath]) {
|
|
218
|
+
delete require.cache[this.configPath];
|
|
219
|
+
this.logger.debug(`已清除模块缓存: ${this.configPath}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 清除相关模块的缓存(如果配置文件有依赖)
|
|
223
|
+
const configDir = path.dirname(this.configPath);
|
|
224
|
+
Object.keys(require.cache).forEach((modulePath) => {
|
|
225
|
+
if (modulePath.startsWith(configDir) && modulePath.endsWith('.js')) {
|
|
226
|
+
delete require.cache[modulePath];
|
|
227
|
+
this.logger.debug(`已清除相关模块缓存: ${modulePath}`);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.logger.warn(`清除模块缓存失败: ${error.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 验证配置变更
|
|
237
|
+
* @param {Object} oldConfig - 旧配置
|
|
238
|
+
* @param {Object} newConfig - 新配置
|
|
239
|
+
* @returns {Object} 验证结果
|
|
240
|
+
*/
|
|
241
|
+
validateConfigChange(oldConfig, newConfig) {
|
|
242
|
+
const validation = {
|
|
243
|
+
isValid: true,
|
|
244
|
+
errors: [],
|
|
245
|
+
warnings: [],
|
|
246
|
+
changes: [],
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
// 检查基本结构
|
|
251
|
+
if (!newConfig || typeof newConfig !== 'object') {
|
|
252
|
+
validation.isValid = false;
|
|
253
|
+
validation.errors.push('Config must be a valid object');
|
|
254
|
+
return validation;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 检查必需字段
|
|
258
|
+
const requiredFields = ['multiFile', 'baseClassName'];
|
|
259
|
+
for (const field of requiredFields) {
|
|
260
|
+
if (!newConfig[field]) {
|
|
261
|
+
validation.isValid = false;
|
|
262
|
+
validation.errors.push(`Missing required field: ${field}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 检查输出配置
|
|
267
|
+
if (newConfig.multiFile && newConfig.multiFile.output) {
|
|
268
|
+
const output = newConfig.multiFile.output;
|
|
269
|
+
if (!output.cssOutType || !['filePath', 'uniFile'].includes(output.cssOutType)) {
|
|
270
|
+
validation.errors.push('Invalid cssOutType, must be "filePath" or "uniFile"');
|
|
271
|
+
validation.isValid = false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 比较配置变化
|
|
276
|
+
this.detectConfigChanges(oldConfig, newConfig, validation);
|
|
277
|
+
|
|
278
|
+
if (validation.errors.length === 0) {
|
|
279
|
+
this.logger.info(`配置验证通过,检测到 ${validation.changes.length} 个变更`);
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
validation.isValid = false;
|
|
283
|
+
validation.errors.push(`Validation error: ${error.message}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return validation;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 检测配置变化
|
|
291
|
+
* @param {Object} oldConfig - 旧配置
|
|
292
|
+
* @param {Object} newConfig - 新配置
|
|
293
|
+
* @param {Object} validation - 验证对象
|
|
294
|
+
*/
|
|
295
|
+
detectConfigChanges(oldConfig, newConfig, validation) {
|
|
296
|
+
try {
|
|
297
|
+
// 检查输出类型变化
|
|
298
|
+
const oldOutputType = oldConfig?.multiFile?.output?.cssOutType;
|
|
299
|
+
const newOutputType = newConfig?.multiFile?.output?.cssOutType;
|
|
300
|
+
|
|
301
|
+
if (oldOutputType !== newOutputType) {
|
|
302
|
+
validation.changes.push({
|
|
303
|
+
type: 'outputType',
|
|
304
|
+
field: 'multiFile.output.cssOutType',
|
|
305
|
+
oldValue: oldOutputType,
|
|
306
|
+
newValue: newOutputType,
|
|
307
|
+
impact: 'high', // 需要重新扫描
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 检查输出路径变化
|
|
312
|
+
const oldOutputPath = oldConfig?.multiFile?.output?.path;
|
|
313
|
+
const newOutputPath = newConfig?.multiFile?.output?.path;
|
|
314
|
+
|
|
315
|
+
if (oldOutputPath !== newOutputPath) {
|
|
316
|
+
validation.changes.push({
|
|
317
|
+
type: 'outputPath',
|
|
318
|
+
field: 'multiFile.output.path',
|
|
319
|
+
oldValue: oldOutputPath,
|
|
320
|
+
newValue: newOutputPath,
|
|
321
|
+
impact: 'medium',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 检查监听路径变化
|
|
326
|
+
const oldWatchPath = oldConfig?.multiFile?.entry?.paths ?? oldConfig?.multiFile?.entry?.path;
|
|
327
|
+
const newWatchPath = newConfig?.multiFile?.entry?.paths ?? newConfig?.multiFile?.entry?.path;
|
|
328
|
+
|
|
329
|
+
if (JSON.stringify(oldWatchPath) !== JSON.stringify(newWatchPath)) {
|
|
330
|
+
validation.changes.push({
|
|
331
|
+
type: 'watchPath',
|
|
332
|
+
field: 'multiFile.entry.path(s)',
|
|
333
|
+
oldValue: oldWatchPath,
|
|
334
|
+
newValue: newWatchPath,
|
|
335
|
+
impact: 'high', // 需要重新设置文件监听
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// 检查基础类名配置变化
|
|
340
|
+
const oldBaseClassName = oldConfig?.baseClassName;
|
|
341
|
+
const newBaseClassName = newConfig?.baseClassName;
|
|
342
|
+
|
|
343
|
+
if (JSON.stringify(oldBaseClassName) !== JSON.stringify(newBaseClassName)) {
|
|
344
|
+
validation.changes.push({
|
|
345
|
+
type: 'baseClassName',
|
|
346
|
+
field: 'baseClassName',
|
|
347
|
+
oldValue: 'changed',
|
|
348
|
+
newValue: 'changed',
|
|
349
|
+
impact: 'medium', // 需要重新生成CSS
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this.logger.warn(`检测配置变更失败: ${error.message}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 设置防抖延迟时间
|
|
359
|
+
* @param {number} delay - 延迟时间(毫秒)
|
|
360
|
+
*/
|
|
361
|
+
setDebounceDelay(delay) {
|
|
362
|
+
if (typeof delay === 'number' && delay > 0) {
|
|
363
|
+
this.debounceDelay = delay;
|
|
364
|
+
this.logger.debug(`配置监听器防抖延迟设置为 ${delay}ms`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 获取监听器统计信息
|
|
370
|
+
* @returns {Object} 统计信息
|
|
371
|
+
*/
|
|
372
|
+
getWatcherStats() {
|
|
373
|
+
return {
|
|
374
|
+
isWatching: this.isWatching,
|
|
375
|
+
configPath: this.configPath,
|
|
376
|
+
watchStartTime: this.stats.watchStartTime,
|
|
377
|
+
reloadCount: this.stats.reloadCount,
|
|
378
|
+
lastReloadTime: this.stats.lastReloadTime,
|
|
379
|
+
successCount: this.stats.successCount,
|
|
380
|
+
errorCount: this.stats.errorCount,
|
|
381
|
+
lastError: this.stats.lastError,
|
|
382
|
+
debounceDelay: this.debounceDelay,
|
|
383
|
+
uptime: this.stats.watchStartTime ? Date.now() - this.stats.watchStartTime.getTime() : 0,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 获取当前监听状态
|
|
389
|
+
* @returns {boolean} 是否正在监听
|
|
390
|
+
*/
|
|
391
|
+
isActive() {
|
|
392
|
+
return this.isWatching;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* 重置统计信息
|
|
397
|
+
*/
|
|
398
|
+
resetStats() {
|
|
399
|
+
this.stats = {
|
|
400
|
+
watchStartTime: this.isWatching ? this.stats.watchStartTime : null,
|
|
401
|
+
reloadCount: 0,
|
|
402
|
+
lastReloadTime: null,
|
|
403
|
+
successCount: 0,
|
|
404
|
+
errorCount: 0,
|
|
405
|
+
lastError: null,
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
this.logger.info('Config watcher stats reset');
|
|
409
|
+
this.eventBus.emit('config:watcher:stats:reset');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = ConfigWatcher;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const chokidar = require('chokidar');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class FileWatcher {
|
|
5
|
+
constructor(eventBus, configManager) {
|
|
6
|
+
this.eventBus = eventBus;
|
|
7
|
+
this.configManager = configManager;
|
|
8
|
+
this.watcher = null;
|
|
9
|
+
this.isWatching = false;
|
|
10
|
+
this.watchedPaths = new Set();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 启动文件监听
|
|
14
|
+
async startWatching() {
|
|
15
|
+
if (this.isWatching) {
|
|
16
|
+
this.eventBus.emit('watcher:already-running');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const multiFile = this.configManager.getMultiFile();
|
|
22
|
+
const entryPaths = this.configManager.getMultiFileEntryPaths();
|
|
23
|
+
if (!multiFile || !multiFile.entry || entryPaths.length === 0) {
|
|
24
|
+
throw new Error('MultiFile configuration is required for file watching');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const fileTypes = multiFile.entry.fileType || ['html', 'wxml'];
|
|
28
|
+
|
|
29
|
+
// 构建监听目标(支持多目录 + 多文件)
|
|
30
|
+
const targets = [];
|
|
31
|
+
for (const p of entryPaths) {
|
|
32
|
+
const normalized = p.replace(/\\/g, '/');
|
|
33
|
+
const ext = path.extname(normalized).slice(1).toLowerCase();
|
|
34
|
+
// 如果看起来是具体文件且扩展名在监听范围内,则直接监听该文件
|
|
35
|
+
if (ext && fileTypes.includes(ext)) {
|
|
36
|
+
targets.push(normalized);
|
|
37
|
+
} else {
|
|
38
|
+
// 当作目录(或未知),监听其下指定类型文件
|
|
39
|
+
for (const type of fileTypes) {
|
|
40
|
+
targets.push(`${normalized}/**/*.${type}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// console.log('[FileWatcher] 监听目标:', targets);
|
|
46
|
+
|
|
47
|
+
this.eventBus.emit('watcher:starting', { paths: entryPaths, targets });
|
|
48
|
+
|
|
49
|
+
// 创建文件监听器
|
|
50
|
+
this.watcher = chokidar.watch(targets, {
|
|
51
|
+
ignoreInitial: false,
|
|
52
|
+
persistent: true,
|
|
53
|
+
usePolling: true,
|
|
54
|
+
interval: 150,
|
|
55
|
+
binaryInterval: 300,
|
|
56
|
+
awaitWriteFinish: {
|
|
57
|
+
// Windows/编辑器保存时可能经历“清空/重写/替换”的瞬时状态,适当加大稳定窗口降低误触发
|
|
58
|
+
stabilityThreshold: 400,
|
|
59
|
+
pollInterval: 100,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 监听文件变化事件
|
|
64
|
+
this.watcher
|
|
65
|
+
.on('all', (event, filePath) => {
|
|
66
|
+
// console.log(`[FileWatcher] 文件事件: ${event} -> ${filePath}`);
|
|
67
|
+
if (event === 'add') {
|
|
68
|
+
this.eventBus.emit('file:added', filePath);
|
|
69
|
+
this.eventBus.emit('file:changed', filePath);
|
|
70
|
+
} else if (event === 'change') {
|
|
71
|
+
this.eventBus.emit('file:changed', filePath);
|
|
72
|
+
} else if (event === 'unlink') {
|
|
73
|
+
this.eventBus.emit('file:removed', filePath);
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
.on('error', (error) => {
|
|
77
|
+
this.eventBus.emit('watcher:error', error);
|
|
78
|
+
})
|
|
79
|
+
.on('ready', () => {
|
|
80
|
+
this.isWatching = true;
|
|
81
|
+
this.eventBus.emit('watcher:ready', {
|
|
82
|
+
paths: entryPaths,
|
|
83
|
+
targets,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 记录监听的路径
|
|
88
|
+
for (const p of entryPaths) this.watchedPaths.add(p);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.eventBus.emit('watcher:error', error);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 停止文件监听
|
|
96
|
+
stopWatching() {
|
|
97
|
+
if (!this.isWatching || !this.watcher) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
this.watcher.close();
|
|
103
|
+
this.watcher = null;
|
|
104
|
+
this.isWatching = false;
|
|
105
|
+
this.watchedPaths.clear();
|
|
106
|
+
|
|
107
|
+
this.eventBus.emit('watcher:stopped');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.eventBus.emit('watcher:error', error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 获取监听状态
|
|
114
|
+
getWatchStats() {
|
|
115
|
+
return {
|
|
116
|
+
isWatching: this.isWatching,
|
|
117
|
+
watchedPaths: Array.from(this.watchedPaths),
|
|
118
|
+
watcherActive: !!this.watcher,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 检查是否正在监听
|
|
123
|
+
isCurrentlyWatching() {
|
|
124
|
+
return this.isWatching && !!this.watcher;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 获取监听的路径列表
|
|
128
|
+
getWatchedPaths() {
|
|
129
|
+
return Array.from(this.watchedPaths);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = FileWatcher;
|