@weapp-tailwindcss/engine 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,173 @@
1
- import { createRequire } from "node:module";
2
- import { promises, realpathSync } from "node:fs";
1
+ import { createRequire, findPackageJSON } from "node:module";
2
+ import { promises, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
- import { pathToFileURL } from "node:url";
6
- import micromatch from "micromatch";
7
- import { stat } from "node:fs/promises";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { AsyncLocalStorage } from "node:async_hooks";
7
+ import { createHash } from "node:crypto";
8
+ import { clearRequireCache } from "@tailwindcss/node/require-cache";
9
+ import { createSourceScanPlan, expandSourceEntries as expandTailwindV4SourceEntries, expandSourceEntryBraces, expandSourceEntryBraces as expandTailwindV4SourceEntryBraces, groupSourceEntriesByBase as groupTailwindV4SourceEntriesByBase, isFileExcludedByTailwindSourceEntries, isFileExcludedByTailwindSourceEntries as isFileExcludedByTailwindV4SourceEntries, isFileMatchedByTailwindSourceEntries, mergeSourceEntries as mergeTailwindV4SourceEntries, normalizeGlobPattern, resolveSourceScanPath, resolveSourceScanPath as resolveSourceScanPath$1, resolveTailwindSourceEntry, toPosixPath } from "@weapp-tailwindcss/source-scan";
8
10
  import postcss from "postcss";
11
+ import { readFile } from "node:fs/promises";
12
+ //#region src/v4/module-cache.ts
13
+ const context = new AsyncLocalStorage();
14
+ const caches = /* @__PURE__ */ new WeakMap();
15
+ let revision = 0;
16
+ const hooks = globalThis;
17
+ let installedHook;
18
+ let activeContexts = 0;
19
+ const MODULE_CACHE_LIMIT = 128;
20
+ const require = createRequire(import.meta.url);
21
+ function canUseCommonJsGraph(file) {
22
+ const extension = path.extname(file);
23
+ if (extension === ".cjs" || extension === ".cts") return true;
24
+ if (extension !== ".js") return false;
25
+ const manifest = findPackageJSON(pathToFileURL(file).href);
26
+ return !manifest || path.basename(manifest) !== "package.json" || JSON.parse(readFileSync(manifest, "utf8")).type !== "module";
27
+ }
28
+ function collectCommonJsDependencies(file, dependencies) {
29
+ const module = require.cache[file];
30
+ if (!module?.loaded) return false;
31
+ const visited = /* @__PURE__ */ new Set();
32
+ const packageRoot = path.dirname(findPackageJSON(pathToFileURL(file).href) ?? file);
33
+ function visit(current) {
34
+ if (visited.has(current) || !current.filename) return;
35
+ visited.add(current);
36
+ let directory = path.dirname(current.filename);
37
+ const relative = path.relative(packageRoot, current.filename);
38
+ while (path.dirname(directory) !== directory) {
39
+ if (path.basename(directory) === "node_modules" && (relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))) return;
40
+ directory = path.dirname(directory);
41
+ }
42
+ dependencies.add(current.filename);
43
+ for (const child of current.children) visit(child);
44
+ }
45
+ visit(module);
46
+ return true;
47
+ }
48
+ function fingerprint(files) {
49
+ const hash = createHash("sha256");
50
+ for (const file of files) {
51
+ hash.update(file);
52
+ hash.update(readFileSync(file));
53
+ }
54
+ return hash.digest("hex");
55
+ }
56
+ async function loadCachedModule(loader, file) {
57
+ let state = caches.get(loader);
58
+ if (!state || state.revision !== revision) {
59
+ state = {
60
+ revision,
61
+ entries: /* @__PURE__ */ new Map()
62
+ };
63
+ caches.set(loader, state);
64
+ }
65
+ const cache = state.entries;
66
+ const previous = cache.get(file);
67
+ if (previous) {
68
+ const entry = await previous;
69
+ try {
70
+ if (fingerprint(entry.dependencies) === entry.fingerprint) return entry;
71
+ } catch {}
72
+ if (cache.get(file) !== previous) return loadCachedModule(loader, file);
73
+ clearRequireCache(entry.dependencies);
74
+ }
75
+ const pending = context.run(void 0, async () => {
76
+ const dependencies = /* @__PURE__ */ new Set([file]);
77
+ const base = path.dirname(file);
78
+ const onDependency = (dependency) => {
79
+ dependencies.add(dependency);
80
+ };
81
+ let loaded = previous || !canUseCommonJsGraph(file) ? void 0 : await loader.loadModule(file, base, onDependency);
82
+ if (!loaded || !collectCommonJsDependencies(file, dependencies)) loaded = await loader.loadModule(`./${path.basename(file)}`, base, onDependency);
83
+ const files = [...dependencies].sort();
84
+ return {
85
+ dependencies: files,
86
+ fingerprint: fingerprint(files),
87
+ value: { default: loaded.module }
88
+ };
89
+ });
90
+ cache.set(file, pending);
91
+ while (cache.size > MODULE_CACHE_LIMIT) cache.delete(cache.keys().next().value);
92
+ try {
93
+ return await pending;
94
+ } catch (error) {
95
+ if (cache.get(file) === pending) cache.delete(file);
96
+ throw error;
97
+ }
98
+ }
99
+ function installHook() {
100
+ if (installedHook && hooks.__tw_load === installedHook) return;
101
+ const previous = hooks.__tw_load;
102
+ installedHook = async (url) => {
103
+ const overridden = await previous?.(url);
104
+ if (overridden) return overridden;
105
+ const active = context.getStore();
106
+ if (!active?.loader.loadModule || !url.startsWith("file:")) return;
107
+ const parsed = new URL(url);
108
+ const file = resolveSourceScanPath(fileURLToPath(parsed));
109
+ if (!parsed.searchParams.has("id") && !active.files.has(file)) return;
110
+ const entry = await loadCachedModule(active.loader, file);
111
+ for (const dependency of entry.dependencies) active.onDependency?.(dependency);
112
+ return entry.value;
113
+ };
114
+ hooks.__tw_load = installedHook;
115
+ }
116
+ function invalidateGenerationModuleCache() {
117
+ revision += 1;
118
+ }
119
+ /** 仅在本引擎调用内复用本地模块;Tailwind 仍负责解析与依赖跟踪。 */
120
+ async function withGenerationModuleCache(loader, run, files = [], onDependency) {
121
+ installHook();
122
+ const resolved = files.map((file) => {
123
+ for (const candidate of [
124
+ file,
125
+ `${file}.ts`,
126
+ path.join(file, "index.ts")
127
+ ]) try {
128
+ return resolveSourceScanPath(require.resolve(candidate));
129
+ } catch {}
130
+ return resolveSourceScanPath(file);
131
+ });
132
+ activeContexts += 1;
133
+ try {
134
+ return await context.run({
135
+ loader,
136
+ files: new Set(resolved),
137
+ onDependency
138
+ }, run);
139
+ } finally {
140
+ activeContexts -= 1;
141
+ if (activeContexts === 0) context.disable();
142
+ }
143
+ }
144
+ //#endregion
145
+ //#region src/v4/module-requests.ts
146
+ /** 固定本地模块身份,依赖跟踪与刷新由生成模块缓存负责。 */
147
+ function prepareGenerationModuleRequests(css, base) {
148
+ const files = [];
149
+ if (!css.includes("@config") && !css.includes("@plugin")) return {
150
+ css,
151
+ files
152
+ };
153
+ const root = postcss.parse(css);
154
+ root.walkAtRules((rule) => {
155
+ if (rule.name !== "config" && rule.name !== "plugin") return;
156
+ const match = /^(["'])(.*)\1$/.exec(rule.params.trim());
157
+ if (!match) return;
158
+ const request = match[2];
159
+ const pathApi = /^[a-z]:[\\/]/i.test(base) || base.includes("\\") ? path.win32 : path;
160
+ if (!pathApi.isAbsolute(request) && !request.startsWith(".")) return;
161
+ const file = pathApi.resolve(base, request);
162
+ files.push(file);
163
+ rule.params = JSON.stringify(file.replaceAll("\\", "/"));
164
+ });
165
+ return {
166
+ css: root.toString(),
167
+ files
168
+ };
169
+ }
170
+ //#endregion
9
171
  //#region src/v4/node-adapter.ts
10
172
  const nodeModulePromiseCache = /* @__PURE__ */ new Map();
11
173
  const designSystemPromiseCache = /* @__PURE__ */ new Map();
@@ -86,7 +248,8 @@ async function createTailwindV4DesignSystem(source) {
86
248
  const node = await loadTailwindV4NodeModule([source.projectRoot, ...bases]);
87
249
  let lastError;
88
250
  for (const base of bases) try {
89
- return await node.__unstable__loadDesignSystem(source.css, { base });
251
+ const prepared = prepareGenerationModuleRequests(source.css, base);
252
+ return await withGenerationModuleCache(node, () => node.__unstable__loadDesignSystem(prepared.css, { base }), prepared.files);
90
253
  } catch (error) {
91
254
  lastError = error;
92
255
  }
@@ -113,14 +276,14 @@ async function compileTailwindV4Source(source) {
113
276
  for (const base of bases) {
114
277
  const dependencies = new Set(source.dependencies);
115
278
  try {
279
+ const prepared = prepareGenerationModuleRequests(source.css, base);
280
+ const onDependency = (dependency) => dependencies.add(path.resolve(dependency));
116
281
  return {
117
- compiled: await node.compile(source.css, {
282
+ compiled: await withGenerationModuleCache(node, () => node.compile(prepared.css, {
118
283
  base,
119
284
  customCssResolver: createFallbackCssResolver([source.projectRoot, ...bases]),
120
- onDependency(dependency) {
121
- dependencies.add(path.resolve(dependency));
122
- }
123
- }),
285
+ onDependency
286
+ }), prepared.files, onDependency),
124
287
  dependencies
125
288
  };
126
289
  } catch (error) {
@@ -131,6 +294,17 @@ async function compileTailwindV4Source(source) {
131
294
  throw new Error("Failed to compile Tailwind CSS v4 source.");
132
295
  }
133
296
  //#endregion
297
+ //#region src/v4/source-scan/matcher.ts
298
+ function isFileMatchedByTailwindV4SourceEntries(file, entries) {
299
+ return isFileMatchedByTailwindSourceEntries(file, entries, { requirePositive: true });
300
+ }
301
+ function createTailwindV4SourceEntryMatcher(entries) {
302
+ return entries?.length ? (file) => isFileMatchedByTailwindV4SourceEntries(file, entries) : void 0;
303
+ }
304
+ function createTailwindV4SourceExclusionMatcher(entries) {
305
+ return entries?.length ? (file) => isFileExcludedByTailwindSourceEntries(file, entries) : void 0;
306
+ }
307
+ //#endregion
134
308
  //#region src/v4/source-scan/sources.ts
135
309
  const TAILWIND_V4_IGNORED_CONTENT_DIRS = [
136
310
  ".git",
@@ -175,48 +349,6 @@ function uniqueResolvedPaths(values) {
175
349
  }
176
350
  return result;
177
351
  }
178
- function toPosixPath(value) {
179
- return value.replaceAll(path.sep, "/");
180
- }
181
- function resolveSourceScanPath(value) {
182
- const resolved = path.resolve(value);
183
- try {
184
- return realpathSync.native(resolved);
185
- } catch {
186
- return resolved;
187
- }
188
- }
189
- function normalizeGlobPattern(pattern) {
190
- return pattern.startsWith("./") ? pattern.slice(2) : pattern;
191
- }
192
- function hasGlobMagic(value) {
193
- return /[*?[\]{}()!+@]/.test(value);
194
- }
195
- function splitStaticGlobPrefix(pattern) {
196
- const segments = normalizeGlobPattern(pattern).split(/[\\/]+/);
197
- const prefix = [];
198
- const rest = [];
199
- let reachedGlob = false;
200
- for (const segment of segments) {
201
- if (!reachedGlob && segment && !hasGlobMagic(segment)) {
202
- prefix.push(segment);
203
- continue;
204
- }
205
- reachedGlob = true;
206
- rest.push(segment);
207
- }
208
- return {
209
- prefix,
210
- rest
211
- };
212
- }
213
- async function pathExistsAsDirectory(file) {
214
- try {
215
- return (await stat(file)).isDirectory();
216
- } catch {
217
- return false;
218
- }
219
- }
220
352
  function createTailwindV4DefaultIgnoreSources(base) {
221
353
  return [
222
354
  ...TAILWIND_V4_IGNORED_CONTENT_DIRS.map((pattern) => ({
@@ -237,117 +369,32 @@ function createTailwindV4DefaultIgnoreSources(base) {
237
369
  ];
238
370
  }
239
371
  function createTailwindV4RootSources(root, fallbackBase) {
240
- if (root === "none") return [];
241
- if (root === null) return [{
372
+ return createSourceScanPlan({
242
373
  base: fallbackBase,
243
- pattern: TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN,
244
- negated: false
245
- }];
246
- return [{
247
- ...root,
248
- negated: false
249
- }];
374
+ mode: root === "none" ? "disabled" : root === null ? "auto" : "explicit",
375
+ entries: root && root !== "none" ? [{
376
+ ...root,
377
+ negated: false
378
+ }] : []
379
+ });
250
380
  }
251
381
  function createTailwindV4CompiledSourceEntries(root, sources, fallbackBase) {
252
382
  return [...createTailwindV4RootSources(root, fallbackBase), ...sources];
253
383
  }
254
- async function resolveTailwindV4SourceEntry(sourcePath, base, negated, defaultPattern = TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN) {
255
- const absoluteSource = path.isAbsolute(sourcePath) ? path.resolve(sourcePath) : path.resolve(base, sourcePath);
256
- if (await pathExistsAsDirectory(absoluteSource)) return {
257
- base: absoluteSource,
258
- negated,
259
- pattern: normalizeGlobPattern(defaultPattern)
260
- };
261
- if (path.isAbsolute(sourcePath)) return {
262
- base: path.dirname(absoluteSource),
263
- negated,
264
- pattern: normalizeGlobPattern(path.basename(absoluteSource))
265
- };
266
- const { prefix, rest } = splitStaticGlobPrefix(sourcePath);
267
- if (prefix.length > 0 && rest.length > 0) return {
268
- base: path.resolve(base, ...prefix),
269
- negated,
270
- pattern: normalizeGlobPattern(rest.join("/"))
271
- };
272
- return {
273
- base,
274
- negated,
275
- pattern: normalizeGlobPattern(sourcePath)
276
- };
384
+ function resolveTailwindV4SourceEntry(sourcePath, base, negated, defaultPattern = TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN) {
385
+ return resolveTailwindSourceEntry(sourcePath, base, negated, defaultPattern);
277
386
  }
278
387
  async function normalizeTailwindV4SourceEntries(sources, options = {}) {
279
388
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
280
389
  return Promise.all(sources.map((source) => resolveTailwindV4SourceEntry(source.pattern, source.base ? path.resolve(source.base) : cwd, source.negated, options.defaultPattern)));
281
390
  }
282
- function expandBracePattern(pattern) {
283
- const index = pattern.indexOf("{");
284
- if (index === -1) return [pattern];
285
- const rest = pattern.slice(index);
286
- let depth = 0;
287
- let endIndex = -1;
288
- for (let i = 0; i < rest.length; i++) {
289
- const char = rest[i];
290
- if (char === "\\") {
291
- i += 1;
292
- continue;
293
- }
294
- if (char === "{") {
295
- depth += 1;
296
- continue;
297
- }
298
- if (char === "}") {
299
- depth -= 1;
300
- if (depth === 0) {
301
- endIndex = i;
302
- break;
303
- }
304
- }
305
- }
306
- if (endIndex === -1) return [pattern];
307
- const prefix = pattern.slice(0, index);
308
- const inner = rest.slice(1, endIndex);
309
- const suffix = rest.slice(endIndex + 1);
310
- const parts = [];
311
- const stack = [];
312
- let lastPos = 0;
313
- for (let i = 0; i < inner.length; i++) {
314
- const char = inner[i];
315
- if (char === "\\") {
316
- i += 1;
317
- continue;
318
- }
319
- if (char === "{") {
320
- stack.push("}");
321
- continue;
322
- }
323
- if (char === "}" && stack[stack.length - 1] === "}") {
324
- stack.pop();
325
- continue;
326
- }
327
- if (char === "," && stack.length === 0) {
328
- parts.push(inner.slice(lastPos, i));
329
- lastPos = i + 1;
330
- }
331
- }
332
- parts.push(inner.slice(lastPos));
333
- return parts.flatMap((part) => expandBracePattern(`${prefix}${part}${suffix}`));
334
- }
335
- function expandTailwindV4SourceEntryBraces(sources) {
336
- return sources.flatMap((source) => {
337
- const base = path.resolve(source.base);
338
- return expandBracePattern(source.pattern).map((pattern) => ({
339
- base,
340
- pattern,
341
- negated: source.negated
342
- }));
343
- });
344
- }
345
391
  function normalizeTailwindV4ScannerSources(sources, cwd, ignoredSources = []) {
346
- return expandTailwindV4SourceEntryBraces([...sources?.length ? sources : [{
392
+ const baseSources = sources?.length ? sources : [{
347
393
  base: cwd,
348
394
  pattern: TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN,
349
395
  negated: false
350
- }], ...ignoredSources]);
396
+ }];
397
+ return expandSourceEntryBraces([...baseSources, ...ignoredSources]);
351
398
  }
352
399
  function resolveTailwindV4SourceBaseCandidates(projectRoot, base, baseFallbacks) {
353
400
  return uniqueResolvedPaths([
@@ -357,79 +404,6 @@ function resolveTailwindV4SourceBaseCandidates(projectRoot, base, baseFallbacks)
357
404
  ]);
358
405
  }
359
406
  //#endregion
360
- //#region src/v4/source-scan/matcher.ts
361
- function normalizeEntryPattern(entry) {
362
- return path.isAbsolute(entry.pattern) ? toPosixPath(path.relative(resolveSourceScanPath(entry.base), entry.pattern)) : normalizeGlobPattern(entry.pattern);
363
- }
364
- function isFileMatchedByTailwindV4SourceEntry(file, entry) {
365
- const relative = toPosixPath(path.relative(resolveSourceScanPath(entry.base), file));
366
- return Boolean(relative) && !relative.startsWith("../") && !path.isAbsolute(relative) && micromatch.isMatch(relative, normalizeEntryPattern(entry));
367
- }
368
- function isFileExcludedByTailwindV4SourceEntries(file, entries) {
369
- if (!entries?.length) return false;
370
- const resolvedFile = resolveSourceScanPath(file);
371
- return entries.some((entry) => entry.negated && isFileMatchedByTailwindV4SourceEntry(resolvedFile, entry));
372
- }
373
- function isFileMatchedByTailwindV4SourceEntries(file, entries) {
374
- if (!entries?.length) return true;
375
- const positiveEntries = entries.filter((entry) => !entry.negated);
376
- const negativeEntries = entries.filter((entry) => entry.negated);
377
- if (positiveEntries.length === 0) return false;
378
- const resolvedFile = resolveSourceScanPath(file);
379
- if (!positiveEntries.some((entry) => isFileMatchedByTailwindV4SourceEntry(resolvedFile, entry))) return false;
380
- return !negativeEntries.some((entry) => isFileMatchedByTailwindV4SourceEntry(resolvedFile, entry));
381
- }
382
- function createTailwindV4SourceEntryMatcher(entries) {
383
- if (!entries?.length) return;
384
- return (file) => isFileMatchedByTailwindV4SourceEntries(file, entries);
385
- }
386
- function createTailwindV4SourceExclusionMatcher(entries) {
387
- if (!entries?.length) return;
388
- return (file) => isFileExcludedByTailwindV4SourceEntries(file, entries);
389
- }
390
- function groupTailwindV4SourceEntriesByBase(entries) {
391
- const entriesByBase = /* @__PURE__ */ new Map();
392
- for (const entry of entries) {
393
- const base = path.resolve(entry.base);
394
- const group = entriesByBase.get(base) ?? [];
395
- group.push({
396
- ...entry,
397
- base,
398
- pattern: normalizeGlobPattern(entry.pattern)
399
- });
400
- entriesByBase.set(base, group);
401
- }
402
- return entriesByBase;
403
- }
404
- async function expandTailwindV4SourceEntries(entries, resolveFiles) {
405
- if (entries.length === 0) return [];
406
- const files = /* @__PURE__ */ new Set();
407
- await Promise.all([...groupTailwindV4SourceEntriesByBase(entries).entries()].map(async ([base, group]) => {
408
- const matched = await resolveFiles({
409
- cwd: base,
410
- sources: group
411
- });
412
- for (const file of matched) files.add(path.resolve(file));
413
- }));
414
- return [...files].filter((file) => !isFileExcludedByTailwindV4SourceEntries(file, entries));
415
- }
416
- function mergeTailwindV4SourceEntries(...entries) {
417
- const result = [];
418
- const seen = /* @__PURE__ */ new Set();
419
- for (const group of entries) for (const entry of group ?? []) {
420
- const normalized = {
421
- base: path.resolve(entry.base),
422
- pattern: normalizeGlobPattern(entry.pattern),
423
- negated: entry.negated
424
- };
425
- const key = JSON.stringify(normalized);
426
- if (seen.has(key)) continue;
427
- seen.add(key);
428
- result.push(normalized);
429
- }
430
- return result;
431
- }
432
- //#endregion
433
407
  //#region src/extraction/oxide.ts
434
408
  let oxideImportPromise;
435
409
  function createOxideRuntimeDependencyError(cause) {
@@ -517,6 +491,94 @@ function createTokenLocation(input) {
517
491
  };
518
492
  }
519
493
  //#endregion
494
+ //#region src/extraction/candidate-extractor/project.ts
495
+ async function resolveScannerSources(options) {
496
+ const cwd = options?.cwd ? path.resolve(options.cwd) : process.cwd();
497
+ if (options?.sources?.length || options?.css === void 0) return {
498
+ cwd,
499
+ sources: normalizeTailwindV4ScannerSources(options?.sources, cwd, options?.ignoredSources)
500
+ };
501
+ const base = options.base ? path.resolve(options.base) : cwd;
502
+ const { compiled } = await compileTailwindV4Source({
503
+ projectRoot: cwd,
504
+ base,
505
+ baseFallbacks: options.baseFallbacks?.map((baseFallback) => path.resolve(baseFallback)) ?? [],
506
+ css: options.css,
507
+ dependencies: []
508
+ });
509
+ return {
510
+ cwd,
511
+ sources: normalizeTailwindV4ScannerSources(createTailwindV4CompiledSourceEntries(compiled.root, compiled.sources, base), cwd, options.ignoredSources)
512
+ };
513
+ }
514
+ async function resolveProjectSourceFiles(options) {
515
+ const { sources } = await resolveScannerSources(options);
516
+ const { Scanner } = await getOxideModule();
517
+ const files = new Scanner({ sources }).files ?? [];
518
+ return options?.filter ? files.filter(options.filter) : files;
519
+ }
520
+ async function extractProjectCandidatesWithPositions(options) {
521
+ const { cwd, sources } = await resolveScannerSources(options);
522
+ const { Scanner } = await getOxideModule();
523
+ const scanner = new Scanner({ sources });
524
+ const files = scanner.files ?? [];
525
+ const entries = [];
526
+ const skipped = [];
527
+ const batchSize = 32;
528
+ for (let start = 0; start < files.length; start += batchSize) {
529
+ const batch = files.slice(start, start + batchSize);
530
+ const contents = await Promise.allSettled(batch.map((file) => promises.readFile(file, "utf8")));
531
+ for (const [index, file] of batch.entries()) {
532
+ const result = contents[index];
533
+ if (result.status === "rejected") {
534
+ skipped.push({
535
+ file,
536
+ reason: result.reason instanceof Error ? result.reason.message : "Unknown error"
537
+ });
538
+ continue;
539
+ }
540
+ const content = result.value;
541
+ const extension = toExtension(file);
542
+ const matches = scanner.getCandidatesWithPositions({
543
+ file,
544
+ content,
545
+ extension
546
+ });
547
+ if (!matches.length) continue;
548
+ const offsets = buildLineOffsets(content);
549
+ for (const match of matches) entries.push(createTokenLocation({
550
+ cwd,
551
+ file,
552
+ content,
553
+ extension,
554
+ candidate: match.candidate,
555
+ position: match.position,
556
+ offsets
557
+ }));
558
+ }
559
+ }
560
+ return {
561
+ entries,
562
+ filesScanned: files.length,
563
+ skippedFiles: skipped,
564
+ sources
565
+ };
566
+ }
567
+ function groupTokensByFile(report, options) {
568
+ const key = options?.key ?? "relative";
569
+ const stripAbsolute = options?.stripAbsolutePaths ?? key !== "absolute";
570
+ return report.entries.reduce((acc, entry) => {
571
+ const bucketKey = key === "absolute" ? entry.file : entry.relativeFile;
572
+ const bucket = acc[bucketKey] ?? (acc[bucketKey] = []);
573
+ const value = stripAbsolute ? {
574
+ ...entry,
575
+ file: entry.relativeFile
576
+ } : entry;
577
+ bucket.push(value);
578
+ return acc;
579
+ }, {});
580
+ }
581
+ //#endregion
520
582
  //#region src/v4/bare-arbitrary-values/escape.ts
521
583
  function escapeCssClassName(value) {
522
584
  let result = "";
@@ -3533,6 +3595,7 @@ function createTailwindGenerationArtifact(css, source, request, classSet, rawCan
3533
3595
  //#region src/v4/generation-request.ts
3534
3596
  function toGenerationRequest(options) {
3535
3597
  return {
3598
+ ...options?.excludeFiles === void 0 ? {} : { excludeFiles: options.excludeFiles },
3536
3599
  ...options?.candidates === void 0 ? {} : { candidates: options.candidates },
3537
3600
  ...options?.sources === void 0 ? {} : { sourceEntries: options.sources.map((sourceEntry, index) => ({
3538
3601
  id: `source:${index}`,
@@ -3544,6 +3607,7 @@ function toGenerationRequest(options) {
3544
3607
  }
3545
3608
  function toGenerateOptions(request) {
3546
3609
  return {
3610
+ ...request.excludeFiles === void 0 ? {} : { excludeFiles: request.excludeFiles },
3547
3611
  ...request.candidates === void 0 ? {} : { candidates: request.candidates },
3548
3612
  ...request.sourceEntries === void 0 ? {} : { sources: request.sourceEntries },
3549
3613
  ...request.bareArbitraryValues === void 0 ? {} : { bareArbitraryValues: request.bareArbitraryValues },
@@ -3582,7 +3646,7 @@ function stripCompiledSourceEntries(source) {
3582
3646
  return source;
3583
3647
  }
3584
3648
  }
3585
- async function collectRawCandidates(source, options, compiledRoot, compiledSources = []) {
3649
+ async function collectRawCandidates(source, options, compiledRoot, compiledSources = [], onFiles) {
3586
3650
  const rawCandidates = /* @__PURE__ */ new Set();
3587
3651
  const extractOptions = options?.bareArbitraryValues === void 0 ? void 0 : { bareArbitraryValues: options.bareArbitraryValues };
3588
3652
  for (const candidate of options?.candidates ?? []) rawCandidates.add(candidate);
@@ -3591,7 +3655,25 @@ async function collectRawCandidates(source, options, compiledRoot, compiledSourc
3591
3655
  for (const candidate of candidates) rawCandidates.add(candidate.rawCandidate);
3592
3656
  }
3593
3657
  const filesystemSources = resolveScanSources(options, source, compiledRoot, compiledSources);
3594
- if (filesystemSources.length > 0) for (const candidate of await extractRawCandidates(filesystemSources, extractOptions)) rawCandidates.add(candidate);
3658
+ if (filesystemSources.some((entry) => !entry.negated)) {
3659
+ const excluded = new Set((options?.excludeFiles ?? []).map(resolveSourceScanPath));
3660
+ const files = await resolveProjectSourceFiles({
3661
+ sources: filesystemSources,
3662
+ filter: (file) => !excluded.has(resolveSourceScanPath(file))
3663
+ });
3664
+ onFiles?.(files);
3665
+ for (const file of files) {
3666
+ let content;
3667
+ try {
3668
+ content = await readFile(file, "utf8");
3669
+ } catch (error) {
3670
+ const code = error?.code;
3671
+ if (code === "ENOENT" || code === "ENOTDIR") continue;
3672
+ throw error;
3673
+ }
3674
+ for (const candidate of await extractRawCandidatesWithPositions(content, path.extname(file).slice(1), extractOptions)) rawCandidates.add(candidate.rawCandidate);
3675
+ }
3676
+ }
3595
3677
  const inlineSources = extractTailwindV4InlineSourceCandidates(source.css);
3596
3678
  for (const candidate of inlineSources.included) rawCandidates.add(candidate);
3597
3679
  for (const candidate of inlineSources.excluded) rawCandidates.delete(candidate);
@@ -3604,6 +3686,7 @@ var TailwindGenerationSessionImpl = class {
3604
3686
  runtimes = /* @__PURE__ */ new Map();
3605
3687
  designSystemPromise;
3606
3688
  disposed = false;
3689
+ moduleDependencies = /* @__PURE__ */ new Set();
3607
3690
  constructor(source) {
3608
3691
  this.currentSource = source;
3609
3692
  }
@@ -3636,10 +3719,15 @@ var TailwindGenerationSessionImpl = class {
3636
3719
  invalidate(change) {
3637
3720
  if (this.disposed) return;
3638
3721
  if (change.type === "source") this.currentSource = change.source;
3722
+ clearRequireCache([...this.moduleDependencies]);
3723
+ invalidateGenerationModuleCache();
3724
+ this.moduleDependencies.clear();
3639
3725
  this.runtimes.clear();
3640
3726
  this.designSystemPromise = void 0;
3641
3727
  }
3642
3728
  dispose() {
3729
+ clearRequireCache([...this.moduleDependencies]);
3730
+ this.moduleDependencies.clear();
3643
3731
  this.runtimes.clear();
3644
3732
  this.designSystemPromise = void 0;
3645
3733
  this.disposed = true;
@@ -3647,12 +3735,12 @@ var TailwindGenerationSessionImpl = class {
3647
3735
  assertActive() {
3648
3736
  if (this.disposed) throw new Error("Tailwind generation session has been disposed.");
3649
3737
  }
3650
- getRuntime(compileSourceEntries) {
3738
+ getRuntime(compileSourceEntries, designSystem) {
3651
3739
  this.assertActive();
3652
3740
  const cached = this.runtimes.get(compileSourceEntries);
3653
3741
  if (cached) return cached;
3654
3742
  const source = compileSourceEntries ? this.currentSource : stripCompiledSourceEntries(this.currentSource);
3655
- const promise = Promise.all([compileTailwindV4Source(source), loadTailwindV4DesignSystem(source, { cache: false })]).then(([compiledSource, designSystem]) => ({
3743
+ const promise = Promise.all([compileTailwindV4Source(source), designSystem ?? loadTailwindV4DesignSystem(source, { cache: false })]).then(([compiledSource, designSystem]) => ({
3656
3744
  compiled: compiledSource.compiled,
3657
3745
  dependencies: compiledSource.dependencies,
3658
3746
  designSystem,
@@ -3664,28 +3752,32 @@ var TailwindGenerationSessionImpl = class {
3664
3752
  });
3665
3753
  return promise;
3666
3754
  }
3667
- async resetRuntime(compileSourceEntries) {
3755
+ async resetRuntime(compileSourceEntries, designSystem) {
3668
3756
  this.runtimes.delete(compileSourceEntries);
3669
- return this.getRuntime(compileSourceEntries);
3757
+ return this.getRuntime(compileSourceEntries, designSystem);
3670
3758
  }
3671
3759
  async generateInternal(request) {
3672
3760
  this.assertActive();
3673
3761
  const options = toGenerateOptions(request);
3674
3762
  const compileSourceEntries = shouldCompileSourceEntries(options);
3675
3763
  let runtime = await this.getRuntime(compileSourceEntries);
3676
- const rawCandidates = await collectRawCandidates(this.currentSource, options, runtime.compiled.root, runtime.compiled.sources);
3764
+ const sourceFiles = [];
3765
+ let rawCandidates = await collectRawCandidates(this.currentSource, options, runtime.compiled.root, runtime.compiled.sources, (files) => sourceFiles.push(...files));
3766
+ if (request.prepareCandidates) rawCandidates = new Set(request.prepareCandidates(rawCandidates));
3677
3767
  const classSet = resolveValidTailwindV4Candidates(runtime.designSystem, rawCandidates, { ...options.bareArbitraryValues === void 0 ? {} : { bareArbitraryValues: options.bareArbitraryValues } });
3678
3768
  const inlineSources = extractTailwindV4InlineSourceCandidates(this.currentSource.css);
3679
3769
  for (const candidate of inlineSources.excluded) classSet.delete(candidate);
3680
3770
  const buildCandidates = new Set(canonicalizeBareArbitraryValueCandidates(classSet, options.bareArbitraryValues));
3681
- if ([...runtime.builtCandidates].some((candidate) => !buildCandidates.has(candidate))) runtime = await this.resetRuntime(compileSourceEntries);
3771
+ if ([...runtime.builtCandidates].some((candidate) => !buildCandidates.has(candidate))) runtime = await this.resetRuntime(compileSourceEntries, runtime.designSystem);
3682
3772
  const css = replaceBareArbitraryValueSelectors(runtime.compiled.build([...buildCandidates]), classSet, options.bareArbitraryValues);
3683
3773
  runtime.builtCandidates = new Set(buildCandidates);
3774
+ for (const dependency of runtime.dependencies) this.moduleDependencies.add(dependency);
3775
+ const dependencies = [.../* @__PURE__ */ new Set([...runtime.dependencies, ...sourceFiles])];
3684
3776
  return {
3685
3777
  css,
3686
3778
  classSet,
3687
3779
  rawCandidates,
3688
- dependencies: Array.from(runtime.dependencies),
3780
+ dependencies,
3689
3781
  sources: runtime.compiled.sources,
3690
3782
  root: runtime.compiled.root
3691
3783
  };
@@ -3703,6 +3795,9 @@ function createTailwindV4Engine(source) {
3703
3795
  const session = createTailwindV4EngineGenerationSession(source);
3704
3796
  return {
3705
3797
  source,
3798
+ dispose() {
3799
+ session.dispose();
3800
+ },
3706
3801
  loadDesignSystem() {
3707
3802
  return session.loadDesignSystem();
3708
3803
  },
@@ -3859,16 +3954,21 @@ async function collectTailwindV4StyleCandidates(options) {
3859
3954
  async function generateTailwindV4Style(options = {}) {
3860
3955
  const source = options.source ?? await resolveTailwindV4Source(createSourceOptions(options));
3861
3956
  const candidates = await collectTailwindV4StyleCandidates(options);
3862
- const result = await createTailwindV4Engine(source).generate({
3863
- candidates,
3864
- ...options.bareArbitraryValues === void 0 ? {} : { bareArbitraryValues: options.bareArbitraryValues },
3865
- ...options.scanSources === void 0 ? {} : { scanSources: options.scanSources }
3866
- });
3867
- return {
3868
- ...result,
3869
- tokens: result.rawCandidates,
3870
- source
3871
- };
3957
+ const engine = createTailwindV4Engine(source);
3958
+ try {
3959
+ const result = await engine.generate({
3960
+ candidates,
3961
+ ...options.bareArbitraryValues === void 0 ? {} : { bareArbitraryValues: options.bareArbitraryValues },
3962
+ ...options.scanSources === void 0 ? {} : { scanSources: options.scanSources }
3963
+ });
3964
+ return {
3965
+ ...result,
3966
+ tokens: result.rawCandidates,
3967
+ source
3968
+ };
3969
+ } finally {
3970
+ engine.dispose?.();
3971
+ }
3872
3972
  }
3873
3973
  //#endregion
3874
- export { mergeTailwindV4SourceEntries as A, normalizeTailwindV4ScannerSources as B, getOxideModule as C, groupTailwindV4SourceEntriesByBase as D, expandTailwindV4SourceEntries as E, createTailwindV4CompiledSourceEntries as F, toPosixPath as G, resolveSourceScanPath as H, createTailwindV4DefaultIgnoreSources as I, loadTailwindV4DesignSystem as J, compileTailwindV4Source as K, createTailwindV4RootSources as L, TAILWIND_V4_IGNORED_CONTENT_DIRS as M, TAILWIND_V4_IGNORED_EXTENSIONS as N, isFileExcludedByTailwindV4SourceEntries as O, TAILWIND_V4_IGNORED_FILES as P, expandTailwindV4SourceEntryBraces as R, toExtension as S, createTailwindV4SourceExclusionMatcher as T, resolveTailwindV4SourceBaseCandidates as U, normalizeTailwindV4SourceEntries as V, resolveTailwindV4SourceEntry as W, loadTailwindV4NodeModule as Y, isBareArbitraryValuesEnabled as _, createTailwindGenerationSession as a, buildLineOffsets as b, replaceBareArbitraryValueSelectors as c, extractRawCandidates as d, extractRawCandidatesWithPositions as f, extractBareArbitraryValueSourceCandidatesWithPositions as g, extractBareArbitraryValueSourceCandidates as h, createTailwindV4Engine as i, TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN as j, isFileMatchedByTailwindV4SourceEntries as k, resolveValidTailwindV4Candidates as l, extractSourceCandidatesWithPositions as m, generateTailwindV4Style as n, collectTailwindStyleCandidates as o, extractSourceCandidates as p, getTailwindV4DesignSystemCacheKey as q, resolveTailwindV4Source as r, canonicalizeBareArbitraryValueCandidates as s, collectTailwindV4StyleCandidates as t, extractTailwindV4InlineSourceCandidates as u, resolveBareArbitraryValueCandidate as v, createTailwindV4SourceEntryMatcher as w, createTokenLocation as x, escapeCssClassName as y, normalizeGlobPattern as z };
3974
+ export { expandTailwindV4SourceEntryBraces as A, expandTailwindV4SourceEntries as B, TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN as C, createTailwindV4CompiledSourceEntries as D, TAILWIND_V4_IGNORED_FILES as E, resolveTailwindV4SourceBaseCandidates as F, compileTailwindV4Source as G, isFileExcludedByTailwindV4SourceEntries as H, resolveTailwindV4SourceEntry as I, loadTailwindV4NodeModule as J, getTailwindV4DesignSystemCacheKey as K, toPosixPath as L, normalizeTailwindV4ScannerSources as M, normalizeTailwindV4SourceEntries as N, createTailwindV4DefaultIgnoreSources as O, resolveSourceScanPath$1 as P, createTailwindV4SourceEntryMatcher as R, resolveProjectSourceFiles as S, TAILWIND_V4_IGNORED_EXTENSIONS as T, isFileMatchedByTailwindV4SourceEntries as U, groupTailwindV4SourceEntriesByBase as V, mergeTailwindV4SourceEntries as W, isBareArbitraryValuesEnabled as _, createTailwindGenerationSession as a, extractProjectCandidatesWithPositions as b, replaceBareArbitraryValueSelectors as c, extractRawCandidates as d, extractRawCandidatesWithPositions as f, extractBareArbitraryValueSourceCandidatesWithPositions as g, extractBareArbitraryValueSourceCandidates as h, createTailwindV4Engine as i, normalizeGlobPattern as j, createTailwindV4RootSources as k, resolveValidTailwindV4Candidates as l, extractSourceCandidatesWithPositions as m, generateTailwindV4Style as n, collectTailwindStyleCandidates as o, extractSourceCandidates as p, loadTailwindV4DesignSystem as q, resolveTailwindV4Source as r, canonicalizeBareArbitraryValueCandidates as s, collectTailwindV4StyleCandidates as t, extractTailwindV4InlineSourceCandidates as u, resolveBareArbitraryValueCandidate as v, TAILWIND_V4_IGNORED_CONTENT_DIRS as w, groupTokensByFile as x, escapeCssClassName as y, createTailwindV4SourceExclusionMatcher as z };