@weapp-tailwindcss/engine 0.1.0 → 0.1.2

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,14 +1,188 @@
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 { createHash } from "node:crypto";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { AsyncLocalStorage } from "node:async_hooks";
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();
174
+ function fingerprintDesignDependencies(files) {
175
+ const hash = createHash("sha256");
176
+ for (const file of [...files].sort()) {
177
+ hash.update(file);
178
+ try {
179
+ hash.update(readFileSync(file));
180
+ } catch {
181
+ hash.update("missing");
182
+ }
183
+ }
184
+ return hash.digest("hex");
185
+ }
12
186
  function unique(values) {
13
187
  return Array.from(new Set(Array.from(values).filter(Boolean).map((value) => path.resolve(value))));
14
188
  }
@@ -80,13 +254,14 @@ function createDesignSystemCacheKey(css, bases) {
80
254
  function getTailwindV4DesignSystemCacheKey(source) {
81
255
  return createDesignSystemCacheKey(source.css, [source.base, ...source.baseFallbacks]);
82
256
  }
83
- async function createTailwindV4DesignSystem(source) {
257
+ async function createTailwindV4DesignSystem(source, onDependency) {
84
258
  const bases = unique([source.base, ...source.baseFallbacks]);
85
259
  if (bases.length === 0) throw new Error("No base directories provided for Tailwind CSS v4 design system.");
86
260
  const node = await loadTailwindV4NodeModule([source.projectRoot, ...bases]);
87
261
  let lastError;
88
262
  for (const base of bases) try {
89
- return await node.__unstable__loadDesignSystem(source.css, { base });
263
+ const prepared = prepareGenerationModuleRequests(source.css, base);
264
+ return await withGenerationModuleCache(node, () => node.__unstable__loadDesignSystem(prepared.css, { base }), prepared.files, onDependency);
90
265
  } catch (error) {
91
266
  lastError = error;
92
267
  }
@@ -95,13 +270,20 @@ async function createTailwindV4DesignSystem(source) {
95
270
  }
96
271
  async function loadTailwindV4DesignSystem(source, options) {
97
272
  if (options?.cache === false) return createTailwindV4DesignSystem(source);
98
- const cacheKey = getTailwindV4DesignSystemCacheKey(source);
273
+ const cacheKey = JSON.stringify([source.projectRoot, getTailwindV4DesignSystemCacheKey(source)]);
99
274
  const cached = designSystemPromiseCache.get(cacheKey);
100
- if (cached) return cached;
101
- const promise = createTailwindV4DesignSystem(source);
102
- designSystemPromiseCache.set(cacheKey, promise);
103
- promise.catch(() => {
104
- if (designSystemPromiseCache.get(cacheKey) === promise) designSystemPromiseCache.delete(cacheKey);
275
+ if (cached && (cached.fingerprint === void 0 || cached.fingerprint === fingerprintDesignDependencies(cached.dependencies))) return cached.promise;
276
+ const dependencies = new Set(source.dependencies);
277
+ const promise = createTailwindV4DesignSystem(source, (file) => dependencies.add(file));
278
+ const entry = {
279
+ promise,
280
+ dependencies
281
+ };
282
+ designSystemPromiseCache.set(cacheKey, entry);
283
+ promise.then(() => {
284
+ entry.fingerprint = fingerprintDesignDependencies(dependencies);
285
+ }, () => {
286
+ if (designSystemPromiseCache.get(cacheKey) === entry) designSystemPromiseCache.delete(cacheKey);
105
287
  });
106
288
  return promise;
107
289
  }
@@ -113,14 +295,14 @@ async function compileTailwindV4Source(source) {
113
295
  for (const base of bases) {
114
296
  const dependencies = new Set(source.dependencies);
115
297
  try {
298
+ const prepared = prepareGenerationModuleRequests(source.css, base);
299
+ const onDependency = (dependency) => dependencies.add(path.resolve(dependency));
116
300
  return {
117
- compiled: await node.compile(source.css, {
301
+ compiled: await withGenerationModuleCache(node, () => node.compile(prepared.css, {
118
302
  base,
119
303
  customCssResolver: createFallbackCssResolver([source.projectRoot, ...bases]),
120
- onDependency(dependency) {
121
- dependencies.add(path.resolve(dependency));
122
- }
123
- }),
304
+ onDependency
305
+ }), prepared.files, onDependency),
124
306
  dependencies
125
307
  };
126
308
  } catch (error) {
@@ -131,6 +313,17 @@ async function compileTailwindV4Source(source) {
131
313
  throw new Error("Failed to compile Tailwind CSS v4 source.");
132
314
  }
133
315
  //#endregion
316
+ //#region src/v4/source-scan/matcher.ts
317
+ function isFileMatchedByTailwindV4SourceEntries(file, entries) {
318
+ return isFileMatchedByTailwindSourceEntries(file, entries, { requirePositive: true });
319
+ }
320
+ function createTailwindV4SourceEntryMatcher(entries) {
321
+ return entries?.length ? (file) => isFileMatchedByTailwindV4SourceEntries(file, entries) : void 0;
322
+ }
323
+ function createTailwindV4SourceExclusionMatcher(entries) {
324
+ return entries?.length ? (file) => isFileExcludedByTailwindSourceEntries(file, entries) : void 0;
325
+ }
326
+ //#endregion
134
327
  //#region src/v4/source-scan/sources.ts
135
328
  const TAILWIND_V4_IGNORED_CONTENT_DIRS = [
136
329
  ".git",
@@ -175,48 +368,6 @@ function uniqueResolvedPaths(values) {
175
368
  }
176
369
  return result;
177
370
  }
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
371
  function createTailwindV4DefaultIgnoreSources(base) {
221
372
  return [
222
373
  ...TAILWIND_V4_IGNORED_CONTENT_DIRS.map((pattern) => ({
@@ -237,117 +388,32 @@ function createTailwindV4DefaultIgnoreSources(base) {
237
388
  ];
238
389
  }
239
390
  function createTailwindV4RootSources(root, fallbackBase) {
240
- if (root === "none") return [];
241
- if (root === null) return [{
391
+ return createSourceScanPlan({
242
392
  base: fallbackBase,
243
- pattern: TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN,
244
- negated: false
245
- }];
246
- return [{
247
- ...root,
248
- negated: false
249
- }];
393
+ mode: root === "none" ? "disabled" : root === null ? "auto" : "explicit",
394
+ entries: root && root !== "none" ? [{
395
+ ...root,
396
+ negated: false
397
+ }] : []
398
+ });
250
399
  }
251
400
  function createTailwindV4CompiledSourceEntries(root, sources, fallbackBase) {
252
401
  return [...createTailwindV4RootSources(root, fallbackBase), ...sources];
253
402
  }
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
- };
403
+ function resolveTailwindV4SourceEntry(sourcePath, base, negated, defaultPattern = TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN) {
404
+ return resolveTailwindSourceEntry(sourcePath, base, negated, defaultPattern);
277
405
  }
278
406
  async function normalizeTailwindV4SourceEntries(sources, options = {}) {
279
407
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
280
408
  return Promise.all(sources.map((source) => resolveTailwindV4SourceEntry(source.pattern, source.base ? path.resolve(source.base) : cwd, source.negated, options.defaultPattern)));
281
409
  }
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
410
  function normalizeTailwindV4ScannerSources(sources, cwd, ignoredSources = []) {
346
- return expandTailwindV4SourceEntryBraces([...sources?.length ? sources : [{
411
+ const baseSources = sources?.length ? sources : [{
347
412
  base: cwd,
348
413
  pattern: TAILWIND_V4_AUTO_SOURCE_SCAN_PATTERN,
349
414
  negated: false
350
- }], ...ignoredSources]);
415
+ }];
416
+ return expandSourceEntryBraces([...baseSources, ...ignoredSources]);
351
417
  }
352
418
  function resolveTailwindV4SourceBaseCandidates(projectRoot, base, baseFallbacks) {
353
419
  return uniqueResolvedPaths([
@@ -357,79 +423,6 @@ function resolveTailwindV4SourceBaseCandidates(projectRoot, base, baseFallbacks)
357
423
  ]);
358
424
  }
359
425
  //#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
426
  //#region src/extraction/oxide.ts
434
427
  let oxideImportPromise;
435
428
  function createOxideRuntimeDependencyError(cause) {
@@ -517,6 +510,94 @@ function createTokenLocation(input) {
517
510
  };
518
511
  }
519
512
  //#endregion
513
+ //#region src/extraction/candidate-extractor/project.ts
514
+ async function resolveScannerSources(options) {
515
+ const cwd = options?.cwd ? path.resolve(options.cwd) : process.cwd();
516
+ if (options?.sources?.length || options?.css === void 0) return {
517
+ cwd,
518
+ sources: normalizeTailwindV4ScannerSources(options?.sources, cwd, options?.ignoredSources)
519
+ };
520
+ const base = options.base ? path.resolve(options.base) : cwd;
521
+ const { compiled } = await compileTailwindV4Source({
522
+ projectRoot: cwd,
523
+ base,
524
+ baseFallbacks: options.baseFallbacks?.map((baseFallback) => path.resolve(baseFallback)) ?? [],
525
+ css: options.css,
526
+ dependencies: []
527
+ });
528
+ return {
529
+ cwd,
530
+ sources: normalizeTailwindV4ScannerSources(createTailwindV4CompiledSourceEntries(compiled.root, compiled.sources, base), cwd, options.ignoredSources)
531
+ };
532
+ }
533
+ async function resolveProjectSourceFiles(options) {
534
+ const { sources } = await resolveScannerSources(options);
535
+ const { Scanner } = await getOxideModule();
536
+ const files = new Scanner({ sources }).files ?? [];
537
+ return options?.filter ? files.filter(options.filter) : files;
538
+ }
539
+ async function extractProjectCandidatesWithPositions(options) {
540
+ const { cwd, sources } = await resolveScannerSources(options);
541
+ const { Scanner } = await getOxideModule();
542
+ const scanner = new Scanner({ sources });
543
+ const files = scanner.files ?? [];
544
+ const entries = [];
545
+ const skipped = [];
546
+ const batchSize = 32;
547
+ for (let start = 0; start < files.length; start += batchSize) {
548
+ const batch = files.slice(start, start + batchSize);
549
+ const contents = await Promise.allSettled(batch.map((file) => promises.readFile(file, "utf8")));
550
+ for (const [index, file] of batch.entries()) {
551
+ const result = contents[index];
552
+ if (result.status === "rejected") {
553
+ skipped.push({
554
+ file,
555
+ reason: result.reason instanceof Error ? result.reason.message : "Unknown error"
556
+ });
557
+ continue;
558
+ }
559
+ const content = result.value;
560
+ const extension = toExtension(file);
561
+ const matches = scanner.getCandidatesWithPositions({
562
+ file,
563
+ content,
564
+ extension
565
+ });
566
+ if (!matches.length) continue;
567
+ const offsets = buildLineOffsets(content);
568
+ for (const match of matches) entries.push(createTokenLocation({
569
+ cwd,
570
+ file,
571
+ content,
572
+ extension,
573
+ candidate: match.candidate,
574
+ position: match.position,
575
+ offsets
576
+ }));
577
+ }
578
+ }
579
+ return {
580
+ entries,
581
+ filesScanned: files.length,
582
+ skippedFiles: skipped,
583
+ sources
584
+ };
585
+ }
586
+ function groupTokensByFile(report, options) {
587
+ const key = options?.key ?? "relative";
588
+ const stripAbsolute = options?.stripAbsolutePaths ?? key !== "absolute";
589
+ return report.entries.reduce((acc, entry) => {
590
+ const bucketKey = key === "absolute" ? entry.file : entry.relativeFile;
591
+ const bucket = acc[bucketKey] ?? (acc[bucketKey] = []);
592
+ const value = stripAbsolute ? {
593
+ ...entry,
594
+ file: entry.relativeFile
595
+ } : entry;
596
+ bucket.push(value);
597
+ return acc;
598
+ }, {});
599
+ }
600
+ //#endregion
520
601
  //#region src/v4/bare-arbitrary-values/escape.ts
521
602
  function escapeCssClassName(value) {
522
603
  let result = "";
@@ -3533,6 +3614,7 @@ function createTailwindGenerationArtifact(css, source, request, classSet, rawCan
3533
3614
  //#region src/v4/generation-request.ts
3534
3615
  function toGenerationRequest(options) {
3535
3616
  return {
3617
+ ...options?.excludeFiles === void 0 ? {} : { excludeFiles: options.excludeFiles },
3536
3618
  ...options?.candidates === void 0 ? {} : { candidates: options.candidates },
3537
3619
  ...options?.sources === void 0 ? {} : { sourceEntries: options.sources.map((sourceEntry, index) => ({
3538
3620
  id: `source:${index}`,
@@ -3544,6 +3626,7 @@ function toGenerationRequest(options) {
3544
3626
  }
3545
3627
  function toGenerateOptions(request) {
3546
3628
  return {
3629
+ ...request.excludeFiles === void 0 ? {} : { excludeFiles: request.excludeFiles },
3547
3630
  ...request.candidates === void 0 ? {} : { candidates: request.candidates },
3548
3631
  ...request.sourceEntries === void 0 ? {} : { sources: request.sourceEntries },
3549
3632
  ...request.bareArbitraryValues === void 0 ? {} : { bareArbitraryValues: request.bareArbitraryValues },
@@ -3582,7 +3665,7 @@ function stripCompiledSourceEntries(source) {
3582
3665
  return source;
3583
3666
  }
3584
3667
  }
3585
- async function collectRawCandidates(source, options, compiledRoot, compiledSources = []) {
3668
+ async function collectRawCandidates(source, options, compiledRoot, compiledSources = [], onFiles) {
3586
3669
  const rawCandidates = /* @__PURE__ */ new Set();
3587
3670
  const extractOptions = options?.bareArbitraryValues === void 0 ? void 0 : { bareArbitraryValues: options.bareArbitraryValues };
3588
3671
  for (const candidate of options?.candidates ?? []) rawCandidates.add(candidate);
@@ -3591,7 +3674,25 @@ async function collectRawCandidates(source, options, compiledRoot, compiledSourc
3591
3674
  for (const candidate of candidates) rawCandidates.add(candidate.rawCandidate);
3592
3675
  }
3593
3676
  const filesystemSources = resolveScanSources(options, source, compiledRoot, compiledSources);
3594
- if (filesystemSources.length > 0) for (const candidate of await extractRawCandidates(filesystemSources, extractOptions)) rawCandidates.add(candidate);
3677
+ if (filesystemSources.some((entry) => !entry.negated)) {
3678
+ const excluded = new Set((options?.excludeFiles ?? []).map(resolveSourceScanPath));
3679
+ const files = await resolveProjectSourceFiles({
3680
+ sources: filesystemSources,
3681
+ filter: (file) => !excluded.has(resolveSourceScanPath(file))
3682
+ });
3683
+ onFiles?.(files);
3684
+ for (const file of files) {
3685
+ let content;
3686
+ try {
3687
+ content = await readFile(file, "utf8");
3688
+ } catch (error) {
3689
+ const code = error?.code;
3690
+ if (code === "ENOENT" || code === "ENOTDIR") continue;
3691
+ throw error;
3692
+ }
3693
+ for (const candidate of await extractRawCandidatesWithPositions(content, path.extname(file).slice(1), extractOptions)) rawCandidates.add(candidate.rawCandidate);
3694
+ }
3695
+ }
3595
3696
  const inlineSources = extractTailwindV4InlineSourceCandidates(source.css);
3596
3697
  for (const candidate of inlineSources.included) rawCandidates.add(candidate);
3597
3698
  for (const candidate of inlineSources.excluded) rawCandidates.delete(candidate);
@@ -3604,6 +3705,7 @@ var TailwindGenerationSessionImpl = class {
3604
3705
  runtimes = /* @__PURE__ */ new Map();
3605
3706
  designSystemPromise;
3606
3707
  disposed = false;
3708
+ moduleDependencies = /* @__PURE__ */ new Set();
3607
3709
  constructor(source) {
3608
3710
  this.currentSource = source;
3609
3711
  }
@@ -3636,10 +3738,15 @@ var TailwindGenerationSessionImpl = class {
3636
3738
  invalidate(change) {
3637
3739
  if (this.disposed) return;
3638
3740
  if (change.type === "source") this.currentSource = change.source;
3741
+ clearRequireCache([...this.moduleDependencies]);
3742
+ invalidateGenerationModuleCache();
3743
+ this.moduleDependencies.clear();
3639
3744
  this.runtimes.clear();
3640
3745
  this.designSystemPromise = void 0;
3641
3746
  }
3642
3747
  dispose() {
3748
+ clearRequireCache([...this.moduleDependencies]);
3749
+ this.moduleDependencies.clear();
3643
3750
  this.runtimes.clear();
3644
3751
  this.designSystemPromise = void 0;
3645
3752
  this.disposed = true;
@@ -3647,12 +3754,12 @@ var TailwindGenerationSessionImpl = class {
3647
3754
  assertActive() {
3648
3755
  if (this.disposed) throw new Error("Tailwind generation session has been disposed.");
3649
3756
  }
3650
- getRuntime(compileSourceEntries) {
3757
+ getRuntime(compileSourceEntries, designSystem) {
3651
3758
  this.assertActive();
3652
3759
  const cached = this.runtimes.get(compileSourceEntries);
3653
3760
  if (cached) return cached;
3654
3761
  const source = compileSourceEntries ? this.currentSource : stripCompiledSourceEntries(this.currentSource);
3655
- const promise = Promise.all([compileTailwindV4Source(source), loadTailwindV4DesignSystem(source, { cache: false })]).then(([compiledSource, designSystem]) => ({
3762
+ const promise = Promise.all([compileTailwindV4Source(source), designSystem ?? loadTailwindV4DesignSystem(source, { cache: false })]).then(([compiledSource, designSystem]) => ({
3656
3763
  compiled: compiledSource.compiled,
3657
3764
  dependencies: compiledSource.dependencies,
3658
3765
  designSystem,
@@ -3664,28 +3771,32 @@ var TailwindGenerationSessionImpl = class {
3664
3771
  });
3665
3772
  return promise;
3666
3773
  }
3667
- async resetRuntime(compileSourceEntries) {
3774
+ async resetRuntime(compileSourceEntries, designSystem) {
3668
3775
  this.runtimes.delete(compileSourceEntries);
3669
- return this.getRuntime(compileSourceEntries);
3776
+ return this.getRuntime(compileSourceEntries, designSystem);
3670
3777
  }
3671
3778
  async generateInternal(request) {
3672
3779
  this.assertActive();
3673
3780
  const options = toGenerateOptions(request);
3674
3781
  const compileSourceEntries = shouldCompileSourceEntries(options);
3675
3782
  let runtime = await this.getRuntime(compileSourceEntries);
3676
- const rawCandidates = await collectRawCandidates(this.currentSource, options, runtime.compiled.root, runtime.compiled.sources);
3783
+ const sourceFiles = [];
3784
+ let rawCandidates = await collectRawCandidates(this.currentSource, options, runtime.compiled.root, runtime.compiled.sources, (files) => sourceFiles.push(...files));
3785
+ if (request.prepareCandidates) rawCandidates = new Set(request.prepareCandidates(rawCandidates));
3677
3786
  const classSet = resolveValidTailwindV4Candidates(runtime.designSystem, rawCandidates, { ...options.bareArbitraryValues === void 0 ? {} : { bareArbitraryValues: options.bareArbitraryValues } });
3678
3787
  const inlineSources = extractTailwindV4InlineSourceCandidates(this.currentSource.css);
3679
3788
  for (const candidate of inlineSources.excluded) classSet.delete(candidate);
3680
3789
  const buildCandidates = new Set(canonicalizeBareArbitraryValueCandidates(classSet, options.bareArbitraryValues));
3681
- if ([...runtime.builtCandidates].some((candidate) => !buildCandidates.has(candidate))) runtime = await this.resetRuntime(compileSourceEntries);
3790
+ if ([...runtime.builtCandidates].some((candidate) => !buildCandidates.has(candidate))) runtime = await this.resetRuntime(compileSourceEntries, runtime.designSystem);
3682
3791
  const css = replaceBareArbitraryValueSelectors(runtime.compiled.build([...buildCandidates]), classSet, options.bareArbitraryValues);
3683
3792
  runtime.builtCandidates = new Set(buildCandidates);
3793
+ for (const dependency of runtime.dependencies) this.moduleDependencies.add(dependency);
3794
+ const dependencies = [.../* @__PURE__ */ new Set([...runtime.dependencies, ...sourceFiles])];
3684
3795
  return {
3685
3796
  css,
3686
3797
  classSet,
3687
3798
  rawCandidates,
3688
- dependencies: Array.from(runtime.dependencies),
3799
+ dependencies,
3689
3800
  sources: runtime.compiled.sources,
3690
3801
  root: runtime.compiled.root
3691
3802
  };
@@ -3703,6 +3814,9 @@ function createTailwindV4Engine(source) {
3703
3814
  const session = createTailwindV4EngineGenerationSession(source);
3704
3815
  return {
3705
3816
  source,
3817
+ dispose() {
3818
+ session.dispose();
3819
+ },
3706
3820
  loadDesignSystem() {
3707
3821
  return session.loadDesignSystem();
3708
3822
  },
@@ -3859,16 +3973,21 @@ async function collectTailwindV4StyleCandidates(options) {
3859
3973
  async function generateTailwindV4Style(options = {}) {
3860
3974
  const source = options.source ?? await resolveTailwindV4Source(createSourceOptions(options));
3861
3975
  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
- };
3976
+ const engine = createTailwindV4Engine(source);
3977
+ try {
3978
+ const result = await engine.generate({
3979
+ candidates,
3980
+ ...options.bareArbitraryValues === void 0 ? {} : { bareArbitraryValues: options.bareArbitraryValues },
3981
+ ...options.scanSources === void 0 ? {} : { scanSources: options.scanSources }
3982
+ });
3983
+ return {
3984
+ ...result,
3985
+ tokens: result.rawCandidates,
3986
+ source
3987
+ };
3988
+ } finally {
3989
+ engine.dispose?.();
3990
+ }
3872
3991
  }
3873
3992
  //#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 };
3993
+ 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 };