@kanso-labs/unplugin-style-dictionary 0.10.5 → 0.10.7

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/dist/index.js CHANGED
@@ -1,50 +1,22 @@
1
- import { matchesWatchedFile } from "./watch-filter.js";
2
- import JSON5 from "json5";
1
+ import { colourAllowed, paint } from "./colour.js";
2
+ import { asError, errorMessage } from "./errors.js";
3
+ import { resolveConfigOption } from "./config.js";
4
+ import { expandPatterns, watchPatternsOf } from "./patterns.js";
5
+ import { runBuilds } from "./compile.js";
6
+ import { createScheduler } from "./scheduler.js";
7
+ import { isWatchedSource } from "./watch-filter.js";
3
8
  import fs from "node:fs";
4
9
  import path from "node:path";
5
- import { pathToFileURL } from "node:url";
6
- import zlib from "node:zlib";
7
- import StyleDictionary from "style-dictionary";
8
- import { glob } from "tinyglobby";
9
10
  import { createUnplugin } from "unplugin";
10
11
  //#region src/index.ts
11
- function asError(error) {
12
- return error instanceof Error ? error : new Error(errorMessage(error));
13
- }
14
- function colourAllowed(stream) {
15
- if (process.env.NO_COLOR) return false;
16
- const forced = process.env.FORCE_COLOR;
17
- if (forced === "0") return false;
18
- if (forced !== void 0 && forced !== "") return true;
19
- if (process.env.TERM === "dumb") return false;
20
- return stream.isTTY === true;
21
- }
22
12
  function discardTemporaryFile(temporary) {
23
13
  try {
24
14
  fs.rmSync(temporary, { force: true });
25
15
  } catch {}
26
16
  }
27
- function errorMessage(error) {
28
- return error instanceof Error ? error.message : String(error);
29
- }
30
- function isConfig(value) {
31
- return typeof value === "object" && value !== null;
32
- }
33
17
  function isMessageChannel(value) {
34
18
  return typeof value === "function";
35
19
  }
36
- function isThenable(value) {
37
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
38
- }
39
- function looksLikeConfig(value) {
40
- if (typeof value !== "object" || value === null) return false;
41
- return [
42
- "include",
43
- "platforms",
44
- "source",
45
- "tokens"
46
- ].some((key) => key in value);
47
- }
48
20
  function nodeModulesNegations(paths) {
49
21
  const negations = /* @__PURE__ */ new Set();
50
22
  for (const file of paths) {
@@ -62,25 +34,6 @@ function nodeModulesWatchDirectories(paths) {
62
34
  }
63
35
  return Array.from(directories);
64
36
  }
65
- function paint(code, value, allowed) {
66
- return allowed ? `\u001B[${code}m${value}\u001B[0m` : value;
67
- }
68
- async function patternsMatchingNothing(patterns) {
69
- const barren = [];
70
- for (const pattern of patterns) {
71
- if (!GLOB_CHARACTERS.test(pattern)) {
72
- if (!fs.existsSync(pattern)) barren.push(pattern);
73
- continue;
74
- }
75
- try {
76
- if ((await glob([pattern], { absolute: true })).length === 0) barren.push(pattern);
77
- } catch {}
78
- }
79
- return barren;
80
- }
81
- function unwrapDefault(value) {
82
- return typeof value === "object" && value !== null && "default" in value ? value.default ?? value : value;
83
- }
84
37
  let temporaryFileCounter = 0;
85
38
  async function rendersWhatIsAlreadyThere(temporary, destination) {
86
39
  try {
@@ -188,23 +141,6 @@ const atomicVolume = Object.create(fs, {
188
141
  promises: { value: Object.create(fs.promises, { writeFile: { value: writeFileAtomic } }) },
189
142
  writeFileSync: { value: writeFileSyncAtomic }
190
143
  });
191
- const GLOB_CHARACTERS = /[!*?[\]{}]/;
192
- const IMPORTED_CONFIG_EXTENSIONS = [
193
- ".js",
194
- ".mjs",
195
- ".ts"
196
- ];
197
- function describeConfig(item, index) {
198
- return item.file ? `The configuration ${item.file}` : `The configuration at position ${index + 1}`;
199
- }
200
- function isImportedConfig(file) {
201
- return IMPORTED_CONFIG_EXTENSIONS.some((extension) => file.endsWith(extension));
202
- }
203
- function staticParentOf(pattern) {
204
- const segments = pattern.split("/");
205
- const firstGlob = segments.findIndex((segment) => GLOB_CHARACTERS.test(segment));
206
- return firstGlob === -1 ? path.posix.dirname(pattern) : segments.slice(0, firstGlob).join("/");
207
- }
208
144
  const compiledFingerprints = /* @__PURE__ */ new Set();
209
145
  const compilesInFlight = /* @__PURE__ */ new Map();
210
146
  function buildKey(root, resolved) {
@@ -214,34 +150,12 @@ function buildKey(root, resolved) {
214
150
  return null;
215
151
  }
216
152
  }
217
- function sourcePatternsOf(configObj) {
218
- const patterns = [];
219
- const add = (pattern) => {
220
- if (typeof pattern === "string") patterns.push((path.isAbsolute(pattern) ? pattern : path.resolve(process.cwd(), pattern)).replace(/\\/g, "/"));
221
- };
222
- for (const value of [configObj.source, configObj.include]) if (Array.isArray(value)) value.forEach(add);
223
- else add(value);
224
- return patterns;
225
- }
226
- function statOrNull(file) {
227
- try {
228
- return fs.statSync(file);
229
- } catch {
230
- return null;
231
- }
232
- }
233
153
  const unpluginFactory = (options = {}, meta) => {
234
154
  const isWebpack = meta.framework === "webpack" || meta.framework === "rspack";
235
155
  const { cache = true, errorOverlay = true, failOnError = "build", logLevel, onBuildEnd, onBuildError, onBuildStart, platforms: platformsOption, report = true, root: rootOption, silent = false } = options;
236
156
  const level = logLevel ?? (silent ? "silent" : void 0);
237
157
  const quiet = level === "silent" || level === "warn";
238
158
  const verbosity = level === void 0 ? void 0 : level === "verbose" ? "verbose" : level === "silent" ? "silent" : "default";
239
- const platformsFor = (context) => {
240
- if (platformsOption === void 0) return void 0;
241
- if (Array.isArray(platformsOption)) return platformsOption;
242
- return context === void 0 ? platformsOption.build : platformsOption.watch;
243
- };
244
- const failsTheBuild = (context) => failOnError === true || (context === void 0 ? failOnError === "build" : failOnError === "serve");
245
159
  let hostCommand = "build";
246
160
  let hostMode;
247
161
  let isWatching = false;
@@ -262,22 +176,6 @@ const unpluginFactory = (options = {}, meta) => {
262
176
  let watchRebuild = false;
263
177
  let hasCompiled = false;
264
178
  let hostClosed = false;
265
- const expandPatterns = async (patterns) => {
266
- const paths = /* @__PURE__ */ new Set();
267
- const globs = [];
268
- for (const pattern of patterns) if (GLOB_CHARACTERS.test(pattern)) {
269
- globs.push(pattern);
270
- const parent = staticParentOf(pattern);
271
- if (parent && fs.existsSync(parent)) paths.add(parent);
272
- } else paths.add(pattern);
273
- if (globs.length > 0) try {
274
- for (const match of await glob(globs, { absolute: true })) paths.add(match.replace(/\\/g, "/"));
275
- } catch (err) {
276
- log(`Failed to expand watch patterns: ${errorMessage(err)}`, "error");
277
- }
278
- return Array.from(paths);
279
- };
280
- const isWatchedSource = (file, patterns) => !generatedDestinations.has(file.replace(/\\/g, "/")) && matchesWatchedFile(file, patterns);
281
179
  const stdoutColour = colourAllowed(process.stdout);
282
180
  const stderrColour = colourAllowed(process.stderr);
283
181
  let host;
@@ -312,292 +210,50 @@ const unpluginFactory = (options = {}, meta) => {
312
210
  }
313
211
  console.log(paint(type === "success" ? "32" : "36", `${prefix} ${message}`, stdoutColour));
314
212
  };
315
- const callHook = (name, hook, ...args) => {
316
- let result;
317
- try {
318
- result = hook(...args);
319
- } catch (err) {
320
- log(`The ${name} hook threw: ${errorMessage(err)}`, "error");
321
- return;
322
- }
323
- if (!isThenable(result)) return;
324
- Promise.resolve(result).catch((err) => {
325
- log(`The ${name} hook rejected: ${errorMessage(err)}`, "error");
326
- });
327
- };
328
- const resolveConfigs = async () => {
329
- let rawConfig = options.config;
330
- if (rawConfig === false) return [];
331
- if (!rawConfig) {
332
- const defaults = [
333
- "sd.config.json",
334
- "config.json",
335
- "sd.config.js",
336
- "sd.config.mjs"
337
- ];
338
- const rejected = [];
339
- for (const file of defaults) {
340
- const fullPath = path.resolve(root, file);
341
- if (!fs.existsSync(fullPath)) continue;
342
- if (!looksLikeConfig(await readConfigObject({
343
- config: fullPath,
344
- file: fullPath
345
- }, false))) {
346
- rejected.push(file);
347
- continue;
348
- }
349
- if (!announcedDiscovery) {
350
- announcedDiscovery = true;
351
- log(`Using the configuration it found at ${fullPath}`, "info");
352
- }
353
- rawConfig = file;
354
- break;
355
- }
356
- if (rejected.length > 0) log(`Ignored ${rejected.join(", ")} in ${root}: nothing there declares platforms, source, include or tokens, so it does not look like a Style Dictionary configuration. Name it with the config option if it is one, or set config to false to stop looking.`, "error");
357
- }
358
- if (!rawConfig) {
359
- log("No configuration specified and no default config file found. Style Dictionary will not compile.", "error");
360
- return [];
361
- }
362
- if (typeof rawConfig === "function") rawConfig = await rawConfig(configContext());
363
- return (Array.isArray(rawConfig) ? rawConfig : [rawConfig]).map((conf) => {
364
- if (typeof conf === "string") {
365
- const fullPath = path.resolve(root, conf);
366
- return {
367
- config: fullPath,
368
- file: fullPath
369
- };
370
- } else return { config: conf };
371
- });
372
- };
373
- const importConfigModule = async (file) => {
374
- let version;
375
- try {
376
- version = fs.statSync(file).mtimeMs;
377
- } catch {
378
- version = Date.now();
379
- }
380
- const key = String(version).replace(".", "_");
381
- return unwrapDefault(await import(`${pathToFileURL(file).href}?t=${key}`));
382
- };
383
- const readConfigObject = async (item, reportErrors) => {
384
- if (typeof item.config !== "string") return item.config;
385
- try {
386
- const loaded = isImportedConfig(item.config) ? await importConfigModule(item.config) : JSON5.parse(fs.readFileSync(item.config, "utf-8"));
387
- if (isConfig(loaded)) return loaded;
388
- if (reportErrors) log(`Config file did not resolve to a configuration object: ${item.config}`, "error");
389
- } catch (err) {
390
- if (reportErrors) log(`Failed to parse config file: ${item.config}. Error: ${errorMessage(err)}`, "error");
391
- }
392
- return null;
393
- };
213
+ const resolveConfigs = async () => resolveConfigOption({
214
+ config: options.config,
215
+ configContext,
216
+ discovery,
217
+ log,
218
+ root
219
+ });
394
220
  const getWatchTargets = async (resolvedConfigs) => {
395
- const filesToWatch = /* @__PURE__ */ new Set();
396
- for (const item of resolvedConfigs) {
397
- if (item.file) filesToWatch.add(item.file.replace(/\\/g, "/"));
398
- const configObj = await readConfigObject(item, true);
399
- if (configObj) {
400
- const addPattern = (pattern) => {
401
- if (typeof pattern === "string") {
402
- const normalized = (path.isAbsolute(pattern) ? pattern : path.resolve(process.cwd(), pattern)).replace(/\\/g, "/");
403
- filesToWatch.add(normalized);
404
- }
405
- };
406
- if (configObj.source) {
407
- if (Array.isArray(configObj.source)) configObj.source.forEach(addPattern);
408
- else addPattern(configObj.source);
409
- }
410
- if (configObj.include) {
411
- if (Array.isArray(configObj.include)) configObj.include.forEach(addPattern);
412
- else addPattern(configObj.include);
413
- }
414
- }
415
- }
416
- if (options.watch) {
417
- const extraWatches = Array.isArray(options.watch) ? options.watch : [options.watch];
418
- for (const pattern of extraWatches) {
419
- const absolutePattern = path.isAbsolute(pattern) ? pattern : path.resolve(root, pattern);
420
- filesToWatch.add(absolutePattern.replace(/\\/g, "/"));
421
- }
422
- }
423
- const patterns = Array.from(filesToWatch);
221
+ const patterns = await watchPatternsOf({
222
+ log,
223
+ root,
224
+ watch: options.watch
225
+ }, resolvedConfigs);
424
226
  cachedPatterns = patterns;
425
227
  return {
426
- paths: await expandPatterns(patterns),
228
+ paths: await expandPatterns(patterns, log),
427
229
  patterns
428
230
  };
429
231
  };
430
- const configForBuild = async (item) => {
431
- const { config } = item;
432
- if (typeof config !== "string" || !isImportedConfig(config)) return config;
433
- const loaded = await readConfigObject(item, false);
434
- if (!loaded) return item.config;
435
- try {
436
- return structuredClone(loaded);
437
- } catch {
438
- return loaded;
439
- }
440
- };
441
- const declaredDestinations = (configObj, only) => {
442
- const destinations = [];
443
- const entries = Object.entries(configObj.platforms ?? {});
444
- const selected = only ? entries.filter(([name]) => only.includes(name)) : entries;
445
- for (const [, platform] of selected) {
446
- const buildPath = platform.buildPath ?? "";
447
- const absoluteBuildPath = path.isAbsolute(buildPath) ? buildPath : path.resolve(root, buildPath);
448
- for (const file of platform.files ?? []) if (file.destination) destinations.push(path.isAbsolute(file.destination) ? file.destination : path.resolve(absoluteBuildPath, file.destination));
449
- }
450
- return destinations;
451
- };
452
- const configFingerprint = (item) => {
453
- try {
454
- return JSON.stringify([root, item.file ?? item.config], (_key, value) => typeof value === "function" ? `[fn]${String(value)}` : value);
455
- } catch {
456
- return null;
457
- }
458
- };
459
- const isUpToDate = async (item, configObj, only) => {
460
- if (Object.values(configObj.platforms ?? {}).some((platform) => (platform.actions?.length ?? 0) > 0)) return false;
461
- const destinations = declaredDestinations(configObj, only);
462
- if (destinations.length === 0) return false;
463
- const extraWatches = options.watch ? Array.isArray(options.watch) ? options.watch : [options.watch] : [];
464
- const sources = await expandPatterns([...sourcePatternsOf(configObj), ...extraWatches.map((pattern) => (path.isAbsolute(pattern) ? pattern : path.resolve(root, pattern)).replace(/\\/g, "/"))]);
465
- if (item.file) sources.push(item.file.replace(/\\/g, "/"));
466
- if (sources.length === 0) return false;
467
- let newestSource = -Infinity;
468
- let sawFile = false;
469
- for (const source of sources) {
470
- const stats = statOrNull(source);
471
- if (!stats) return false;
472
- if (stats.isDirectory()) continue;
473
- sawFile = true;
474
- newestSource = Math.max(newestSource, stats.mtimeMs);
475
- }
476
- if (!sawFile) return false;
477
- let oldestDestination = Infinity;
478
- for (const destination of destinations) {
479
- const stats = statOrNull(destination);
480
- if (!stats) return false;
481
- oldestDestination = Math.min(oldestDestination, stats.mtimeMs);
482
- }
483
- if (oldestDestination <= newestSource) return false;
484
- if (item.file) return true;
485
- const fingerprint = configFingerprint(item);
486
- return fingerprint !== null && compiledFingerprints.has(fingerprint);
487
- };
488
- const reportSizes = (generatedFiles) => {
489
- const fileInfos = [];
490
- for (const filePath of generatedFiles) if (fs.existsSync(filePath)) {
491
- const displayPath = path.relative(root, filePath).replace(/\\/g, "/");
492
- const dir = path.dirname(displayPath);
493
- const base = path.basename(displayPath);
494
- const coloredPath = dir === "." ? paint("32", base, stdoutColour) : paint("90", `${dir}/`, stdoutColour) + paint("32", base, stdoutColour);
495
- try {
496
- const sizeStr = `${(fs.statSync(filePath).size / 1024).toFixed(2)} kB`;
497
- const content = fs.readFileSync(filePath);
498
- const gzipSizeStr = `${(zlib.gzipSync(content).length / 1024).toFixed(2)} kB`;
499
- fileInfos.push({
500
- coloredPath,
501
- gzipSizeStr,
502
- relativeDisplayPath: displayPath,
503
- sizeStr
504
- });
505
- } catch {}
506
- }
507
- if (fileInfos.length > 0) {
508
- const longestPathLength = Math.max(...fileInfos.map((f) => f.relativeDisplayPath.length), 0);
509
- const longestSizeLength = Math.max(...fileInfos.map((f) => f.sizeStr.length), 0);
510
- for (const info of fileInfos) {
511
- const pathPadding = " ".repeat(Math.max(2, longestPathLength - info.relativeDisplayPath.length + 2));
512
- const sizePadded = info.sizeStr.padStart(longestSizeLength);
513
- console.log(info.coloredPath + pathPadding + paint("90", `${sizePadded} │ gzip: ${info.gzipSizeStr}`, stdoutColour));
514
- }
515
- }
516
- };
517
- const runBuilds = async (resolvedConfigs, context) => {
518
- const startTime = Date.now();
519
- const generatedFiles = /* @__PURE__ */ new Set();
520
- let skipped = 0;
521
- try {
522
- if (!context) log("Compiling design tokens...", "info");
523
- if (onBuildStart) callHook("onBuildStart", onBuildStart);
524
- for (const [index, item] of resolvedConfigs.entries()) {
525
- const declared = cache ? await readConfigObject(item, false) : null;
526
- const selectedPlatforms = platformsFor(context);
527
- if (declared && await isUpToDate(item, declared, selectedPlatforms)) {
528
- for (const destination of declaredDestinations(declared)) generatedFiles.add(destination);
529
- skipped++;
530
- continue;
531
- }
532
- const sd = new StyleDictionary(await configForBuild(item), { init: false });
533
- await sd.extend(void 0, {
534
- mutateOriginal: true,
535
- verbosity
536
- });
537
- if (sd.allTokens.length === 0) {
538
- const asObject = await readConfigObject(item, false);
539
- const barren = asObject ? await patternsMatchingNothing(sourcePatternsOf(asObject)) : [];
540
- throw new Error([
541
- `${describeConfig(item, index)} resolved no tokens, so its output would be emptied.`,
542
- barren.length > 0 ? `These patterns matched no files: ${barren.join(", ")}` : `It declares no source or include patterns that matched anything.`,
543
- `Nothing was written. Set failOnError to false to build anyway.`
544
- ].join(" "));
545
- }
546
- sd.volume = atomicVolume;
547
- if (selectedPlatforms === void 0) await sd.buildAllPlatforms();
548
- else {
549
- const defined = Object.keys(sd.platforms);
550
- const unknown = selectedPlatforms.filter((name) => !defined.includes(name));
551
- if (unknown.length > 0) throw new Error(`${describeConfig(item, index)} does not define the platform(s) ${unknown.join(", ")}. It defines ${defined.join(", ")}.`);
552
- for (const name of selectedPlatforms) await sd.buildPlatform(name);
553
- }
554
- for (const platform of Object.values(sd.platforms)) {
555
- const buildPath = platform.buildPath ?? "";
556
- for (const file of platform.files ?? []) if (file.destination) {
557
- const absoluteBuildPath = path.isAbsolute(buildPath) ? buildPath : path.resolve(root, buildPath);
558
- const absoluteDestination = path.isAbsolute(file.destination) ? file.destination : path.resolve(absoluteBuildPath, file.destination);
559
- generatedFiles.add(absoluteDestination);
560
- }
561
- }
562
- const fingerprint = configFingerprint(item);
563
- if (fingerprint !== null) compiledFingerprints.add(fingerprint);
564
- }
565
- generatedDestinations.clear();
566
- for (const destination of generatedFiles) generatedDestinations.add(destination.replace(/\\/g, "/"));
567
- } catch (err) {
568
- const duration = Date.now() - startTime;
569
- log(`Compilation failed after ${duration}ms: ${errorMessage(err)}`, "error");
570
- notifyBuildOutcome?.(asError(err));
571
- if (onBuildError) callHook("onBuildError", onBuildError, err);
572
- if (failsTheBuild(context)) throw err;
573
- return;
574
- }
575
- notifyBuildOutcome?.(null);
576
- const duration = Date.now() - startTime;
577
- if (onBuildEnd) {
578
- const files = Array.from(generatedFiles).sort((left, right) => left.localeCompare(right));
579
- callHook("onBuildEnd", onBuildEnd, files, duration);
580
- }
581
- const everythingSkipped = skipped === resolvedConfigs.length;
582
- if (context) {
583
- log(everythingSkipped ? `Design tokens already up to date after change in ${context} (${duration}ms)` : `Rebuilt design tokens due to change in ${context} (${duration}ms)`, "success");
584
- return;
585
- }
586
- if (report && !quiet && !everythingSkipped && generatedFiles.size > 0) try {
587
- reportSizes(generatedFiles);
588
- } catch (err) {
589
- log(`Failed to report generated file sizes: ${errorMessage(err)}`, "error");
590
- }
591
- if (everythingSkipped) {
592
- log(`Design tokens are already up to date (${duration}ms)`, "success");
593
- return;
594
- }
595
- log(skipped > 0 ? `Compiled successfully! (${duration}ms, ${skipped} already up to date)` : `Compiled successfully! (${duration}ms)`, "success");
232
+ const instance = {
233
+ cache,
234
+ compiledFingerprints,
235
+ failOnError,
236
+ generatedDestinations,
237
+ log,
238
+ notifyBuildOutcome: (error) => {
239
+ notifyBuildOutcome?.(error);
240
+ },
241
+ onBuildEnd,
242
+ onBuildError,
243
+ onBuildStart,
244
+ platformsOption,
245
+ quiet,
246
+ report,
247
+ root: () => root,
248
+ stdoutColour,
249
+ verbosity,
250
+ volume: atomicVolume,
251
+ watch: options.watch
596
252
  };
597
253
  const compileOnceAcrossInstances = async (resolvedConfigs) => {
598
254
  const key = buildKey(root, resolvedConfigs);
599
255
  if (key === null) {
600
- await runBuilds(resolvedConfigs);
256
+ await runBuilds(instance, resolvedConfigs);
601
257
  return;
602
258
  }
603
259
  const running = compilesInFlight.get(key);
@@ -605,7 +261,7 @@ const unpluginFactory = (options = {}, meta) => {
605
261
  await running;
606
262
  return;
607
263
  }
608
- const compile = runBuilds(resolvedConfigs);
264
+ const compile = runBuilds(instance, resolvedConfigs);
609
265
  compilesInFlight.set(key, compile);
610
266
  try {
611
267
  await compile;
@@ -613,59 +269,35 @@ const unpluginFactory = (options = {}, meta) => {
613
269
  compilesInFlight.delete(key);
614
270
  }
615
271
  };
616
- const REBUILD_DEBOUNCE_MS = 50;
617
- let debounceTimer;
618
- let pendingReason;
619
- let inFlight;
620
- let waiting = [];
621
272
  let refreshServerWatchList;
622
- let announcedDiscovery = false;
273
+ let closeOwnWatchers;
274
+ const discovery = { announced: false };
623
275
  let startupResolved;
624
276
  let notifyBuildOutcome;
625
- const drain = async () => {
626
- while (pendingReason !== void 0) {
627
- const reason = pendingReason;
628
- pendingReason = void 0;
629
- const resolvers = waiting;
630
- waiting = [];
631
- let failure;
632
- let compiling = false;
633
- try {
634
- const resolved = await resolveConfigs();
635
- if (resolved.length > 0) {
636
- compiling = true;
637
- await runBuilds(resolved, reason);
638
- compiling = false;
639
- hasCompiled = true;
640
- await refreshServerWatchList?.(resolved);
641
- }
642
- } catch (err) {
643
- failure = { error: err };
644
- if (!compiling) {
645
- log(`Rebuild failed: ${errorMessage(err)}`, "error");
646
- notifyBuildOutcome?.(asError(err));
647
- }
277
+ const rebuild = async (reason) => {
278
+ let compiling = false;
279
+ try {
280
+ const resolved = await resolveConfigs();
281
+ if (resolved.length > 0) {
282
+ compiling = true;
283
+ await runBuilds(instance, resolved, reason);
284
+ compiling = false;
285
+ hasCompiled = true;
286
+ await refreshServerWatchList?.(resolved);
287
+ }
288
+ } catch (err) {
289
+ if (!compiling) {
290
+ log(`Rebuild failed: ${errorMessage(err)}`, "error");
291
+ notifyBuildOutcome?.(asError(err));
648
292
  }
649
- for (const settle of resolvers) settle(failure);
293
+ throw err;
650
294
  }
651
295
  };
652
- const schedule = async (reason) => {
653
- if (hostClosed) return;
654
- pendingReason = reason;
655
- const covered = new Promise((resolve, reject) => {
656
- waiting.push((failure) => {
657
- if (failure) reject(asError(failure.error));
658
- else resolve();
659
- });
660
- });
661
- if (debounceTimer) clearTimeout(debounceTimer);
662
- debounceTimer = setTimeout(() => {
663
- debounceTimer = void 0;
664
- inFlight = (inFlight ?? Promise.resolve()).then(drain);
665
- }, REBUILD_DEBOUNCE_MS);
666
- debounceTimer.unref();
667
- return covered;
668
- };
296
+ const schedule = createScheduler({
297
+ debounceMs: 50,
298
+ isClosed: () => hostClosed,
299
+ run: rebuild
300
+ });
669
301
  const closeWatcher = () => {
670
302
  hostClosed = true;
671
303
  };
@@ -719,6 +351,9 @@ const unpluginFactory = (options = {}, meta) => {
719
351
  rollup: { closeWatcher },
720
352
  rspack: adoptCompiler,
721
353
  vite: {
354
+ buildEnd() {
355
+ closeOwnWatchers?.();
356
+ },
722
357
  closeWatcher,
723
358
  async configResolved(config) {
724
359
  if (rootOption === void 0) root = config.root || process.cwd();
@@ -771,7 +406,7 @@ const unpluginFactory = (options = {}, meta) => {
771
406
  const watcher = fs.watch(directory, (_event, filename) => {
772
407
  if (filename === null) return;
773
408
  const changed = path.posix.join(directory, filename);
774
- if (!isWatchedSource(changed, targets.patterns)) return;
409
+ if (!isWatchedSource(changed, targets.patterns, generatedDestinations)) return;
775
410
  schedule(path.basename(changed)).catch(() => {});
776
411
  });
777
412
  watcher.unref();
@@ -780,10 +415,10 @@ const unpluginFactory = (options = {}, meta) => {
780
415
  }
781
416
  };
782
417
  watchNodeModules(targets.paths);
783
- server.httpServer?.once("close", () => {
418
+ closeOwnWatchers = () => {
784
419
  for (const watcher of ownWatchers.values()) watcher.close();
785
420
  ownWatchers.clear();
786
- });
421
+ };
787
422
  refreshServerWatchList = async (rebuilt) => {
788
423
  targets = await getWatchTargets(rebuilt);
789
424
  server.watcher.add(targets.paths);
@@ -813,7 +448,7 @@ const unpluginFactory = (options = {}, meta) => {
813
448
  };
814
449
  }
815
450
  server.watcher.on("all", (_event, file) => {
816
- if (!isWatchedSource(file, targets.patterns)) return;
451
+ if (!isWatchedSource(file, targets.patterns, generatedDestinations)) return;
817
452
  schedule(path.basename(file)).catch(() => {});
818
453
  });
819
454
  }
@@ -823,13 +458,13 @@ const unpluginFactory = (options = {}, meta) => {
823
458
  adoptWatchMode(this);
824
459
  if (hostClosed) return;
825
460
  watchRebuild = true;
826
- if (cachedPatterns && !isWatchedSource(id, cachedPatterns)) return;
461
+ if (cachedPatterns && !isWatchedSource(id, cachedPatterns, generatedDestinations)) return;
827
462
  const resolved = await resolveConfigs();
828
463
  if (resolved.length === 0) return;
829
464
  const { patterns } = await getWatchTargets(resolved);
830
- if (!isWatchedSource(id, patterns)) return;
465
+ if (!isWatchedSource(id, patterns, generatedDestinations)) return;
831
466
  if (!isWebpack) await schedule(path.basename(id));
832
- for (const file of await expandPatterns(patterns)) this.addWatchFile(file);
467
+ for (const file of await expandPatterns(patterns, log)) this.addWatchFile(file);
833
468
  },
834
469
  webpack: adoptCompiler
835
470
  };