tw-migrate 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -48
- package/dist/bin.js +15 -7
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1662 -566
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { promisify } from "node:util";
|
|
|
3
3
|
import { chmod, lstat, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
|
-
import { parse } from "parse5";
|
|
7
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { parse } from "parse5";
|
|
8
8
|
//#region src/util/diff.ts
|
|
9
9
|
function unifiedDiff(path, before, after) {
|
|
10
10
|
const oldLines = before.split("\n");
|
|
@@ -18,186 +18,10 @@ function unifiedDiff(path, before, after) {
|
|
|
18
18
|
].join("");
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
21
|
-
//#region src/parser/html.ts
|
|
22
|
-
const RECOVERABLE_PARSE_ERRORS = /* @__PURE__ */ new Set(["missing-doctype"]);
|
|
23
|
-
const TEMPLATE_MARKERS = /\{\{|\}\}|\$\{|<%|%>|<#|#>|\[%|%\]/;
|
|
24
|
-
function parseHtmlSource(path, source) {
|
|
25
|
-
const errors = [];
|
|
26
|
-
const document = parse(source, {
|
|
27
|
-
sourceCodeLocationInfo: true,
|
|
28
|
-
onParseError(error) {
|
|
29
|
-
if (!RECOVERABLE_PARSE_ERRORS.has(error.code)) errors.push(error);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
if (errors.length > 0) throw new Error(`Failed to parse ${path}: ${errors.map((error) => error.code).join(", ")}`);
|
|
33
|
-
const links = [];
|
|
34
|
-
const bases = [];
|
|
35
|
-
const elements = [];
|
|
36
|
-
const dynamicAttributes = [];
|
|
37
|
-
const scriptTexts = [];
|
|
38
|
-
const unquoted = (attribute) => source[attribute.start - 1] !== "\"" && source[attribute.start - 1] !== "'";
|
|
39
|
-
function visit(node) {
|
|
40
|
-
if (node.tagName === "script") {
|
|
41
|
-
for (const child of node.childNodes ?? []) if (child.nodeName === "#text" && child.value) scriptTexts.push(child.value);
|
|
42
|
-
}
|
|
43
|
-
if (node.tagName) {
|
|
44
|
-
const attributes = new Map((node.attrs ?? []).map((attribute) => [attribute.name, attribute.value]));
|
|
45
|
-
const locations = node.sourceCodeLocation?.attrs;
|
|
46
|
-
if (locations) {
|
|
47
|
-
if (node.tagName === "link" && stylesheetRel(attributes.get("rel")) && !attributes.has("disabled")) {
|
|
48
|
-
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
49
|
-
const media = locatedAttribute(source, locations.media, attributes.get("media"));
|
|
50
|
-
const tag = node.sourceCodeLocation?.startTag ?? node.sourceCodeLocation;
|
|
51
|
-
if (href && tag) links.push({
|
|
52
|
-
href: href.value,
|
|
53
|
-
media: media?.value ?? "",
|
|
54
|
-
start: href.start,
|
|
55
|
-
end: href.end,
|
|
56
|
-
tagStart: tag.startOffset,
|
|
57
|
-
tagEnd: tag.endOffset
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
if (node.tagName === "base" && bases.length === 0) {
|
|
61
|
-
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
62
|
-
if (href) bases.push({
|
|
63
|
-
href: href.value,
|
|
64
|
-
writable: href.writable,
|
|
65
|
-
start: href.start,
|
|
66
|
-
end: href.end
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
let classAttribute = locatedAttribute(source, locations.class, attributes.get("class"));
|
|
70
|
-
const idAttribute = locatedAttribute(source, locations.id, attributes.get("id"));
|
|
71
|
-
const dynamic = (!classAttribute && idAttribute && locations.class ? {
|
|
72
|
-
start: locations.class.startOffset,
|
|
73
|
-
end: locations.class.endOffset
|
|
74
|
-
} : void 0) ?? [classAttribute, idAttribute].find((attribute) => attribute && (!attribute.writable || TEMPLATE_MARKERS.test(attribute.value))) ?? (classAttribute && unquoted(classAttribute) ? classAttribute : void 0);
|
|
75
|
-
if (dynamic) dynamicAttributes.push({
|
|
76
|
-
start: dynamic.start,
|
|
77
|
-
end: dynamic.end
|
|
78
|
-
});
|
|
79
|
-
else if (classAttribute || idAttribute) {
|
|
80
|
-
if (!classAttribute && idAttribute) {
|
|
81
|
-
const insertion = classInsertionOffset$1(source, node.sourceCodeLocation?.startTag);
|
|
82
|
-
if (insertion !== void 0) classAttribute = {
|
|
83
|
-
value: "",
|
|
84
|
-
start: insertion,
|
|
85
|
-
end: insertion,
|
|
86
|
-
synthetic: true
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
elements.push({
|
|
90
|
-
classAttribute,
|
|
91
|
-
idAttribute
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
for (const child of node.childNodes ?? []) visit(child);
|
|
97
|
-
if (node.content) visit(node.content);
|
|
98
|
-
}
|
|
99
|
-
visit(document);
|
|
100
|
-
return {
|
|
101
|
-
...toByteOffsets(source, {
|
|
102
|
-
links,
|
|
103
|
-
bases,
|
|
104
|
-
elements,
|
|
105
|
-
dynamicAttributes
|
|
106
|
-
}),
|
|
107
|
-
scriptText: scriptTexts.join("\n")
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function utf8OffsetMap(source, indices) {
|
|
111
|
-
const sorted = [...new Set(indices)].sort((left, right) => left - right);
|
|
112
|
-
const map = /* @__PURE__ */ new Map();
|
|
113
|
-
let lastIndex = 0;
|
|
114
|
-
let bytes = 0;
|
|
115
|
-
for (const index of sorted) {
|
|
116
|
-
bytes += Buffer.byteLength(source.slice(lastIndex, index));
|
|
117
|
-
lastIndex = index;
|
|
118
|
-
map.set(index, bytes);
|
|
119
|
-
}
|
|
120
|
-
return map;
|
|
121
|
-
}
|
|
122
|
-
function offsetLookup(offsets) {
|
|
123
|
-
return (index) => {
|
|
124
|
-
const byte = offsets.get(index);
|
|
125
|
-
if (byte === void 0) throw new Error(`No byte offset was mapped for index ${index}`);
|
|
126
|
-
return byte;
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function toByteOffsets(source, parsed) {
|
|
130
|
-
const offset = offsetLookup(utf8OffsetMap(source, [
|
|
131
|
-
...parsed.links.flatMap((link) => [
|
|
132
|
-
link.start,
|
|
133
|
-
link.end,
|
|
134
|
-
link.tagStart,
|
|
135
|
-
link.tagEnd
|
|
136
|
-
]),
|
|
137
|
-
...parsed.bases.flatMap((base) => [base.start, base.end]),
|
|
138
|
-
...parsed.elements.flatMap((element) => [element.classAttribute, element.idAttribute].flatMap((value) => value ? [value.start, value.end] : [])),
|
|
139
|
-
...parsed.dynamicAttributes.flatMap((value) => [value.start, value.end])
|
|
140
|
-
]));
|
|
141
|
-
const attribute = (value) => ({
|
|
142
|
-
...value,
|
|
143
|
-
start: offset(value.start),
|
|
144
|
-
end: offset(value.end)
|
|
145
|
-
});
|
|
146
|
-
return {
|
|
147
|
-
links: parsed.links.map((link) => ({
|
|
148
|
-
...link,
|
|
149
|
-
start: offset(link.start),
|
|
150
|
-
end: offset(link.end),
|
|
151
|
-
tagStart: offset(link.tagStart),
|
|
152
|
-
tagEnd: offset(link.tagEnd)
|
|
153
|
-
})),
|
|
154
|
-
bases: parsed.bases.map((base) => attribute(base)),
|
|
155
|
-
elements: parsed.elements.map((element) => ({
|
|
156
|
-
classAttribute: element.classAttribute && attribute(element.classAttribute),
|
|
157
|
-
idAttribute: element.idAttribute && attribute(element.idAttribute)
|
|
158
|
-
})),
|
|
159
|
-
dynamicAttributes: parsed.dynamicAttributes.map((value) => attribute(value))
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function classInsertionOffset$1(source, startTag) {
|
|
163
|
-
if (!startTag) return void 0;
|
|
164
|
-
let offset = startTag.endOffset - 1;
|
|
165
|
-
if (source[offset] !== ">") return void 0;
|
|
166
|
-
while (offset > startTag.startOffset && /\s/.test(source[offset - 1] ?? "")) offset -= 1;
|
|
167
|
-
if (source[offset - 1] === "/") offset -= 1;
|
|
168
|
-
return offset;
|
|
169
|
-
}
|
|
170
|
-
function stylesheetRel(value = "") {
|
|
171
|
-
const tokens = value.toLowerCase().split(/\s+/);
|
|
172
|
-
return tokens.includes("stylesheet") && !tokens.includes("alternate");
|
|
173
|
-
}
|
|
174
|
-
function locatedAttribute(source, location, parsedValue) {
|
|
175
|
-
if (!location || parsedValue === void 0) return void 0;
|
|
176
|
-
const raw = source.slice(location.startOffset, location.endOffset);
|
|
177
|
-
const equals = raw.indexOf("=");
|
|
178
|
-
if (equals < 0) return void 0;
|
|
179
|
-
let start = equals + 1;
|
|
180
|
-
while (/\s/.test(raw[start] ?? "")) start += 1;
|
|
181
|
-
const quote = raw[start];
|
|
182
|
-
let end;
|
|
183
|
-
if (quote === "\"" || quote === "'") {
|
|
184
|
-
start += 1;
|
|
185
|
-
end = raw.lastIndexOf(quote);
|
|
186
|
-
if (end < start) return void 0;
|
|
187
|
-
} else {
|
|
188
|
-
end = start;
|
|
189
|
-
while (end < raw.length && !/\s/.test(raw[end] ?? "")) end += 1;
|
|
190
|
-
}
|
|
191
|
-
const value = raw.slice(start, end);
|
|
192
|
-
return {
|
|
193
|
-
value: value.includes("&") ? parsedValue : value,
|
|
194
|
-
writable: !value.includes("&"),
|
|
195
|
-
start: location.startOffset + start,
|
|
196
|
-
end: location.startOffset + end
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
//#endregion
|
|
200
21
|
//#region src/native.ts
|
|
22
|
+
function errorCode(error) {
|
|
23
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
24
|
+
}
|
|
201
25
|
const require = createRequire(import.meta.url);
|
|
202
26
|
const target = {
|
|
203
27
|
"darwin-arm64": "darwin-arm64",
|
|
@@ -219,7 +43,75 @@ try {
|
|
|
219
43
|
throw new Error(`No tw-migrate native addon was found for ${target}. Reinstall the package or build it locally.`);
|
|
220
44
|
}
|
|
221
45
|
}
|
|
222
|
-
const {
|
|
46
|
+
const { collectMediaConditions, mediaProbeKey, decodeSourceMap, planBatchMigration, validateCss } = binding;
|
|
47
|
+
function object(value) {
|
|
48
|
+
return value !== null && typeof value === "object";
|
|
49
|
+
}
|
|
50
|
+
function decode(label, json, valid) {
|
|
51
|
+
const value = JSON.parse(json);
|
|
52
|
+
if (!valid(value)) throw new Error(`Invalid native ${label} result`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function cssDirectives$1(source) {
|
|
56
|
+
try {
|
|
57
|
+
const value = JSON.parse(binding.collectCssDirectives(source));
|
|
58
|
+
return Array.isArray(value) ? value : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function sourceImport(value) {
|
|
64
|
+
return object(value) && typeof value.specifier === "string" && typeof value.typeOnly === "boolean" && typeof value.dynamic === "boolean";
|
|
65
|
+
}
|
|
66
|
+
function sourceAnalysisResult(value) {
|
|
67
|
+
return object(value) && Array.isArray(value.imports) && value.imports.every(sourceImport) && Array.isArray(value.staticImports) && value.staticImports.every((item) => typeof item === "string") && Array.isArray(value.defaultImports) && value.defaultImports.every((item) => object(item) && typeof item.source === "string" && typeof item.local === "string") && Array.isArray(value.vueGlobPatterns) && value.vueGlobPatterns.every((item) => typeof item === "string") && Array.isArray(value.useCssModuleLocals) && value.useCssModuleLocals.every((item) => typeof item === "string") && Array.isArray(value.unboundReferences) && value.unboundReferences.every((item) => typeof item === "string") && [
|
|
68
|
+
value.vueGlobUnverifiable,
|
|
69
|
+
value.hasDynamicImport,
|
|
70
|
+
value.hasVueFallthroughMacro,
|
|
71
|
+
value.usesCssModule,
|
|
72
|
+
value.hasUnboundUseCssModule,
|
|
73
|
+
value.definesRootUseCssModule
|
|
74
|
+
].every((item) => typeof item === "boolean");
|
|
75
|
+
}
|
|
76
|
+
const ANALYSIS_CACHE_LIMIT = 4096;
|
|
77
|
+
const sourceAnalysisCache = /* @__PURE__ */ new Map();
|
|
78
|
+
const stylesheetAnalysisCache = /* @__PURE__ */ new Map();
|
|
79
|
+
function bound(cache) {
|
|
80
|
+
while (cache.size > ANALYSIS_CACHE_LIMIT) {
|
|
81
|
+
const oldest = cache.keys().next().value;
|
|
82
|
+
if (oldest === void 0) break;
|
|
83
|
+
cache.delete(oldest);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function expressionAnalysis(path, source) {
|
|
87
|
+
return decode("expression analysis", binding.expressionAnalysis(path, source), (value) => object(value) && (typeof value.staticString === "string" || value.staticString === null) && (typeof value.vueModuleMember === "string" || value.vueModuleMember === null) && typeof value.usesCssModule === "boolean" && typeof value.referencesUseCssModule === "boolean" && Array.isArray(value.references) && value.references.every((item) => typeof item === "string"));
|
|
88
|
+
}
|
|
89
|
+
function sourceAnalysis(path, source) {
|
|
90
|
+
const cached = sourceAnalysisCache.get(path);
|
|
91
|
+
if (cached?.source === source) return cached.analysis;
|
|
92
|
+
const analysis = decode("source analysis", binding.sourceAnalysis(path, source), sourceAnalysisResult);
|
|
93
|
+
sourceAnalysisCache.set(path, {
|
|
94
|
+
source,
|
|
95
|
+
analysis
|
|
96
|
+
});
|
|
97
|
+
bound(sourceAnalysisCache);
|
|
98
|
+
return analysis;
|
|
99
|
+
}
|
|
100
|
+
function stylesheetAnalysis(path, source) {
|
|
101
|
+
const cached = stylesheetAnalysisCache.get(path);
|
|
102
|
+
if (cached?.source === source) return cached.analysis;
|
|
103
|
+
const analysis = decode("stylesheet analysis", binding.stylesheetAnalysis(path, source), (value) => object(value) && Array.isArray(value.references) && value.references.every((item) => typeof item === "string") && Array.isArray(value.imports) && value.imports.every((item) => object(item) && typeof item.href === "string" && typeof item.media === "string" && typeof item.start === "number" && typeof item.end === "number") && [
|
|
104
|
+
value.unverifiable,
|
|
105
|
+
value.scopeEscapesUnverifiable,
|
|
106
|
+
value.selectorsUnverifiable
|
|
107
|
+
].every((item) => typeof item === "boolean") && Array.isArray(value.scopeEscapes) && value.scopeEscapes.every((item) => typeof item === "string") && Array.isArray(value.scopeShadowCss) && value.scopeShadowCss.every((item) => typeof item === "string") && object(value.themeTokens) && Object.values(value.themeTokens).every((item) => typeof item === "string") && Array.isArray(value.globalAtRuleIdentities) && value.globalAtRuleIdentities.every((item) => typeof item === "string") && typeof value.globalAtRulesUnverifiable === "boolean");
|
|
108
|
+
stylesheetAnalysisCache.set(path, {
|
|
109
|
+
source,
|
|
110
|
+
analysis
|
|
111
|
+
});
|
|
112
|
+
bound(stylesheetAnalysisCache);
|
|
113
|
+
return analysis;
|
|
114
|
+
}
|
|
223
115
|
//#endregion
|
|
224
116
|
//#region src/parser/style-compiler.ts
|
|
225
117
|
const SASS_EXTENSIONS = /* @__PURE__ */ new Set([".scss", ".sass"]);
|
|
@@ -240,11 +132,13 @@ async function loadProjectModule(packageRoot, name, errorMessage) {
|
|
|
240
132
|
const imported = await import(pathToFileURL(modulePath).href);
|
|
241
133
|
return imported.default ?? imported;
|
|
242
134
|
}
|
|
135
|
+
const missingCompilerMessage = (name) => `${name} must be installed in the target project.`;
|
|
136
|
+
const MISSING_STYLE_COMPILER_MESSAGES = /* @__PURE__ */ new Set([missingCompilerMessage("Sass"), missingCompilerMessage("Less")]);
|
|
243
137
|
function loadProjectSass(packageRoot) {
|
|
244
|
-
return loadProjectModule(packageRoot, "sass", "Sass
|
|
138
|
+
return loadProjectModule(packageRoot, "sass", missingCompilerMessage("Sass"));
|
|
245
139
|
}
|
|
246
140
|
function loadProjectLess(packageRoot) {
|
|
247
|
-
return loadProjectModule(packageRoot, "less", "Less
|
|
141
|
+
return loadProjectModule(packageRoot, "less", missingCompilerMessage("Less"));
|
|
248
142
|
}
|
|
249
143
|
async function compileStyleEntry(compilers, packageRoot, entryPath, source, options = {}) {
|
|
250
144
|
if (isSassPath(entryPath)) return compileSassEntry(compilers.sass ??= await loadProjectSass(packageRoot), entryPath, source, options);
|
|
@@ -353,8 +247,8 @@ const IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
353
247
|
"node_modules"
|
|
354
248
|
]);
|
|
355
249
|
const RECOVERABLE_INPUT_ERROR = "TW_MIGRATE_RECOVERABLE_INPUT:";
|
|
356
|
-
function
|
|
357
|
-
return error instanceof Error
|
|
250
|
+
function errorMessage(error) {
|
|
251
|
+
return error instanceof Error ? error.message : String(error);
|
|
358
252
|
}
|
|
359
253
|
function isWithin(root, path) {
|
|
360
254
|
return path === root || path.startsWith(`${root}${sep}`);
|
|
@@ -369,15 +263,6 @@ function isStylesheetModule(path) {
|
|
|
369
263
|
const syntax = stylesheetSyntax(path);
|
|
370
264
|
return syntax !== void 0 && path.endsWith(`.module.${syntax}`);
|
|
371
265
|
}
|
|
372
|
-
function sourceReferencesStyle(file, stylePath) {
|
|
373
|
-
let importPath = normalizedRelativePath(dirname(file.path), stylePath);
|
|
374
|
-
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
375
|
-
return [
|
|
376
|
-
`'${importPath}'`,
|
|
377
|
-
`"${importPath}"`,
|
|
378
|
-
`\`${importPath}\``
|
|
379
|
-
].some((literal) => file.source.includes(literal));
|
|
380
|
-
}
|
|
381
266
|
function isProjectInput(workspaceRoot, path) {
|
|
382
267
|
return isWithin(workspaceRoot, path) && !relative(workspaceRoot, path).split(/[\\/]/).includes("node_modules");
|
|
383
268
|
}
|
|
@@ -404,43 +289,44 @@ function recordSnapshot(snapshots, path, source) {
|
|
|
404
289
|
return source;
|
|
405
290
|
}
|
|
406
291
|
function packageFailure(workspaceRoot, packageRoot, error) {
|
|
407
|
-
const message =
|
|
292
|
+
const message = errorMessage(error);
|
|
408
293
|
return {
|
|
409
294
|
package: normalizedRelativePath(workspaceRoot, packageRoot) || ".",
|
|
410
295
|
message: message.startsWith("TW_MIGRATE_RECOVERABLE_INPUT:") ? message.slice(29) : message
|
|
411
296
|
};
|
|
412
297
|
}
|
|
413
298
|
function isRecoverablePlanningError(error) {
|
|
414
|
-
return (error
|
|
299
|
+
return errorMessage(error).startsWith(RECOVERABLE_INPUT_ERROR);
|
|
415
300
|
}
|
|
416
301
|
function isMissingStyleCompilerError(error) {
|
|
417
|
-
|
|
418
|
-
return /^(?:Sass|Less) must be installed in the target project\.$/.test(message);
|
|
302
|
+
return MISSING_STYLE_COMPILER_MESSAGES.has(errorMessage(error));
|
|
419
303
|
}
|
|
420
304
|
function isIntegrityError(error) {
|
|
421
|
-
return (error
|
|
305
|
+
return errorMessage(error).startsWith("Source changed during planning:");
|
|
422
306
|
}
|
|
423
307
|
function normalizedRelativePath(root, path) {
|
|
424
308
|
return relative(root, path).split(sep).join("/");
|
|
425
309
|
}
|
|
426
|
-
function maskCssComments(source) {
|
|
427
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, (comment) => comment.replace(/[^\r\n]/g, " "));
|
|
428
|
-
}
|
|
429
310
|
function indexStylesheetDependents(styleSources) {
|
|
430
311
|
const dependents = /* @__PURE__ */ new Map();
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
312
|
+
const allPossibleTargets = [...styleSources.keys()].filter((path) => isStylesheetModule(path) || isPreprocessorPath(path));
|
|
313
|
+
for (const [path, source] of styleSources) {
|
|
314
|
+
let references;
|
|
315
|
+
try {
|
|
316
|
+
const analysis = stylesheetAnalysis(path, source);
|
|
317
|
+
references = analysis.references;
|
|
318
|
+
if (analysis.unverifiable) references = [...references, ...allPossibleTargets];
|
|
319
|
+
} catch {
|
|
320
|
+
references = allPossibleTargets;
|
|
321
|
+
}
|
|
322
|
+
for (const reference of new Set(references)) {
|
|
323
|
+
const targets = styleSources.has(reference) ? [reference] : stylesheetReferenceTargets(path, reference, styleSources);
|
|
324
|
+
for (const target of targets) {
|
|
325
|
+
if (target === path || !isStylesheetModule(target) && !isPreprocessorPath(target)) continue;
|
|
326
|
+
const paths = dependents.get(target) ?? [];
|
|
327
|
+
paths.push(path);
|
|
328
|
+
dependents.set(target, paths);
|
|
329
|
+
}
|
|
444
330
|
}
|
|
445
331
|
}
|
|
446
332
|
for (const [target, paths] of dependents) dependents.set(target, [...new Set(paths)].sort());
|
|
@@ -462,63 +348,8 @@ function stylesheetReferenceTargets(importer, reference, styleSources) {
|
|
|
462
348
|
join(target, `index${syntax}`)
|
|
463
349
|
])).filter((path) => styleSources.has(path));
|
|
464
350
|
}
|
|
465
|
-
function cssImports(source) {
|
|
466
|
-
|
|
467
|
-
const masked = maskCssComments(source);
|
|
468
|
-
let depth = 0;
|
|
469
|
-
let quote;
|
|
470
|
-
let importsAllowed = true;
|
|
471
|
-
for (let index = 0; index < masked.length; index += 1) {
|
|
472
|
-
const character = masked[index];
|
|
473
|
-
if (quote) {
|
|
474
|
-
if (character === "\\") index += 1;
|
|
475
|
-
else if (character === quote) quote = void 0;
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
if (character === "\"" || character === "'") {
|
|
479
|
-
quote = character;
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
if (character === "{") {
|
|
483
|
-
depth += 1;
|
|
484
|
-
importsAllowed = false;
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
if (character === "}") {
|
|
488
|
-
depth = Math.max(0, depth - 1);
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
if (!importsAllowed || depth !== 0 || masked.slice(index, index + 7).toLowerCase() !== "@import" || /[-\w]/.test(masked[index + 7] ?? "")) continue;
|
|
492
|
-
let end = index + 7;
|
|
493
|
-
let importQuote;
|
|
494
|
-
let parentheses = 0;
|
|
495
|
-
for (; end < masked.length; end += 1) {
|
|
496
|
-
const next = masked[end];
|
|
497
|
-
if (importQuote) {
|
|
498
|
-
if (next === "\\") end += 1;
|
|
499
|
-
else if (next === importQuote) importQuote = void 0;
|
|
500
|
-
} else if (next === "\"" || next === "'") importQuote = next;
|
|
501
|
-
else if (next === "(") parentheses += 1;
|
|
502
|
-
else if (next === ")") parentheses = Math.max(0, parentheses - 1);
|
|
503
|
-
else if (next === ";" && parentheses === 0) break;
|
|
504
|
-
}
|
|
505
|
-
if (end >= masked.length) continue;
|
|
506
|
-
const statement = masked.slice(index, end + 1);
|
|
507
|
-
const match = /^@import\s+(?:url\(\s*)?(?:["']([^"']+)["']|([^"'()\s;]+))\s*\)?\s*([^;]*);$/i.exec(statement);
|
|
508
|
-
if (match) imports.push({
|
|
509
|
-
href: match[1] ?? match[2],
|
|
510
|
-
media: match[3].trim(),
|
|
511
|
-
start: index,
|
|
512
|
-
end: end + 1
|
|
513
|
-
});
|
|
514
|
-
index = end;
|
|
515
|
-
}
|
|
516
|
-
const offsets = utf8OffsetMap(source, imports.flatMap((imported) => [imported.start, imported.end]));
|
|
517
|
-
return imports.map((imported) => ({
|
|
518
|
-
...imported,
|
|
519
|
-
start: offsets.get(imported.start) ?? imported.start,
|
|
520
|
-
end: offsets.get(imported.end) ?? imported.end
|
|
521
|
-
}));
|
|
351
|
+
function cssImports(path, source) {
|
|
352
|
+
return stylesheetAnalysis(path, source).imports;
|
|
522
353
|
}
|
|
523
354
|
//#endregion
|
|
524
355
|
//#region src/discovery.ts
|
|
@@ -528,7 +359,7 @@ async function resolveScope(options) {
|
|
|
528
359
|
const currentPackage = await findPackageRoot(cwd);
|
|
529
360
|
const gitRoot = await findGitRoot(currentPackage);
|
|
530
361
|
const workspaceRoot = gitRoot && !await isIgnoredByGit(gitRoot, currentPackage) ? gitRoot : currentPackage;
|
|
531
|
-
const allPaths = await
|
|
362
|
+
const allPaths = workspaceRoot === gitRoot ? await discoverGitFiles(workspaceRoot) : await collectFiles(workspaceRoot, isRelevantDiscoveredFile);
|
|
532
363
|
const scannedPaths = workspaceRoot === gitRoot ? [.../* @__PURE__ */ new Set([...allPaths, ...await collectFiles(workspaceRoot, isRelevantDiscoveredFile)])] : [...allPaths];
|
|
533
364
|
const explicitStyle = options.styleFile ? resolve(cwd, options.styleFile) : void 0;
|
|
534
365
|
const configuredEntry = options.tailwindCss ? resolve(cwd, options.tailwindCss) : void 0;
|
|
@@ -589,6 +420,28 @@ async function findGitRoot(cwd) {
|
|
|
589
420
|
return;
|
|
590
421
|
}
|
|
591
422
|
}
|
|
423
|
+
async function scannerIgnoredPaths(workspaceRoot, paths) {
|
|
424
|
+
if (paths.length === 0 || await findGitRoot(workspaceRoot) !== workspaceRoot) return /* @__PURE__ */ new Set();
|
|
425
|
+
return new Promise((resolvePromise) => {
|
|
426
|
+
execFile("git", [
|
|
427
|
+
"check-ignore",
|
|
428
|
+
"--no-index",
|
|
429
|
+
"-z",
|
|
430
|
+
"--stdin"
|
|
431
|
+
], {
|
|
432
|
+
cwd: workspaceRoot,
|
|
433
|
+
maxBuffer: 67108864
|
|
434
|
+
}, (error, stdout) => {
|
|
435
|
+
if (error !== null && error.code !== 1) {
|
|
436
|
+
resolvePromise(new Set(paths));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
440
|
+
for (const entry of stdout.split("\0")) if (entry) ignored.add(resolve(workspaceRoot, entry));
|
|
441
|
+
resolvePromise(ignored);
|
|
442
|
+
}).stdin?.end(paths.map((path) => `${relative(workspaceRoot, path)}\0`).join(""));
|
|
443
|
+
});
|
|
444
|
+
}
|
|
592
445
|
async function isIgnoredByGit(gitRoot, path) {
|
|
593
446
|
if (gitRoot === path) return false;
|
|
594
447
|
try {
|
|
@@ -603,8 +456,7 @@ async function isIgnoredByGit(gitRoot, path) {
|
|
|
603
456
|
return false;
|
|
604
457
|
}
|
|
605
458
|
}
|
|
606
|
-
async function
|
|
607
|
-
if (!useGit) return collectFiles(root, isRelevantDiscoveredFile);
|
|
459
|
+
async function discoverGitFiles(root) {
|
|
608
460
|
const { stdout } = await run("git", [
|
|
609
461
|
"ls-files",
|
|
610
462
|
"-co",
|
|
@@ -614,7 +466,7 @@ async function discoverFiles(root, useGit) {
|
|
|
614
466
|
"."
|
|
615
467
|
], {
|
|
616
468
|
cwd: root,
|
|
617
|
-
maxBuffer:
|
|
469
|
+
maxBuffer: 67108864
|
|
618
470
|
});
|
|
619
471
|
const paths = stdout.split("\0").filter(Boolean).map((path) => resolve(root, path)).filter((path) => !hasIgnoredDirectory(root, path) && isRelevantDiscoveredFile(path));
|
|
620
472
|
return (await Promise.all(paths.map(async (path) => {
|
|
@@ -645,7 +497,6 @@ async function collectFiles(root, include) {
|
|
|
645
497
|
const files = [];
|
|
646
498
|
async function visit(directory) {
|
|
647
499
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
648
|
-
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
649
500
|
for (const entry of entries) {
|
|
650
501
|
const path = join(directory, entry.name);
|
|
651
502
|
if (entry.isDirectory()) {
|
|
@@ -657,6 +508,478 @@ async function collectFiles(root, include) {
|
|
|
657
508
|
return files;
|
|
658
509
|
}
|
|
659
510
|
//#endregion
|
|
511
|
+
//#region src/parser/html.ts
|
|
512
|
+
const TEMPLATE_MARKERS = /\{\{|\}\}|\$\{|<%|%>|<#|#>|\[%|%\]/;
|
|
513
|
+
function parseHtmlSource(path, source) {
|
|
514
|
+
const errors = [];
|
|
515
|
+
const document = parse(source, {
|
|
516
|
+
sourceCodeLocationInfo: true,
|
|
517
|
+
scriptingEnabled: false,
|
|
518
|
+
onParseError(error) {
|
|
519
|
+
if (error.code !== "missing-doctype") errors.push(error);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
if (errors.length > 0) throw new Error(`Failed to parse ${path}: ${errors.map((error) => error.code).join(", ")}`);
|
|
523
|
+
const links = [];
|
|
524
|
+
const bases = [];
|
|
525
|
+
const elements = [];
|
|
526
|
+
const dynamicAttributes = [];
|
|
527
|
+
const scriptTexts = [];
|
|
528
|
+
let hasStyle = false;
|
|
529
|
+
const unquoted = (attribute) => source[attribute.start - 1] !== "\"" && source[attribute.start - 1] !== "'";
|
|
530
|
+
function visit(node) {
|
|
531
|
+
if (node.tagName === "style") hasStyle = true;
|
|
532
|
+
if (node.tagName === "script") {
|
|
533
|
+
for (const child of node.childNodes ?? []) if (child.nodeName === "#text" && child.value) scriptTexts.push(child.value);
|
|
534
|
+
}
|
|
535
|
+
if (node.tagName) {
|
|
536
|
+
const attributes = new Map((node.attrs ?? []).map((attribute) => [attribute.name, attribute.value]));
|
|
537
|
+
const locations = node.sourceCodeLocation?.attrs;
|
|
538
|
+
if (locations) {
|
|
539
|
+
if (node.tagName === "link" && stylesheetRel(attributes.get("rel")) && !attributes.has("disabled")) {
|
|
540
|
+
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
541
|
+
const media = locatedAttribute(source, locations.media, attributes.get("media"));
|
|
542
|
+
const tag = node.sourceCodeLocation?.startTag ?? node.sourceCodeLocation;
|
|
543
|
+
if (href && tag) links.push({
|
|
544
|
+
href: href.value,
|
|
545
|
+
media: media?.value ?? "",
|
|
546
|
+
start: href.start,
|
|
547
|
+
end: href.end,
|
|
548
|
+
tagStart: tag.startOffset,
|
|
549
|
+
tagEnd: tag.endOffset
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (node.tagName === "base" && bases.length === 0) {
|
|
553
|
+
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
554
|
+
if (href) bases.push({
|
|
555
|
+
href: href.value,
|
|
556
|
+
writable: href.writable,
|
|
557
|
+
start: href.start,
|
|
558
|
+
end: href.end
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
let classAttribute = locatedAttribute(source, locations.class, attributes.get("class"));
|
|
562
|
+
const idAttribute = locatedAttribute(source, locations.id, attributes.get("id"));
|
|
563
|
+
const dynamic = (!classAttribute && idAttribute && locations.class ? {
|
|
564
|
+
start: locations.class.startOffset,
|
|
565
|
+
end: locations.class.endOffset
|
|
566
|
+
} : void 0) ?? [classAttribute, idAttribute].find((attribute) => attribute && (!attribute.writable || TEMPLATE_MARKERS.test(attribute.value))) ?? (classAttribute && unquoted(classAttribute) ? classAttribute : void 0);
|
|
567
|
+
if (dynamic) dynamicAttributes.push({
|
|
568
|
+
start: dynamic.start,
|
|
569
|
+
end: dynamic.end
|
|
570
|
+
});
|
|
571
|
+
else if (classAttribute || idAttribute) {
|
|
572
|
+
if (!classAttribute && idAttribute) {
|
|
573
|
+
const startTag = node.sourceCodeLocation?.startTag;
|
|
574
|
+
if (startTag && source[startTag.endOffset - 1] === ">") {
|
|
575
|
+
const insertion = classInsertionOffset(source, startTag.startOffset, startTag.endOffset - 1);
|
|
576
|
+
classAttribute = {
|
|
577
|
+
value: "",
|
|
578
|
+
start: insertion,
|
|
579
|
+
end: insertion,
|
|
580
|
+
synthetic: true
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
elements.push({
|
|
585
|
+
classAttribute,
|
|
586
|
+
idAttribute
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const child of node.childNodes ?? []) visit(child);
|
|
592
|
+
if (node.content) visit(node.content);
|
|
593
|
+
}
|
|
594
|
+
visit(document);
|
|
595
|
+
return {
|
|
596
|
+
...toByteOffsets(source, {
|
|
597
|
+
links,
|
|
598
|
+
bases,
|
|
599
|
+
elements,
|
|
600
|
+
dynamicAttributes
|
|
601
|
+
}),
|
|
602
|
+
scriptText: scriptTexts.join("\n"),
|
|
603
|
+
hasStyle
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function utf8OffsetMap(source, indices) {
|
|
607
|
+
const sorted = [...new Set(indices)].sort((left, right) => left - right);
|
|
608
|
+
const map = /* @__PURE__ */ new Map();
|
|
609
|
+
let lastIndex = 0;
|
|
610
|
+
let bytes = 0;
|
|
611
|
+
for (const index of sorted) {
|
|
612
|
+
bytes += Buffer.byteLength(source.slice(lastIndex, index));
|
|
613
|
+
lastIndex = index;
|
|
614
|
+
map.set(index, bytes);
|
|
615
|
+
}
|
|
616
|
+
return map;
|
|
617
|
+
}
|
|
618
|
+
function offsetLookup(offsets) {
|
|
619
|
+
return (index) => {
|
|
620
|
+
const byte = offsets.get(index);
|
|
621
|
+
if (byte === void 0) throw new Error(`No byte offset was mapped for index ${index}`);
|
|
622
|
+
return byte;
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function toByteOffsets(source, parsed) {
|
|
626
|
+
const offset = offsetLookup(utf8OffsetMap(source, [
|
|
627
|
+
...parsed.links.flatMap((link) => [
|
|
628
|
+
link.start,
|
|
629
|
+
link.end,
|
|
630
|
+
link.tagStart,
|
|
631
|
+
link.tagEnd
|
|
632
|
+
]),
|
|
633
|
+
...parsed.bases.flatMap((base) => [base.start, base.end]),
|
|
634
|
+
...parsed.elements.flatMap((element) => [element.classAttribute, element.idAttribute].flatMap((value) => value ? [value.start, value.end] : [])),
|
|
635
|
+
...parsed.dynamicAttributes.flatMap((value) => [value.start, value.end])
|
|
636
|
+
]));
|
|
637
|
+
const attribute = (value) => ({
|
|
638
|
+
...value,
|
|
639
|
+
start: offset(value.start),
|
|
640
|
+
end: offset(value.end)
|
|
641
|
+
});
|
|
642
|
+
return {
|
|
643
|
+
links: parsed.links.map((link) => ({
|
|
644
|
+
...link,
|
|
645
|
+
start: offset(link.start),
|
|
646
|
+
end: offset(link.end),
|
|
647
|
+
tagStart: offset(link.tagStart),
|
|
648
|
+
tagEnd: offset(link.tagEnd)
|
|
649
|
+
})),
|
|
650
|
+
bases: parsed.bases.map((base) => attribute(base)),
|
|
651
|
+
elements: parsed.elements.map((element) => ({
|
|
652
|
+
classAttribute: element.classAttribute && attribute(element.classAttribute),
|
|
653
|
+
idAttribute: element.idAttribute && attribute(element.idAttribute)
|
|
654
|
+
})),
|
|
655
|
+
dynamicAttributes: parsed.dynamicAttributes.map((value) => attribute(value))
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function classInsertionOffset(source, floor, tagEnd) {
|
|
659
|
+
let offset = tagEnd;
|
|
660
|
+
if (source[offset - 1] === "/") offset -= 1;
|
|
661
|
+
while (offset > floor && /\s/.test(source[offset - 1] ?? "")) offset -= 1;
|
|
662
|
+
return offset;
|
|
663
|
+
}
|
|
664
|
+
function stylesheetRel(value = "") {
|
|
665
|
+
const tokens = value.toLowerCase().split(/\s+/);
|
|
666
|
+
return tokens.includes("stylesheet") && !tokens.includes("alternate");
|
|
667
|
+
}
|
|
668
|
+
function locatedAttribute(source, location, parsedValue) {
|
|
669
|
+
if (!location || parsedValue === void 0) return void 0;
|
|
670
|
+
const raw = source.slice(location.startOffset, location.endOffset);
|
|
671
|
+
const equals = raw.indexOf("=");
|
|
672
|
+
if (equals < 0) return void 0;
|
|
673
|
+
let start = equals + 1;
|
|
674
|
+
while (/\s/.test(raw[start] ?? "")) start += 1;
|
|
675
|
+
const quote = raw[start];
|
|
676
|
+
let end;
|
|
677
|
+
if (quote === "\"" || quote === "'") {
|
|
678
|
+
start += 1;
|
|
679
|
+
end = raw.lastIndexOf(quote);
|
|
680
|
+
if (end < start) return void 0;
|
|
681
|
+
} else {
|
|
682
|
+
end = start;
|
|
683
|
+
while (end < raw.length && !/\s/.test(raw[end] ?? "")) end += 1;
|
|
684
|
+
}
|
|
685
|
+
const value = raw.slice(start, end);
|
|
686
|
+
return {
|
|
687
|
+
value: value.includes("&") ? parsedValue : value,
|
|
688
|
+
writable: !value.includes("&"),
|
|
689
|
+
start: location.startOffset + start,
|
|
690
|
+
end: location.startOffset + end
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region src/plan/entry.ts
|
|
695
|
+
function emitsUtilities(specifier) {
|
|
696
|
+
return specifier === "tailwindcss" || specifier === "tailwindcss/utilities" || specifier === "tailwindcss/utilities.css";
|
|
697
|
+
}
|
|
698
|
+
function hasTailwindImport(source) {
|
|
699
|
+
return cssDirectives(source)?.some((directive) => directive.kind === "import" && directive.specifier !== null && emitsUtilities(directive.specifier)) ?? false;
|
|
700
|
+
}
|
|
701
|
+
function tailwindEntryCatalog(styleSources, pathOwners) {
|
|
702
|
+
const catalog = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const [path, source] of styleSources) {
|
|
704
|
+
if (extname(path) !== ".css") continue;
|
|
705
|
+
if (!hasTailwindImport(source)) continue;
|
|
706
|
+
const owner = pathOwners.get(path);
|
|
707
|
+
if (owner === void 0) continue;
|
|
708
|
+
const entries = catalog.get(owner) ?? [];
|
|
709
|
+
entries.push(path);
|
|
710
|
+
catalog.set(owner, entries);
|
|
711
|
+
}
|
|
712
|
+
for (const entries of catalog.values()) entries.sort();
|
|
713
|
+
return catalog;
|
|
714
|
+
}
|
|
715
|
+
function cssDirectives(source) {
|
|
716
|
+
return cssDirectives$1(source)?.filter((directive) => directive !== null && typeof directive === "object" && "kind" in directive && typeof directive.kind === "string") ?? null;
|
|
717
|
+
}
|
|
718
|
+
function entryGraphSheets(entry, styleSources) {
|
|
719
|
+
const sheets = [];
|
|
720
|
+
const seen = /* @__PURE__ */ new Set();
|
|
721
|
+
const pending = [entry];
|
|
722
|
+
while (pending.length > 0) {
|
|
723
|
+
const path = pending.pop();
|
|
724
|
+
if (path === void 0 || seen.has(path)) continue;
|
|
725
|
+
seen.add(path);
|
|
726
|
+
const raw = styleSources.get(path);
|
|
727
|
+
if (raw === void 0) return null;
|
|
728
|
+
const directives = cssDirectives(raw);
|
|
729
|
+
if (directives === null) return null;
|
|
730
|
+
sheets.push({
|
|
731
|
+
path,
|
|
732
|
+
directives
|
|
733
|
+
});
|
|
734
|
+
for (const directive of directives) {
|
|
735
|
+
if (directive.kind !== "import") continue;
|
|
736
|
+
if (directive.specifier === null) return null;
|
|
737
|
+
const spec = directive.specifier;
|
|
738
|
+
if (directive.tailwind) continue;
|
|
739
|
+
if (!spec.startsWith(".") && !spec.startsWith("/")) {
|
|
740
|
+
let resolved;
|
|
741
|
+
try {
|
|
742
|
+
resolved = createRequire(join(dirname(path), "package.json")).resolve(spec);
|
|
743
|
+
} catch {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
if (/[\\/]node_modules[\\/]/.test(resolved)) continue;
|
|
747
|
+
pending.push(resolved);
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const resolved = resolve(dirname(path), spec);
|
|
751
|
+
pending.push(styleSources.has(resolved) ? resolved : `${resolved}.css`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return sheets;
|
|
755
|
+
}
|
|
756
|
+
function scanProof(options) {
|
|
757
|
+
const sheets = entryGraphSheets(options.entry, new Map([...options.styleSources ?? [], [options.entry, options.entrySource]]));
|
|
758
|
+
if (sheets === null) return null;
|
|
759
|
+
let literal = false;
|
|
760
|
+
let automaticDisabled = false;
|
|
761
|
+
for (const { path, directives } of sheets) {
|
|
762
|
+
const base = dirname(path);
|
|
763
|
+
const coversChild = (scope) => {
|
|
764
|
+
if (scope.includes("*")) return false;
|
|
765
|
+
return isWithin(resolve(base, scope), options.packageRoot);
|
|
766
|
+
};
|
|
767
|
+
const excludesChild = (scope) => {
|
|
768
|
+
const stripped = resolve(base, scope.replace(/\*.*$/, ""));
|
|
769
|
+
if (scope.includes("*")) return options.packageRoot.startsWith(stripped) || stripped.startsWith(options.packageRoot);
|
|
770
|
+
return isWithin(stripped, options.packageRoot) || isWithin(options.packageRoot, stripped);
|
|
771
|
+
};
|
|
772
|
+
for (const directive of directives) if (directive.kind === "source") {
|
|
773
|
+
if (directive.inline) continue;
|
|
774
|
+
if (directive.unreadable || directive.scope === null) return null;
|
|
775
|
+
if (directive.not) {
|
|
776
|
+
if (excludesChild(directive.scope)) return null;
|
|
777
|
+
} else if (coversChild(directive.scope)) literal = true;
|
|
778
|
+
} else if (directive.kind === "import" && directive.tailwind) {
|
|
779
|
+
if (directive.sourceUnreadable) return null;
|
|
780
|
+
if (directive.source === "none") automaticDisabled = true;
|
|
781
|
+
else if (directive.source !== null) if (coversChild(directive.source)) literal = true;
|
|
782
|
+
else automaticDisabled = true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (literal) return "literal";
|
|
786
|
+
if (automaticDisabled) return null;
|
|
787
|
+
return isWithin(dirname(options.entry), options.packageRoot) ? "automatic" : null;
|
|
788
|
+
}
|
|
789
|
+
const OPAQUE_SOURCE_IMPORT = {
|
|
790
|
+
specifier: "#unverifiable-source",
|
|
791
|
+
typeOnly: false,
|
|
792
|
+
dynamic: true
|
|
793
|
+
};
|
|
794
|
+
function sourceImports(file) {
|
|
795
|
+
const extension = extname(file.path);
|
|
796
|
+
if (extension === ".html") return [];
|
|
797
|
+
if (extension === ".vue") return file.sourceImports && !file.sourceImportsUnverifiable ? file.sourceImports : [OPAQUE_SOURCE_IMPORT];
|
|
798
|
+
try {
|
|
799
|
+
return sourceAnalysis(file.path, file.source).imports;
|
|
800
|
+
} catch {
|
|
801
|
+
return [OPAQUE_SOURCE_IMPORT];
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const RESOLVABLE_EXTENSIONS = [
|
|
805
|
+
".ts",
|
|
806
|
+
".tsx",
|
|
807
|
+
".mts",
|
|
808
|
+
".cts",
|
|
809
|
+
".js",
|
|
810
|
+
".jsx",
|
|
811
|
+
".mjs",
|
|
812
|
+
".cjs",
|
|
813
|
+
".vue"
|
|
814
|
+
];
|
|
815
|
+
function resolveImport(fromDir, spec, known) {
|
|
816
|
+
if (!spec.startsWith(".")) return void 0;
|
|
817
|
+
const base = resolve(fromDir, spec);
|
|
818
|
+
const emitted = base.match(/^(.*)\.(?:mjs|cjs|js|jsx)$/);
|
|
819
|
+
return [
|
|
820
|
+
base,
|
|
821
|
+
...emitted ? [
|
|
822
|
+
".ts",
|
|
823
|
+
".tsx",
|
|
824
|
+
".mts",
|
|
825
|
+
".cts"
|
|
826
|
+
].map((extension) => `${emitted[1]}${extension}`) : [],
|
|
827
|
+
...RESOLVABLE_EXTENSIONS.map((extension) => `${base}${extension}`),
|
|
828
|
+
...RESOLVABLE_EXTENSIONS.map((extension) => join(base, `index${extension}`))
|
|
829
|
+
].find((candidate) => known.has(candidate));
|
|
830
|
+
}
|
|
831
|
+
function scriptLikeSpecifier(spec) {
|
|
832
|
+
if (!spec.startsWith(".")) return false;
|
|
833
|
+
const trimmed = spec.split(/[?#]/, 1)[0];
|
|
834
|
+
return /\.(?:m?[jt]sx?|c[jt]s|vue)$/.test(trimmed) || !/\.[^/.]+$/.test(trimmed);
|
|
835
|
+
}
|
|
836
|
+
function externalPackageSpecifier(spec) {
|
|
837
|
+
return /^(?:node:[\w/.-]+|(?:@[a-z0-9~][\w.~-]*\/)?[a-z0-9~][\w.~-]*(?:\/.*)?)$/.test(spec);
|
|
838
|
+
}
|
|
839
|
+
function importEdges(files) {
|
|
840
|
+
const known = new Set(files.map((file) => file.path));
|
|
841
|
+
const edges = /* @__PURE__ */ new Map();
|
|
842
|
+
for (const file of files) {
|
|
843
|
+
if (extname(file.path) === ".html") continue;
|
|
844
|
+
const targets = [];
|
|
845
|
+
for (const record of sourceImports(file)) {
|
|
846
|
+
if (record.typeOnly) continue;
|
|
847
|
+
const resolved = resolveImport(dirname(file.path), record.specifier, known);
|
|
848
|
+
if (resolved !== void 0) targets.push(resolved);
|
|
849
|
+
}
|
|
850
|
+
edges.set(file.path, targets);
|
|
851
|
+
}
|
|
852
|
+
return edges;
|
|
853
|
+
}
|
|
854
|
+
function reachableFrom(starts, edges) {
|
|
855
|
+
const reached = new Set(starts);
|
|
856
|
+
const pending = [...reached];
|
|
857
|
+
while (pending.length > 0) {
|
|
858
|
+
const current = pending.pop();
|
|
859
|
+
if (current === void 0) break;
|
|
860
|
+
for (const target of edges.get(current) ?? []) if (!reached.has(target)) {
|
|
861
|
+
reached.add(target);
|
|
862
|
+
pending.push(target);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return reached;
|
|
866
|
+
}
|
|
867
|
+
function exposedFiles(packageRoot, packageJson, files, inboundSeeds) {
|
|
868
|
+
const known = new Set(files.map((file) => file.path));
|
|
869
|
+
const specs = [];
|
|
870
|
+
const collect = (value) => {
|
|
871
|
+
if (typeof value === "string") {
|
|
872
|
+
if (value.includes("*")) return "all";
|
|
873
|
+
specs.push(value);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (value !== null && typeof value === "object") for (const [key, child] of Object.entries(value)) {
|
|
877
|
+
if (key.includes("*")) return "all";
|
|
878
|
+
if (collect(child) === "all") return "all";
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
for (const field of [
|
|
882
|
+
"main",
|
|
883
|
+
"module",
|
|
884
|
+
"exports",
|
|
885
|
+
"browser"
|
|
886
|
+
]) if (collect(packageJson[field]) === "all") return "all";
|
|
887
|
+
if (packageJson.exports === void 0 && packageJson.private !== true) return "all";
|
|
888
|
+
const declared = specs.length > 0;
|
|
889
|
+
const entryFileSeeds = [...inboundSeeds];
|
|
890
|
+
if (!declared) specs.push("./index");
|
|
891
|
+
const entryFiles = [...entryFileSeeds];
|
|
892
|
+
for (const spec of specs) {
|
|
893
|
+
const resolved = resolveImport(packageRoot, spec.startsWith(".") ? spec : `./${spec}`, known);
|
|
894
|
+
if (resolved === void 0) {
|
|
895
|
+
if (declared) return "all";
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
entryFiles.push(resolved);
|
|
899
|
+
}
|
|
900
|
+
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
901
|
+
const exposed = new Set(entryFiles);
|
|
902
|
+
const pending = [...entryFiles];
|
|
903
|
+
while (pending.length > 0) {
|
|
904
|
+
const current = pending.pop();
|
|
905
|
+
if (current === void 0) break;
|
|
906
|
+
const file = byPath.get(current);
|
|
907
|
+
if (file === void 0 || extname(file.path) === ".html") continue;
|
|
908
|
+
for (const record of sourceImports(file)) {
|
|
909
|
+
if (record.typeOnly) continue;
|
|
910
|
+
const resolved = resolveImport(dirname(file.path), record.specifier, known);
|
|
911
|
+
if (resolved === void 0) {
|
|
912
|
+
if (scriptLikeSpecifier(record.specifier) || !record.specifier.startsWith(".") && !externalPackageSpecifier(record.specifier)) return "all";
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (!exposed.has(resolved)) {
|
|
916
|
+
exposed.add(resolved);
|
|
917
|
+
pending.push(resolved);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return exposed;
|
|
922
|
+
}
|
|
923
|
+
function proveSharedEntry(options) {
|
|
924
|
+
const scan = scanProof(options);
|
|
925
|
+
if (scan === null) return null;
|
|
926
|
+
const ownedSources = options.packageSources.filter((file) => options.owned(file.path));
|
|
927
|
+
const loaders = ownedSources.filter((file) => options.writable(file.path)).filter((file) => (file.htmlStylesheets ?? []).some((context) => context.cssPath === options.entry) || htmlLinksEntry(file, options.entry) || extname(file.path) !== ".html" && importsEntry(file, options.entry)).map((file) => file.path);
|
|
928
|
+
if (loaders.length === 0) return null;
|
|
929
|
+
const reachable = reachableFrom(loaders, importEdges(ownedSources));
|
|
930
|
+
const ownedPaths = new Set(ownedSources.map((file) => file.path));
|
|
931
|
+
const packageName = typeof options.packageJson.name === "string" ? options.packageJson.name : void 0;
|
|
932
|
+
const resolveInbound = (fromDir, spec) => {
|
|
933
|
+
const relative = resolveImport(fromDir, spec, ownedPaths);
|
|
934
|
+
if (relative !== void 0) return relative;
|
|
935
|
+
if (packageName !== void 0 && spec.startsWith(`${packageName}/`)) return resolveImport(options.packageRoot, `./${spec.slice(packageName.length + 1)}`, ownedPaths);
|
|
936
|
+
};
|
|
937
|
+
const inboundSeeds = [];
|
|
938
|
+
let foreignUnverifiable = false;
|
|
939
|
+
for (const file of options.packageSources) {
|
|
940
|
+
if (options.owned(file.path) || extname(file.path) === ".html") continue;
|
|
941
|
+
if (importsEntry(file, options.entry)) continue;
|
|
942
|
+
const records = sourceImports(file);
|
|
943
|
+
if (records.includes(OPAQUE_SOURCE_IMPORT)) {
|
|
944
|
+
foreignUnverifiable = true;
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
for (const record of records) {
|
|
948
|
+
if (record.typeOnly) continue;
|
|
949
|
+
const resolved = resolveInbound(dirname(file.path), record.specifier);
|
|
950
|
+
if (resolved !== void 0) inboundSeeds.push(resolved);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
const exposed = foreignUnverifiable ? "all" : exposedFiles(options.packageRoot, options.packageJson, ownedSources, inboundSeeds);
|
|
954
|
+
const provenConsumer = (consumer) => options.owned(consumer.path) && !sourceImports(consumer).includes(OPAQUE_SOURCE_IMPORT) && reachable.has(consumer.path) && (exposed === "all" ? false : !exposed.has(consumer.path)) && (scan === "literal" || !options.ignoredPaths.has(consumer.path));
|
|
955
|
+
return {
|
|
956
|
+
provenConsumer,
|
|
957
|
+
provenStyle: (consumers) => consumers.length > 0 && consumers.every(provenConsumer)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function htmlLinksEntry(file, entry) {
|
|
961
|
+
if (extname(file.path) !== ".html") return false;
|
|
962
|
+
try {
|
|
963
|
+
const parsed = parseHtmlSource(file.path, file.source);
|
|
964
|
+
if (parsed.bases.length > 0) return false;
|
|
965
|
+
return parsed.links.some((link) => {
|
|
966
|
+
const media = link.media.trim().toLowerCase();
|
|
967
|
+
if (media !== "" && media !== "all") return false;
|
|
968
|
+
const href = link.href.split(/[?#]/, 1)[0];
|
|
969
|
+
if (!href || /^[a-z][a-z0-9+.-]*:|^\/\//i.test(href)) return false;
|
|
970
|
+
return resolve(dirname(file.path), href) === entry;
|
|
971
|
+
});
|
|
972
|
+
} catch {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function importsStylesheet(file, stylePath) {
|
|
977
|
+
return sourceImports(file).some((record) => record === OPAQUE_SOURCE_IMPORT && file.source.includes(basename(stylePath)) || !record.typeOnly && record.specifier.startsWith(".") && resolve(dirname(file.path), record.specifier) === stylePath);
|
|
978
|
+
}
|
|
979
|
+
function importsEntry(file, entry) {
|
|
980
|
+
return sourceImports(file).some((record) => !record.typeOnly && !record.dynamic && record.specifier.startsWith(".") && resolve(dirname(file.path), record.specifier) === entry);
|
|
981
|
+
}
|
|
982
|
+
//#endregion
|
|
660
983
|
//#region src/plan/html.ts
|
|
661
984
|
async function preparePackageHtml({ packageRoot, sourceFiles, styleSources, snapshots, pathOwners, styleDependents }) {
|
|
662
985
|
const files = [];
|
|
@@ -781,7 +1104,7 @@ async function collectHtmlStyleContexts(state) {
|
|
|
781
1104
|
analyzable: true
|
|
782
1105
|
});
|
|
783
1106
|
if (extname(state.path) !== ".css") return;
|
|
784
|
-
for (const imported of cssImports(source)) {
|
|
1107
|
+
for (const imported of cssImports(state.path, source)) {
|
|
785
1108
|
const variants = mediaVariants(imported.media);
|
|
786
1109
|
if (variants === void 0) {
|
|
787
1110
|
state.warnings.push(htmlWarning("unsupported-link-media", state.path, imported.start, imported.end, `The stylesheet import media condition ${JSON.stringify(imported.media)} cannot be represented safely.`));
|
|
@@ -804,7 +1127,7 @@ function inferredPreprocessorPath(state) {
|
|
|
804
1127
|
return matches.length === 1 ? matches[0] : void 0;
|
|
805
1128
|
}
|
|
806
1129
|
function addInferredPreprocessorContext(state) {
|
|
807
|
-
if (state.styleSources.has(state.path) && state.sourceFiles.some((file) => extname(file.path) !== ".html" &&
|
|
1130
|
+
if (state.styleSources.has(state.path) && state.sourceFiles.some((file) => extname(file.path) !== ".html" && importsStylesheet(file, state.path))) return false;
|
|
808
1131
|
const path = inferredPreprocessorPath(state);
|
|
809
1132
|
if (!path) return false;
|
|
810
1133
|
state.generatedPaths.add(state.path);
|
|
@@ -855,22 +1178,14 @@ function htmlWarning(code, file, start, end, message) {
|
|
|
855
1178
|
}
|
|
856
1179
|
//#endregion
|
|
857
1180
|
//#region src/tailwind.ts
|
|
858
|
-
function
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
});
|
|
864
|
-
if (configuredPath) return {
|
|
865
|
-
path: configuredPath,
|
|
866
|
-
entries
|
|
867
|
-
};
|
|
1181
|
+
function findTailwindEntries(stylePaths, styleSources) {
|
|
1182
|
+
return stylePaths.filter((path) => extname(path) === ".css" && importSpecifiers(styleSources.get(path) ?? "").some((specifier) => specifier === "tailwindcss" || specifier.startsWith("tailwindcss/")));
|
|
1183
|
+
}
|
|
1184
|
+
function selectTailwindEntry(entries, configuredPath) {
|
|
1185
|
+
if (configuredPath) return configuredPath;
|
|
868
1186
|
if (entries.length === 0) throw new Error("No Tailwind v4 CSS entry was found. Pass --tailwind-css.");
|
|
869
1187
|
if (entries.length > 1) throw new Error("Multiple Tailwind CSS entries were found. Pass --tailwind-css.");
|
|
870
|
-
return
|
|
871
|
-
path: entries[0],
|
|
872
|
-
entries
|
|
873
|
-
};
|
|
1188
|
+
return entries[0];
|
|
874
1189
|
}
|
|
875
1190
|
async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot) {
|
|
876
1191
|
const projectRequire = createRequire(join(packageRoot, "package.json"));
|
|
@@ -884,47 +1199,10 @@ async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot)
|
|
|
884
1199
|
if (!String(packageJson.version).startsWith("4.")) throw new Error(`Tailwind v4 is required; found ${packageJson.version}.`);
|
|
885
1200
|
const { __unstable__loadDesignSystem: loadDesignSystem } = await loadProjectModule(packageRoot, "tailwindcss", "Tailwind v4 must be installed in the target project.");
|
|
886
1201
|
const css = await snapshotFile(snapshots, tailwindCss);
|
|
887
|
-
const base = dirname(tailwindCss);
|
|
888
|
-
const
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
...extractThemeTokens(await readFile(join(dirname(packagePath), "theme.css"), "utf8")),
|
|
892
|
-
...await extractThemeTokensFromGraph(css, base, loadStylesheet)
|
|
893
|
-
};
|
|
894
|
-
return {
|
|
895
|
-
designSystem: await loadDesignSystem(css, {
|
|
896
|
-
base,
|
|
897
|
-
loadModule,
|
|
898
|
-
loadStylesheet
|
|
899
|
-
}),
|
|
900
|
-
css,
|
|
901
|
-
path: tailwindCss,
|
|
902
|
-
themeTokens
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
function extractThemeTokens(css) {
|
|
906
|
-
const tokens = {};
|
|
907
|
-
for (const block of css.matchAll(/@theme[^{]*\{([^}]*)\}/gs)) for (const match of block[1].matchAll(/--([\w-]+):\s*([^;{}]+);/g)) tokens[match[1]] = match[2].trim();
|
|
908
|
-
return tokens;
|
|
909
|
-
}
|
|
910
|
-
async function extractThemeTokensFromGraph(css, base, loadStylesheet, seen = /* @__PURE__ */ new Set()) {
|
|
911
|
-
const tokens = {};
|
|
912
|
-
for (const match of css.matchAll(/@import\s+["']([^"']+)["']/g)) {
|
|
913
|
-
const key = `${base}\0${match[1]}`;
|
|
914
|
-
if (seen.has(key)) continue;
|
|
915
|
-
seen.add(key);
|
|
916
|
-
const loaded = await loadStylesheet(match[1], base);
|
|
917
|
-
Object.assign(tokens, await extractThemeTokensFromGraph(loaded.content, loaded.base, loadStylesheet, seen));
|
|
918
|
-
}
|
|
919
|
-
return Object.assign(tokens, extractThemeTokens(css));
|
|
920
|
-
}
|
|
921
|
-
function invalidCandidates(tailwind, candidates) {
|
|
922
|
-
const generated = tailwind.designSystem.candidatesToCss(candidates);
|
|
923
|
-
return candidates.filter((_, index) => generated[index] === null);
|
|
924
|
-
}
|
|
925
|
-
function createModuleLoader(snapshots, workspaceRoot) {
|
|
926
|
-
return async (id, base) => {
|
|
927
|
-
const path = createRequire(join(base, "package.json")).resolve(id);
|
|
1202
|
+
const base = dirname(tailwindCss);
|
|
1203
|
+
const tailwindRoot = dirname(packagePath);
|
|
1204
|
+
const loadModule = async (id, moduleBase) => {
|
|
1205
|
+
const path = createRequire(join(moduleBase, "package.json")).resolve(id);
|
|
928
1206
|
if (isProjectInput(workspaceRoot, path)) await snapshotFile(snapshots, path);
|
|
929
1207
|
const imported = await import(pathToFileURL(path).href);
|
|
930
1208
|
return {
|
|
@@ -933,27 +1211,278 @@ function createModuleLoader(snapshots, workspaceRoot) {
|
|
|
933
1211
|
module: imported.default ?? imported
|
|
934
1212
|
};
|
|
935
1213
|
};
|
|
936
|
-
|
|
937
|
-
function createStylesheetLoader(projectRequire, tailwindPackagePath, snapshots, workspaceRoot) {
|
|
938
|
-
const tailwindRoot = dirname(tailwindPackagePath);
|
|
939
|
-
return async (id, base) => {
|
|
1214
|
+
const loadStylesheet = async (id, sheetBase) => {
|
|
940
1215
|
let path;
|
|
941
1216
|
if (id === "tailwindcss") path = join(tailwindRoot, "index.css");
|
|
942
1217
|
else if (id.startsWith("tailwindcss/")) {
|
|
943
1218
|
const subpath = id.slice(12);
|
|
944
1219
|
path = join(tailwindRoot, subpath.endsWith(".css") ? subpath : `${subpath}.css`);
|
|
945
|
-
} else if (id.startsWith(".") || isAbsolute(id)) path = resolve(
|
|
1220
|
+
} else if (id.startsWith(".") || isAbsolute(id)) path = resolve(sheetBase, id);
|
|
946
1221
|
else path = projectRequire.resolve(id);
|
|
947
1222
|
return {
|
|
948
1223
|
content: isProjectInput(workspaceRoot, path) ? await snapshotFile(snapshots, path) : await readFile(path, "utf8"),
|
|
949
1224
|
base: dirname(path)
|
|
950
1225
|
};
|
|
951
1226
|
};
|
|
1227
|
+
const defaultTheme = await readFile(join(dirname(packagePath), "theme.css"), "utf8");
|
|
1228
|
+
const graphSources = [];
|
|
1229
|
+
const themeTokens = {
|
|
1230
|
+
...extractThemeTokens("tailwind-default.theme.css", defaultTheme),
|
|
1231
|
+
...await extractThemeTokensFromGraph(tailwindCss, css, base, loadStylesheet, graphSources)
|
|
1232
|
+
};
|
|
1233
|
+
graphSources.push({
|
|
1234
|
+
path: tailwindCss,
|
|
1235
|
+
source: css
|
|
1236
|
+
});
|
|
1237
|
+
const designSystem = await loadDesignSystem(css, {
|
|
1238
|
+
base,
|
|
1239
|
+
loadModule,
|
|
1240
|
+
loadStylesheet
|
|
1241
|
+
});
|
|
1242
|
+
const loadWith = (replacement) => loadDesignSystem(replacement, {
|
|
1243
|
+
base,
|
|
1244
|
+
loadModule,
|
|
1245
|
+
loadStylesheet
|
|
1246
|
+
});
|
|
1247
|
+
return {
|
|
1248
|
+
designSystem,
|
|
1249
|
+
css,
|
|
1250
|
+
path: tailwindCss,
|
|
1251
|
+
themeTokens,
|
|
1252
|
+
graphSources,
|
|
1253
|
+
loadWith
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
function extractThemeTokens(key, css) {
|
|
1257
|
+
return stylesheetAnalysis(key, css).themeTokens;
|
|
1258
|
+
}
|
|
1259
|
+
function importSpecifiers(css) {
|
|
1260
|
+
return (cssDirectives$1(css) ?? []).flatMap((directive) => directive !== null && typeof directive === "object" && "kind" in directive && directive.kind === "import" && "specifier" in directive && typeof directive.specifier === "string" ? [directive.specifier] : []);
|
|
1261
|
+
}
|
|
1262
|
+
async function extractThemeTokensFromGraph(identity, css, base, loadStylesheet, graphSources, seen = /* @__PURE__ */ new Set()) {
|
|
1263
|
+
const tokens = {};
|
|
1264
|
+
for (const spec of importSpecifiers(css)) {
|
|
1265
|
+
const key = `${base}\0${spec}`;
|
|
1266
|
+
if (seen.has(key)) continue;
|
|
1267
|
+
seen.add(key);
|
|
1268
|
+
const loaded = await loadStylesheet(spec, base);
|
|
1269
|
+
if (spec !== "tailwindcss" && !spec.startsWith("tailwindcss/")) graphSources.push({
|
|
1270
|
+
path: key,
|
|
1271
|
+
source: loaded.content
|
|
1272
|
+
});
|
|
1273
|
+
Object.assign(tokens, await extractThemeTokensFromGraph(key, loaded.content, loaded.base, loadStylesheet, graphSources, seen));
|
|
1274
|
+
}
|
|
1275
|
+
return Object.assign(tokens, extractThemeTokens(`${identity}.theme.css`, css));
|
|
1276
|
+
}
|
|
1277
|
+
function invalidCandidates(system, candidates) {
|
|
1278
|
+
const generated = system.candidatesToCss(candidates);
|
|
1279
|
+
return candidates.filter((_, index) => generated[index] === null);
|
|
1280
|
+
}
|
|
1281
|
+
//#endregion
|
|
1282
|
+
//#region src/plan/media.ts
|
|
1283
|
+
const GENERATED_NAMESPACE = "twm-media";
|
|
1284
|
+
/**
|
|
1285
|
+
* Resolve one fixed name per key across a complete entry group.
|
|
1286
|
+
*
|
|
1287
|
+
* Resolution follows the RFC order: a built-in candidate with a verified
|
|
1288
|
+
* effective expansion, an existing breakpoint matched by exact value and
|
|
1289
|
+
* verified the same way, an adopted definition whose name, normalized
|
|
1290
|
+
* meaning, and effective expansion equal what the migration would emit,
|
|
1291
|
+
* the readable generated name, the digest name, and finally the
|
|
1292
|
+
* arbitrary-variant fallback. Reservation is ownership-blind:
|
|
1293
|
+
* authored names with any different meaning, active theme names, explicit
|
|
1294
|
+
* reserved names such as inert scanned candidates, and any name the design
|
|
1295
|
+
* system resolves all block a generated name.
|
|
1296
|
+
*/
|
|
1297
|
+
function resolveMediaNames(collections, themeTokens, probes, reservedNames = []) {
|
|
1298
|
+
const components = mergeComponents(collections);
|
|
1299
|
+
const authoredKeysByName = mergeAuthoredVariants(collections);
|
|
1300
|
+
const reserved = new Set(reservedNames);
|
|
1301
|
+
for (const token of Object.keys(themeTokens)) if (token.startsWith("breakpoint-")) reserved.add(token.slice(11));
|
|
1302
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1303
|
+
const adoptableKey = (name) => {
|
|
1304
|
+
const keys = authoredKeysByName.get(name);
|
|
1305
|
+
if (keys === void 0 || keys.size !== 1) return null;
|
|
1306
|
+
const [key] = keys;
|
|
1307
|
+
return key ?? null;
|
|
1308
|
+
};
|
|
1309
|
+
const taken = (name) => claimed.has(name) || reserved.has(name) || authoredKeysByName.has(name) || probes.resolves(name);
|
|
1310
|
+
const names = /* @__PURE__ */ new Map();
|
|
1311
|
+
const fallbacks = /* @__PURE__ */ new Set();
|
|
1312
|
+
for (const component of components) {
|
|
1313
|
+
const resolved = (name, kind) => {
|
|
1314
|
+
claimed.add(name);
|
|
1315
|
+
names.set(component.key, {
|
|
1316
|
+
name,
|
|
1317
|
+
kind
|
|
1318
|
+
});
|
|
1319
|
+
};
|
|
1320
|
+
if (component.builtin !== null && probes.expansionMatches(component.builtin, component.key)) {
|
|
1321
|
+
resolved(component.builtin, "builtin");
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
if (component.breakpoint !== null && probes.expansionMatches(component.breakpoint, component.key)) {
|
|
1325
|
+
resolved(component.breakpoint, "breakpoint");
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
const digestName = `${GENERATED_NAMESPACE}-${component.digest}`;
|
|
1329
|
+
const candidates = component.readableName === null ? [digestName] : [component.readableName, digestName];
|
|
1330
|
+
let done = false;
|
|
1331
|
+
for (const candidate of candidates) {
|
|
1332
|
+
if (adoptableKey(candidate) === component.key && !claimed.has(candidate) && probes.expansionMatches(candidate, component.key)) {
|
|
1333
|
+
resolved(candidate, "adopted");
|
|
1334
|
+
done = true;
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
if (!taken(candidate)) {
|
|
1338
|
+
resolved(candidate, "generated");
|
|
1339
|
+
done = true;
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (!done) fallbacks.add(component.key);
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
names,
|
|
1347
|
+
fallbacks
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
function mergeComponents(collections) {
|
|
1351
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1352
|
+
for (const collection of collections) for (const component of collection.components) if (!byKey.has(component.key)) byKey.set(component.key, component);
|
|
1353
|
+
return [...byKey.values()].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
1354
|
+
}
|
|
1355
|
+
function mergeAuthoredVariants(collections) {
|
|
1356
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1357
|
+
for (const collection of collections) for (const variant of collection.authoredVariants) {
|
|
1358
|
+
const keys = byName.get(variant.name) ?? /* @__PURE__ */ new Set();
|
|
1359
|
+
keys.add(variant.mediaQueryKey);
|
|
1360
|
+
byName.set(variant.name, keys);
|
|
1361
|
+
}
|
|
1362
|
+
return byName;
|
|
1363
|
+
}
|
|
1364
|
+
const PROBE_UTILITY = "[--tw-probe:1]";
|
|
1365
|
+
function probeCandidate(prefix, name) {
|
|
1366
|
+
return `${prefix === null || prefix === "" ? "" : `${prefix}:`}${name}:${PROBE_UTILITY}`;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Build the effective-expansion probes against the unaugmented loaded
|
|
1370
|
+
* design system. `resolves` proves only existence; `expansionMatches`
|
|
1371
|
+
* proves that compiling the name yields exactly one `@media` wrapper whose
|
|
1372
|
+
* normalized query equals the key.
|
|
1373
|
+
*/
|
|
1374
|
+
function buildMediaProbes(tailwind) {
|
|
1375
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
1376
|
+
const prefix = tailwind.designSystem.theme.prefix;
|
|
1377
|
+
const compile = (name) => {
|
|
1378
|
+
let css = compiled.get(name);
|
|
1379
|
+
if (css === void 0) {
|
|
1380
|
+
[css = null] = tailwind.designSystem.candidatesToCss([probeCandidate(prefix, name)]);
|
|
1381
|
+
compiled.set(name, css);
|
|
1382
|
+
}
|
|
1383
|
+
return css;
|
|
1384
|
+
};
|
|
1385
|
+
return {
|
|
1386
|
+
resolves: (name) => compile(name) !== null,
|
|
1387
|
+
expansionMatches: (name, key) => {
|
|
1388
|
+
const css = compile(name);
|
|
1389
|
+
if (css === null) return false;
|
|
1390
|
+
return JSON.parse(mediaProbeKey(css)) === key;
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Conservative scanned-corpus reservation: every colon-chained token in the
|
|
1396
|
+
* package's scanned sources reserves its variant segments, so an inert
|
|
1397
|
+
* candidate such as `width-lte-768px:hidden` is never activated with a new
|
|
1398
|
+
* meaning. Over-reservation only pushes a generated name to its digest
|
|
1399
|
+
* form, and content-identity adoption bypasses these reservations, so
|
|
1400
|
+
* reruns stay idempotent.
|
|
1401
|
+
*/
|
|
1402
|
+
function scannedVariantReservations(sources) {
|
|
1403
|
+
const reserved = /* @__PURE__ */ new Set();
|
|
1404
|
+
for (const source of sources) for (const match of source.matchAll(/[A-Za-z][\w-]*(?::[^\s"'`{}]+)+/g)) for (const segment of match[0].split(":").slice(0, -1)) reserved.add(segment);
|
|
1405
|
+
return reserved;
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Run native condition collection for one member package of an entry
|
|
1409
|
+
* group. Kept separate from resolution so a recoverable collection
|
|
1410
|
+
* failure is attributable to its member.
|
|
1411
|
+
*/
|
|
1412
|
+
function collectMemberMediaConditions(stylesheets, tailwind) {
|
|
1413
|
+
return JSON.parse(collectMediaConditions(JSON.stringify({
|
|
1414
|
+
stylesheets,
|
|
1415
|
+
themeTokens: tailwind.themeTokens,
|
|
1416
|
+
tailwindSources: tailwind.graphSources
|
|
1417
|
+
})));
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Resolve the fixed map the planner consumes plus the definitions
|
|
1421
|
+
* extraction may emit from the group's member collections. Name
|
|
1422
|
+
* allocation runs once for the complete group, so different keys from
|
|
1423
|
+
* different packages can never independently claim one name.
|
|
1424
|
+
*/
|
|
1425
|
+
function planMediaExtraction(options) {
|
|
1426
|
+
const resolution = resolveMediaNames(options.collections, options.tailwind.themeTokens, buildMediaProbes(options.tailwind), scannedVariantReservations([...options.scannedSources, ...options.tailwind.graphSources.map((graphSource) => graphSource.source)]));
|
|
1427
|
+
const names = {};
|
|
1428
|
+
const generated = [];
|
|
1429
|
+
for (const [key, resolved] of resolution.names) {
|
|
1430
|
+
names[key] = resolved.name;
|
|
1431
|
+
if (resolved.kind === "generated") generated.push({
|
|
1432
|
+
key,
|
|
1433
|
+
name: resolved.name
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
return {
|
|
1437
|
+
names,
|
|
1438
|
+
generated
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* The generated definitions the final plan actually uses, ordered by the
|
|
1443
|
+
* first applied rule that uses each name. A rule counts as applied only
|
|
1444
|
+
* when every one of its candidates survived into `plan.candidates`: a
|
|
1445
|
+
* retained global rule still applies its utilities to proven consumers,
|
|
1446
|
+
* while a blocked rule always contains a dropped candidate, so its
|
|
1447
|
+
* occurrences must not fix registration order for a candidate another rule
|
|
1448
|
+
* still applies. Registration order controls conflicting utility
|
|
1449
|
+
* precedence, so the order follows the applied rules rather than every
|
|
1450
|
+
* encountered condition.
|
|
1451
|
+
*/
|
|
1452
|
+
function usedGeneratedDefinitions(extraction, plan) {
|
|
1453
|
+
const applied = new Set(plan.candidates);
|
|
1454
|
+
const byName = new Map(extraction.generated.map((definition) => [definition.name, definition]));
|
|
1455
|
+
const ordered = [];
|
|
1456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1457
|
+
for (const rule of plan.rules) {
|
|
1458
|
+
if (!rule.candidates.every((candidate) => applied.has(candidate))) continue;
|
|
1459
|
+
for (const candidate of rule.candidates) for (const segment of candidate.split(":").slice(0, -1)) {
|
|
1460
|
+
const definition = byName.get(segment);
|
|
1461
|
+
if (definition !== void 0 && !seen.has(segment)) {
|
|
1462
|
+
seen.add(segment);
|
|
1463
|
+
ordered.push(definition);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return ordered;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Append the used definitions to the entry source, each as its own block,
|
|
1471
|
+
* matching the planner's moved-block formatting. Definitions already
|
|
1472
|
+
* present are recognized through adoption before this runs, so appending
|
|
1473
|
+
* never duplicates one.
|
|
1474
|
+
*/
|
|
1475
|
+
function appendMediaDefinitions(source, definitions) {
|
|
1476
|
+
let output = source;
|
|
1477
|
+
for (const { key, name } of definitions) {
|
|
1478
|
+
if (!output.endsWith("\n")) output += "\n";
|
|
1479
|
+
if (!output.endsWith("\n\n")) output += "\n";
|
|
1480
|
+
output += `@custom-variant ${name} {\n @media ${key} {\n @slot;\n }\n}\n`;
|
|
1481
|
+
}
|
|
1482
|
+
return output;
|
|
952
1483
|
}
|
|
953
1484
|
//#endregion
|
|
954
1485
|
//#region src/parser/vue.ts
|
|
955
|
-
const ESCAPE_SELECTOR = /(?:::v-|:)(?:deep|global|slotted)\(([^)]*)\)/g;
|
|
956
|
-
const ESCAPE_RESIDUE = /(?:>>>|\/deep\/|::v-deep|:deep|::v-slotted|:slotted|::v-global|:global)/;
|
|
957
1486
|
const SUPPORTED_STYLE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
958
1487
|
"lang",
|
|
959
1488
|
"module",
|
|
@@ -967,6 +1496,13 @@ const SUPPORTED_STYLE_LANGUAGES = /* @__PURE__ */ new Set([
|
|
|
967
1496
|
"sass",
|
|
968
1497
|
"less"
|
|
969
1498
|
]);
|
|
1499
|
+
const SUPPORTED_SCRIPT_LANGUAGES = /* @__PURE__ */ new Set([
|
|
1500
|
+
void 0,
|
|
1501
|
+
"js",
|
|
1502
|
+
"jsx",
|
|
1503
|
+
"ts",
|
|
1504
|
+
"tsx"
|
|
1505
|
+
]);
|
|
970
1506
|
const NODE_ELEMENT = 1;
|
|
971
1507
|
const NODE_TEXT = 2;
|
|
972
1508
|
const NODE_INTERPOLATION = 5;
|
|
@@ -997,29 +1533,48 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
997
1533
|
message
|
|
998
1534
|
});
|
|
999
1535
|
const { descriptor, errors } = compiler.parse(source, { filename: path });
|
|
1536
|
+
const styleRanges = descriptor.styles.map((style) => ({
|
|
1537
|
+
start: style.loc.start.offset,
|
|
1538
|
+
end: style.loc.end.offset
|
|
1539
|
+
}));
|
|
1540
|
+
const byteRanges = (offset) => ({
|
|
1541
|
+
warnings: warnings.map((warning) => ({
|
|
1542
|
+
...warning,
|
|
1543
|
+
start: offset(warning.start),
|
|
1544
|
+
end: offset(warning.end)
|
|
1545
|
+
})),
|
|
1546
|
+
styleRanges: styleRanges.map((range) => ({
|
|
1547
|
+
start: offset(range.start),
|
|
1548
|
+
end: offset(range.end)
|
|
1549
|
+
}))
|
|
1550
|
+
});
|
|
1551
|
+
const retained = () => {
|
|
1552
|
+
const offsets = utf8OffsetMap(source, [...warnings, ...styleRanges].flatMap((range) => [range.start, range.end]));
|
|
1553
|
+
return {
|
|
1554
|
+
retained: true,
|
|
1555
|
+
...byteRanges(offsetLookup(offsets))
|
|
1556
|
+
};
|
|
1557
|
+
};
|
|
1000
1558
|
if (errors.length > 0) {
|
|
1001
1559
|
const offset = errors[0]?.loc?.start?.offset ?? 0;
|
|
1002
1560
|
warn("unsupported-sfc-block", offset, offset, `The SFC could not be parsed: ${errors[0].message}`);
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1561
|
+
if (errors[0]?.loc?.start?.offset === 0) Object.assign(warnings[0], {
|
|
1562
|
+
line: 1,
|
|
1563
|
+
column: 1,
|
|
1564
|
+
endLine: 1,
|
|
1565
|
+
endColumn: 1
|
|
1566
|
+
});
|
|
1567
|
+
return retained();
|
|
1007
1568
|
}
|
|
1008
1569
|
const template = descriptor.template;
|
|
1009
1570
|
if (!template || template.lang || template.src) {
|
|
1010
1571
|
warn("unsupported-sfc-block", 0, 0, !template ? "The SFC has no template block, so its class usage cannot be analyzed." : "Only the default template language with inline content is analyzed.");
|
|
1011
|
-
return
|
|
1012
|
-
warnings: toByteWarnings(source, warnings),
|
|
1013
|
-
retained: true
|
|
1014
|
-
};
|
|
1572
|
+
return retained();
|
|
1015
1573
|
}
|
|
1016
1574
|
if (descriptor.customBlocks.length > 0) {
|
|
1017
1575
|
const block = descriptor.customBlocks[0];
|
|
1018
1576
|
warn("unsupported-sfc-block", block.loc.start.offset, block.loc.end.offset, `The custom <${block.type}> block is not analyzed.`);
|
|
1019
|
-
return
|
|
1020
|
-
warnings: toByteWarnings(source, warnings),
|
|
1021
|
-
retained: true
|
|
1022
|
-
};
|
|
1577
|
+
return retained();
|
|
1023
1578
|
}
|
|
1024
1579
|
const blocks = [];
|
|
1025
1580
|
const unscopedBlocks = [];
|
|
@@ -1031,6 +1586,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1031
1586
|
const shadowPreprocessorTexts = [];
|
|
1032
1587
|
const unscopedShadowPreprocessorTexts = [];
|
|
1033
1588
|
let escapeUnverifiable = false;
|
|
1589
|
+
let opaqueStyleBlocks = false;
|
|
1034
1590
|
let moduleSiblingUnsupported = false;
|
|
1035
1591
|
for (const style of descriptor.styles) {
|
|
1036
1592
|
const start = style.loc.start.offset;
|
|
@@ -1041,6 +1597,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1041
1597
|
warn("unsupported-sfc-block", start, end, `Unsupported <style> attributes: ${unsupportedAttributes.join(", ")}.`);
|
|
1042
1598
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1043
1599
|
escapeUnverifiable = true;
|
|
1600
|
+
opaqueStyleBlocks = true;
|
|
1044
1601
|
continue;
|
|
1045
1602
|
}
|
|
1046
1603
|
if (style.src !== void 0) {
|
|
@@ -1048,6 +1605,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1048
1605
|
warn("unsupported-sfc-block", start, end, "A <style module src> block is not supported.");
|
|
1049
1606
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1050
1607
|
escapeUnverifiable = true;
|
|
1608
|
+
opaqueStyleBlocks = true;
|
|
1051
1609
|
} else styleBlockImports.push({
|
|
1052
1610
|
reference: style.src,
|
|
1053
1611
|
start,
|
|
@@ -1059,12 +1617,19 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1059
1617
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1060
1618
|
warn("unsupported-sfc-block", start, end, "Named <style module> blocks are not supported.");
|
|
1061
1619
|
shadowModuleCssTexts.push(style.content);
|
|
1620
|
+
opaqueStyleBlocks = true;
|
|
1062
1621
|
continue;
|
|
1063
1622
|
}
|
|
1064
1623
|
if (!SUPPORTED_STYLE_LANGUAGES.has(style.lang)) {
|
|
1065
1624
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1066
1625
|
warn("preprocessor-style-block", start, end, `The <style lang="${style.lang}"> language is not supported.`);
|
|
1067
1626
|
shadowPreprocessorTexts.push(style.content);
|
|
1627
|
+
opaqueStyleBlocks = true;
|
|
1628
|
+
try {
|
|
1629
|
+
if (stylesheetAnalysis(`${path}.css`, style.content).selectorsUnverifiable) escapeUnverifiable = true;
|
|
1630
|
+
} catch {
|
|
1631
|
+
escapeUnverifiable = true;
|
|
1632
|
+
}
|
|
1068
1633
|
continue;
|
|
1069
1634
|
}
|
|
1070
1635
|
const outerStart = source.lastIndexOf("<style", start);
|
|
@@ -1072,6 +1637,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1072
1637
|
if (outerStart < 0 || !closing) {
|
|
1073
1638
|
warn("unsupported-sfc-block", start, end, "The style block tags could not be located.");
|
|
1074
1639
|
escapeUnverifiable = true;
|
|
1640
|
+
opaqueStyleBlocks = true;
|
|
1075
1641
|
continue;
|
|
1076
1642
|
}
|
|
1077
1643
|
const block = {
|
|
@@ -1088,24 +1654,37 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1088
1654
|
}
|
|
1089
1655
|
if (!style.scoped) {
|
|
1090
1656
|
unscopedBlocks.push(block);
|
|
1091
|
-
if (style.lang && style.lang !== "css")
|
|
1092
|
-
|
|
1657
|
+
if (style.lang && style.lang !== "css") {
|
|
1658
|
+
unscopedShadowPreprocessorTexts.push(style.content);
|
|
1659
|
+
try {
|
|
1660
|
+
if (stylesheetAnalysis(`${path}.${style.lang}`, style.content).selectorsUnverifiable) escapeUnverifiable = true;
|
|
1661
|
+
} catch {
|
|
1662
|
+
escapeUnverifiable = true;
|
|
1663
|
+
}
|
|
1664
|
+
} else unscopedShadowCssTexts.push(style.content);
|
|
1093
1665
|
continue;
|
|
1094
1666
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1667
|
+
try {
|
|
1668
|
+
const escapes = stylesheetAnalysis(`${path}.${style.lang ?? "css"}`, style.content);
|
|
1669
|
+
shadowCssTexts.push(...escapes.scopeEscapes);
|
|
1670
|
+
if (escapes.scopeEscapes.length > 0) block.shadowSource = escapes.scopeShadowCss.join("\n") || "/* scope escapes extracted */";
|
|
1671
|
+
if (escapes.scopeEscapesUnverifiable || escapes.selectorsUnverifiable) escapeUnverifiable = true;
|
|
1672
|
+
} catch {
|
|
1673
|
+
escapeUnverifiable = true;
|
|
1098
1674
|
}
|
|
1099
|
-
if (ESCAPE_RESIDUE.test(style.content.replace(ESCAPE_SELECTOR, " "))) escapeUnverifiable = true;
|
|
1100
1675
|
blocks.push(block);
|
|
1101
1676
|
}
|
|
1677
|
+
const scriptLang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
|
|
1102
1678
|
const state = {
|
|
1103
1679
|
elements: [],
|
|
1104
1680
|
components: [],
|
|
1105
1681
|
dynamic: false,
|
|
1106
1682
|
vHtml: false,
|
|
1107
1683
|
hasSlot: false,
|
|
1108
|
-
|
|
1684
|
+
moduleClosureBroken: false,
|
|
1685
|
+
referencesUseCssModule: false,
|
|
1686
|
+
expressionReferences: /* @__PURE__ */ new Set(),
|
|
1687
|
+
expressionPath: scriptLang === "ts" || scriptLang === "tsx" ? "Component.ts" : "Component.js"
|
|
1109
1688
|
};
|
|
1110
1689
|
visitTemplateNode(source, template.ast, state);
|
|
1111
1690
|
const alwaysRenderedRoots = template.ast.children.filter((node) => node.type === NODE_ELEMENT && (node.tagType === TAG_ELEMENT || node.tagType === TAG_COMPONENT) && !node.props?.some((prop) => prop.type === PROP_DIRECTIVE && [
|
|
@@ -1115,41 +1694,40 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1115
1694
|
"for"
|
|
1116
1695
|
].includes(prop.name))).length;
|
|
1117
1696
|
const scriptText = [descriptor.script?.content, descriptor.scriptSetup?.content].filter(Boolean).join("\n");
|
|
1118
|
-
const fallthroughUnverifiable = Boolean(descriptor.script) || descriptor.scriptSetup?.src !== void 0 || /\b(?:defineOptions|defineProps|inheritAttrs)\b/.test(descriptor.scriptSetup?.content ?? "");
|
|
1119
1697
|
let scriptImportsUnverifiable = false;
|
|
1698
|
+
const scriptImports = [];
|
|
1699
|
+
let setupAnalysis;
|
|
1700
|
+
let scriptUsesCssModule = false;
|
|
1701
|
+
let scriptHasDynamicImport = false;
|
|
1702
|
+
const scriptVueReferences = [];
|
|
1703
|
+
const scriptVueGlobPatterns = [];
|
|
1704
|
+
let scriptVueGlobUnverifiable = false;
|
|
1120
1705
|
const styleImports = [descriptor.script, descriptor.scriptSetup].flatMap((script) => {
|
|
1121
1706
|
if (!script) return [];
|
|
1122
|
-
if (script.src || !
|
|
1123
|
-
void 0,
|
|
1124
|
-
"js",
|
|
1125
|
-
"jsx",
|
|
1126
|
-
"ts",
|
|
1127
|
-
"tsx"
|
|
1128
|
-
].includes(script.lang)) {
|
|
1707
|
+
if (script.src || !SUPPORTED_SCRIPT_LANGUAGES.has(script.lang)) {
|
|
1129
1708
|
scriptImportsUnverifiable = true;
|
|
1130
1709
|
return [];
|
|
1131
1710
|
}
|
|
1132
1711
|
try {
|
|
1133
|
-
|
|
1712
|
+
const analysis = sourceAnalysis(`${path}.${script.lang ?? "js"}`, script.content);
|
|
1713
|
+
if (script === descriptor.scriptSetup) setupAnalysis = analysis;
|
|
1714
|
+
if (analysis.usesCssModule || analysis.hasUnboundUseCssModule) scriptUsesCssModule = true;
|
|
1715
|
+
if (analysis.hasDynamicImport) scriptHasDynamicImport = true;
|
|
1716
|
+
scriptImports.push(...analysis.imports);
|
|
1717
|
+
scriptVueReferences.push(...analysis.imports.filter((record) => !record.typeOnly).map((record) => record.specifier));
|
|
1718
|
+
scriptVueGlobPatterns.push(...analysis.vueGlobPatterns);
|
|
1719
|
+
if (analysis.vueGlobUnverifiable) scriptVueGlobUnverifiable = true;
|
|
1720
|
+
return analysis.staticImports;
|
|
1134
1721
|
} catch {
|
|
1135
1722
|
scriptImportsUnverifiable = true;
|
|
1136
1723
|
return [];
|
|
1137
1724
|
}
|
|
1138
1725
|
});
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
void 0,
|
|
1142
|
-
"js",
|
|
1143
|
-
"jsx",
|
|
1144
|
-
"ts",
|
|
1145
|
-
"tsx"
|
|
1146
|
-
].includes(descriptor.scriptSetup.lang)) try {
|
|
1147
|
-
componentImports = staticImportBindings(`${path}.${descriptor.scriptSetup.lang ?? "js"}`, descriptor.scriptSetup.content);
|
|
1148
|
-
} catch {
|
|
1149
|
-
componentImports = [];
|
|
1150
|
-
}
|
|
1726
|
+
const fallthroughUnverifiable = Boolean(descriptor.script) || descriptor.scriptSetup?.src !== void 0 || Boolean(descriptor.scriptSetup) && !setupAnalysis || setupAnalysis?.hasVueFallthroughMacro === true;
|
|
1727
|
+
const componentImports = setupAnalysis?.defaultImports ?? [];
|
|
1151
1728
|
const offsets = utf8OffsetMap(source, [
|
|
1152
1729
|
...warnings.flatMap((warning) => [warning.start, warning.end]),
|
|
1730
|
+
...styleRanges.flatMap((range) => [range.start, range.end]),
|
|
1153
1731
|
...[
|
|
1154
1732
|
...blocks,
|
|
1155
1733
|
...unscopedBlocks,
|
|
@@ -1194,15 +1772,11 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1194
1772
|
}
|
|
1195
1773
|
});
|
|
1196
1774
|
return {
|
|
1197
|
-
|
|
1198
|
-
...warning,
|
|
1199
|
-
start: offset(warning.start),
|
|
1200
|
-
end: offset(warning.end)
|
|
1201
|
-
})),
|
|
1775
|
+
...byteRanges(offset),
|
|
1202
1776
|
blocks: blocks.map(toByteBlock),
|
|
1203
1777
|
unscopedBlocks: unscopedBlocks.map(toByteBlock),
|
|
1204
1778
|
moduleBlocks: moduleBlocks.map(toByteBlock),
|
|
1205
|
-
moduleClosureBroken: moduleSiblingUnsupported || scriptImportsUnverifiable ||
|
|
1779
|
+
moduleClosureBroken: moduleSiblingUnsupported || scriptImportsUnverifiable || scriptUsesCssModule || state.moduleClosureBroken || state.referencesUseCssModule && setupAnalysis?.definesRootUseCssModule !== true || (setupAnalysis?.useCssModuleLocals ?? []).some((name) => state.expressionReferences.has(name)),
|
|
1206
1780
|
htmlElements: state.elements.map(toByteSite),
|
|
1207
1781
|
componentSites: state.components.map(toByteSite),
|
|
1208
1782
|
rootStarts: template.ast.children.filter((node) => node.type === NODE_ELEMENT).map((node) => offsets.get(node.loc.start.offset)),
|
|
@@ -1210,7 +1784,13 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1210
1784
|
rootFragment: template.ast.children.filter((node) => node.type === NODE_ELEMENT || node.type === NODE_INTERPOLATION || node.type === NODE_TEXT && node.content?.trim?.() !== "").length !== 1,
|
|
1211
1785
|
scriptText,
|
|
1212
1786
|
scriptStyleImports: [...new Set(styleImports)],
|
|
1787
|
+
scriptImports,
|
|
1213
1788
|
scriptImportsUnverifiable,
|
|
1789
|
+
scriptHasDynamicImport,
|
|
1790
|
+
scriptVueReferences,
|
|
1791
|
+
scriptVueGlobPatterns,
|
|
1792
|
+
scriptVueGlobUnverifiable,
|
|
1793
|
+
hasOpaqueStyleBlocks: opaqueStyleBlocks,
|
|
1214
1794
|
styleBlockImports: styleBlockImports.map((entry) => ({
|
|
1215
1795
|
...entry,
|
|
1216
1796
|
start: offset(entry.start),
|
|
@@ -1247,27 +1827,36 @@ function visitTemplateNode(source, node, state) {
|
|
|
1247
1827
|
let moduleBinding;
|
|
1248
1828
|
for (const prop of node.props ?? []) {
|
|
1249
1829
|
if (prop.type !== PROP_DIRECTIVE) continue;
|
|
1830
|
+
const expression = prop.exp?.content ? prop.name === "on" ? templateHandler(prop.exp.content) : templateExpression(state.expressionPath, prop.exp.content) : void 0;
|
|
1831
|
+
if (prop.exp?.content && !expression) state.moduleClosureBroken = true;
|
|
1250
1832
|
let provenModuleExpression = false;
|
|
1251
1833
|
if (prop.name === "bind") {
|
|
1252
1834
|
if (!prop.arg || !prop.arg.isStatic) classOpaque = true;
|
|
1253
1835
|
else if (prop.arg.content === "class") {
|
|
1254
|
-
const member = node.tagType === TAG_ELEMENT
|
|
1836
|
+
const member = node.tagType === TAG_ELEMENT ? expression?.vueModuleMember : void 0;
|
|
1255
1837
|
if (member && !moduleBinding) {
|
|
1256
1838
|
moduleBinding = {
|
|
1257
|
-
name: member
|
|
1839
|
+
name: member,
|
|
1258
1840
|
start: attributeRemovalStart(source, node, prop),
|
|
1259
1841
|
end: prop.loc.end.offset
|
|
1260
1842
|
};
|
|
1261
1843
|
provenModuleExpression = true;
|
|
1262
1844
|
} else {
|
|
1263
|
-
const value =
|
|
1845
|
+
const value = expression?.staticString ?? void 0;
|
|
1264
1846
|
if (value === void 0) classOpaque = true;
|
|
1265
1847
|
else bindingClasses.push(...value.split(/[\t\n\f\r ]+/).filter(Boolean));
|
|
1266
1848
|
}
|
|
1267
1849
|
} else if (prop.arg.content === "id") classOpaque = true;
|
|
1268
1850
|
}
|
|
1269
|
-
if (!provenModuleExpression &&
|
|
1270
|
-
if (
|
|
1851
|
+
if (!provenModuleExpression && expression?.usesCssModule) state.moduleClosureBroken = true;
|
|
1852
|
+
if (expression?.referencesUseCssModule) state.referencesUseCssModule = true;
|
|
1853
|
+
for (const name of expression?.references ?? []) state.expressionReferences.add(name);
|
|
1854
|
+
if (prop.arg && !prop.arg.isStatic && prop.arg.content) {
|
|
1855
|
+
const argExpression = templateExpression(state.expressionPath, prop.arg.content);
|
|
1856
|
+
state.moduleClosureBroken ||= argExpression?.usesCssModule ?? true;
|
|
1857
|
+
state.referencesUseCssModule ||= argExpression?.referencesUseCssModule ?? false;
|
|
1858
|
+
for (const name of argExpression?.references ?? []) state.expressionReferences.add(name);
|
|
1859
|
+
}
|
|
1271
1860
|
if (prop.name === "html") state.vHtml = true;
|
|
1272
1861
|
}
|
|
1273
1862
|
if (classOpaque) state.dynamic = true;
|
|
@@ -1281,7 +1870,12 @@ function visitTemplateNode(source, node, state) {
|
|
|
1281
1870
|
if (node.tagType === TAG_COMPONENT) state.components.push(element);
|
|
1282
1871
|
else if (node.tagType === TAG_ELEMENT) state.elements.push(element);
|
|
1283
1872
|
}
|
|
1284
|
-
if (node.type === NODE_INTERPOLATION && node.content?.content)
|
|
1873
|
+
if (node.type === NODE_INTERPOLATION && node.content?.content) {
|
|
1874
|
+
const interpolation = templateExpression(state.expressionPath, node.content.content);
|
|
1875
|
+
state.moduleClosureBroken ||= interpolation?.usesCssModule ?? true;
|
|
1876
|
+
state.referencesUseCssModule ||= interpolation?.referencesUseCssModule ?? false;
|
|
1877
|
+
for (const name of interpolation?.references ?? []) state.expressionReferences.add(name);
|
|
1878
|
+
}
|
|
1285
1879
|
for (const child of node.children ?? []) visitTemplateNode(source, child, state);
|
|
1286
1880
|
}
|
|
1287
1881
|
function attributeRemovalStart(source, node, prop) {
|
|
@@ -1290,10 +1884,23 @@ function attributeRemovalStart(source, node, prop) {
|
|
|
1290
1884
|
while (start > node.loc.start.offset + 1 && /\s/.test(source[start - 1])) start -= 1;
|
|
1291
1885
|
return /[\r\n]/.test(source.slice(start, attributeStart)) ? attributeStart : start;
|
|
1292
1886
|
}
|
|
1293
|
-
function
|
|
1294
|
-
|
|
1887
|
+
function templateExpression(path, source) {
|
|
1888
|
+
try {
|
|
1889
|
+
return expressionAnalysis(path, source);
|
|
1890
|
+
} catch {
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function templateHandler(source) {
|
|
1295
1895
|
try {
|
|
1296
|
-
|
|
1896
|
+
const analysis = sourceAnalysis("Component.handler.ts", `async function handler($event) { ${source} }`);
|
|
1897
|
+
return {
|
|
1898
|
+
staticString: null,
|
|
1899
|
+
vueModuleMember: null,
|
|
1900
|
+
usesCssModule: analysis.usesCssModule,
|
|
1901
|
+
referencesUseCssModule: analysis.hasUnboundUseCssModule,
|
|
1902
|
+
references: analysis.unboundReferences
|
|
1903
|
+
};
|
|
1297
1904
|
} catch {
|
|
1298
1905
|
return;
|
|
1299
1906
|
}
|
|
@@ -1309,7 +1916,7 @@ function templateSite(source, node, bindingClasses, classOpaque, state) {
|
|
|
1309
1916
|
}
|
|
1310
1917
|
const matchClasses = bindingClasses.length ? [...classAttribute?.value.split(/[\t\n\f\r ]+/).filter(Boolean) ?? [], ...bindingClasses] : void 0;
|
|
1311
1918
|
if (!classAttribute && (bindingClasses.length > 0 || !classOpaque && (node.tagType === TAG_COMPONENT || idAttribute))) {
|
|
1312
|
-
const insertion =
|
|
1919
|
+
const insertion = nodeClassInsertionOffset(source, node);
|
|
1313
1920
|
if (insertion === void 0) state.dynamic = true;
|
|
1314
1921
|
else classAttribute = {
|
|
1315
1922
|
value: "",
|
|
@@ -1340,22 +1947,11 @@ function literalAttribute(source, node, name) {
|
|
|
1340
1947
|
end: end - 1
|
|
1341
1948
|
};
|
|
1342
1949
|
}
|
|
1343
|
-
function
|
|
1950
|
+
function nodeClassInsertionOffset(source, node) {
|
|
1344
1951
|
const propsEnd = Math.max(node.loc.start.offset + 1 + node.tag.length, ...(node.props ?? []).map((prop) => prop.loc.end.offset));
|
|
1345
1952
|
const tagEnd = source.indexOf(">", propsEnd);
|
|
1346
1953
|
if (tagEnd < 0) return void 0;
|
|
1347
|
-
|
|
1348
|
-
if (source[offset - 1] === "/") offset -= 1;
|
|
1349
|
-
while (offset > node.loc.start.offset && /\s/.test(source[offset - 1])) offset -= 1;
|
|
1350
|
-
return offset;
|
|
1351
|
-
}
|
|
1352
|
-
function toByteWarnings(source, warnings) {
|
|
1353
|
-
const offsets = utf8OffsetMap(source, warnings.flatMap((warning) => [warning.start, warning.end]));
|
|
1354
|
-
return warnings.map((warning) => ({
|
|
1355
|
-
...warning,
|
|
1356
|
-
start: offsets.get(warning.start) ?? warning.start,
|
|
1357
|
-
end: offsets.get(warning.end) ?? warning.end
|
|
1358
|
-
}));
|
|
1954
|
+
return classInsertionOffset(source, node.loc.start.offset, tagEnd);
|
|
1359
1955
|
}
|
|
1360
1956
|
//#endregion
|
|
1361
1957
|
//#region src/plan/vue.ts
|
|
@@ -1375,12 +1971,12 @@ function vueReferenceTarget(importer, reference, vuePaths) {
|
|
|
1375
1971
|
join(target, "index.vue")
|
|
1376
1972
|
].find((path) => vuePaths.has(path));
|
|
1377
1973
|
}
|
|
1378
|
-
function
|
|
1379
|
-
return new Set(
|
|
1380
|
-
if (/[?*[\]{}]/.test(
|
|
1381
|
-
const target = vueReferenceTarget(
|
|
1974
|
+
function vueGlobTargets(importer, patterns, vuePaths) {
|
|
1975
|
+
return new Set(patterns.flatMap((pattern) => {
|
|
1976
|
+
if (/[?*[\]{}]/.test(pattern)) return pattern.includes("vue") || isLocalVueReference(pattern) ? [...vuePaths] : [];
|
|
1977
|
+
const target = vueReferenceTarget(importer, pattern, vuePaths);
|
|
1382
1978
|
if (target) return [target];
|
|
1383
|
-
return
|
|
1979
|
+
return pattern.includes(".vue") ? [...vuePaths] : [];
|
|
1384
1980
|
}));
|
|
1385
1981
|
}
|
|
1386
1982
|
function normalizedVueTag(value) {
|
|
@@ -1427,28 +2023,48 @@ function buildVueComponentGraph(ownedVue, sourceFiles, analyses, { styleSources,
|
|
|
1427
2023
|
if (analysis.componentsOpen) for (const target of vuePaths) callerOpen.add(target);
|
|
1428
2024
|
}
|
|
1429
2025
|
for (const file of sourceFiles) {
|
|
2026
|
+
if (extname(file.path) === ".html") continue;
|
|
1430
2027
|
let references;
|
|
2028
|
+
let vueReferences;
|
|
1431
2029
|
let blockReferences = [];
|
|
2030
|
+
let hasDynamicImport = false;
|
|
2031
|
+
let vueGlobPatterns = [];
|
|
2032
|
+
let vueGlobUnverifiable = false;
|
|
1432
2033
|
if (extname(file.path) === ".vue") {
|
|
1433
2034
|
const fileAnalysis = analyses.get(file.path);
|
|
1434
2035
|
if (fileAnalysis && !fileAnalysis.retained) {
|
|
1435
2036
|
references = fileAnalysis.scriptStyleImports;
|
|
2037
|
+
vueReferences = fileAnalysis.scriptVueReferences;
|
|
1436
2038
|
blockReferences = fileAnalysis.styleBlockImports.map((entry) => entry.reference);
|
|
1437
|
-
|
|
2039
|
+
hasDynamicImport = fileAnalysis.scriptHasDynamicImport || fileAnalysis.scriptImportsUnverifiable;
|
|
2040
|
+
vueGlobPatterns = fileAnalysis.scriptVueGlobPatterns;
|
|
2041
|
+
vueGlobUnverifiable = fileAnalysis.scriptVueGlobUnverifiable;
|
|
2042
|
+
} else {
|
|
2043
|
+
references = [];
|
|
2044
|
+
vueReferences = [];
|
|
2045
|
+
hasDynamicImport = true;
|
|
2046
|
+
}
|
|
1438
2047
|
} else try {
|
|
1439
|
-
|
|
2048
|
+
const analysis = sourceAnalysis(file.path, file.source);
|
|
2049
|
+
references = analysis.staticImports;
|
|
2050
|
+
vueReferences = analysis.imports.filter((record) => !record.typeOnly).map((record) => record.specifier);
|
|
2051
|
+
hasDynamicImport = analysis.hasDynamicImport;
|
|
2052
|
+
vueGlobPatterns = analysis.vueGlobPatterns;
|
|
2053
|
+
vueGlobUnverifiable = analysis.vueGlobUnverifiable;
|
|
1440
2054
|
} catch {
|
|
1441
2055
|
references = [];
|
|
2056
|
+
vueReferences = [];
|
|
2057
|
+
hasDynamicImport = true;
|
|
1442
2058
|
}
|
|
1443
|
-
if (
|
|
2059
|
+
if (hasDynamicImport || vueGlobUnverifiable || vueReferences.some((reference) => !stylesheetReference.test(reference) && isLocalVueReference(reference) && couldResolveToVue(reference) && !vueReferenceTarget(file.path, reference, vuePaths))) for (const target of vuePaths) callerOpen.add(target);
|
|
1444
2060
|
const resolvesOwned = (reference) => stylesheetReferenceTargets(file.path, reference, styleSources).some((path) => pathOwners.get(path) === packageRoot);
|
|
1445
2061
|
if (pathOwners.get(file.path) === packageRoot && (references.some((reference) => stylesheetReference.test(reference) && (!reference.startsWith(".") || !resolvesOwned(reference))) || blockReferences.some((reference) => !resolvesOwned(reference)))) unresolvedStyleImport = true;
|
|
1446
|
-
const imported = new Set(
|
|
2062
|
+
const imported = new Set(vueReferences.flatMap((reference) => {
|
|
1447
2063
|
const target = vueReferenceTarget(file.path, reference, vuePaths);
|
|
1448
2064
|
return target ? [target] : [];
|
|
1449
2065
|
}));
|
|
1450
2066
|
for (const target of imported) if (extname(file.path) !== ".vue" || !analyses.get(file.path)?.setupImports?.has(target)) callerOpen.add(target);
|
|
1451
|
-
for (const target of
|
|
2067
|
+
for (const target of vueGlobTargets(file.path, vueGlobPatterns, vuePaths)) if (!imported.has(target)) callerOpen.add(target);
|
|
1452
2068
|
}
|
|
1453
2069
|
return {
|
|
1454
2070
|
callers,
|
|
@@ -1456,15 +2072,16 @@ function buildVueComponentGraph(ownedVue, sourceFiles, analyses, { styleSources,
|
|
|
1456
2072
|
unresolvedStyleImport
|
|
1457
2073
|
};
|
|
1458
2074
|
}
|
|
1459
|
-
function vueWarningsOnlyResult(preparedVue) {
|
|
1460
|
-
|
|
2075
|
+
function vueWarningsOnlyResult(preparedVue, extraWarnings = []) {
|
|
2076
|
+
const warnings = [...preparedVue.warnings, ...extraWarnings];
|
|
2077
|
+
if (warnings.length === 0) return {};
|
|
1461
2078
|
return { plan: {
|
|
1462
2079
|
files: [],
|
|
1463
2080
|
deletedFiles: [],
|
|
1464
2081
|
unlinkedFiles: [],
|
|
1465
2082
|
candidates: [],
|
|
1466
2083
|
rules: [],
|
|
1467
|
-
warnings
|
|
2084
|
+
warnings,
|
|
1468
2085
|
convertedRules: 0,
|
|
1469
2086
|
retainedRules: 0
|
|
1470
2087
|
} };
|
|
@@ -1473,6 +2090,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1473
2090
|
const none = {
|
|
1474
2091
|
files: /* @__PURE__ */ new Map(),
|
|
1475
2092
|
stylesheets: [],
|
|
2093
|
+
styleRanges: /* @__PURE__ */ new Map(),
|
|
1476
2094
|
stylePaths: /* @__PURE__ */ new Set(),
|
|
1477
2095
|
unscopedPaths: /* @__PURE__ */ new Set(),
|
|
1478
2096
|
warnings: [],
|
|
@@ -1545,13 +2163,17 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1545
2163
|
pathOwners,
|
|
1546
2164
|
packageRoot
|
|
1547
2165
|
});
|
|
1548
|
-
const generatesSelectors = (text) => /#\{|@\{|&[\w-]/.test(text);
|
|
1549
2166
|
const vueShadowCss = [];
|
|
1550
2167
|
const vueShadowModuleCss = [];
|
|
1551
2168
|
let vueShadowUnverifiable = vueGraph.unresolvedStyleImport;
|
|
1552
2169
|
for (const [path, source] of styleSources) {
|
|
1553
2170
|
if (pathOwners.get(path) !== packageRoot) continue;
|
|
1554
|
-
|
|
2171
|
+
try {
|
|
2172
|
+
if (stylesheetAnalysis(path, source).selectorsUnverifiable) {
|
|
2173
|
+
vueShadowUnverifiable = true;
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
} catch {
|
|
1555
2177
|
vueShadowUnverifiable = true;
|
|
1556
2178
|
continue;
|
|
1557
2179
|
}
|
|
@@ -1561,21 +2183,23 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1561
2183
|
source
|
|
1562
2184
|
});
|
|
1563
2185
|
}
|
|
1564
|
-
for (const file of sourceFiles) if (extname(file.path) === ".html" && pathOwners.get(file.path) === packageRoot
|
|
2186
|
+
for (const file of sourceFiles) if (extname(file.path) === ".html" && pathOwners.get(file.path) === packageRoot) try {
|
|
2187
|
+
if (parseHtmlSource(file.path, file.source).hasStyle) vueShadowUnverifiable = true;
|
|
2188
|
+
} catch {
|
|
2189
|
+
vueShadowUnverifiable = true;
|
|
2190
|
+
}
|
|
1565
2191
|
for (const file of ownedVue) {
|
|
1566
2192
|
const analysis = analysisOf(file.path);
|
|
1567
2193
|
if (analysis.retained) {
|
|
1568
|
-
if (
|
|
2194
|
+
if (analysis.styleRanges.length > 0) vueShadowUnverifiable = true;
|
|
1569
2195
|
continue;
|
|
1570
2196
|
}
|
|
1571
2197
|
if (analysis.escapeUnverifiable || analysis.scriptImportsUnverifiable) vueShadowUnverifiable = true;
|
|
1572
|
-
for (const text of analysis.shadowPreprocessorTexts)
|
|
1573
|
-
else vueShadowCss.push({
|
|
2198
|
+
for (const text of analysis.shadowPreprocessorTexts) vueShadowCss.push({
|
|
1574
2199
|
path: file.path,
|
|
1575
2200
|
source: text
|
|
1576
2201
|
});
|
|
1577
|
-
for (const text of analysis.unscopedShadowPreprocessorTexts)
|
|
1578
|
-
else vueShadowCss.push({
|
|
2202
|
+
for (const text of analysis.unscopedShadowPreprocessorTexts) vueShadowCss.push({
|
|
1579
2203
|
path: file.path,
|
|
1580
2204
|
source: text,
|
|
1581
2205
|
migratingUnscoped: true
|
|
@@ -1589,7 +2213,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1589
2213
|
migratingUnscoped: true
|
|
1590
2214
|
})), ...analysis.blocks.map((block) => ({
|
|
1591
2215
|
path: file.path,
|
|
1592
|
-
source: block.analysisSource ?? block.content,
|
|
2216
|
+
source: block.shadowSource ?? block.analysisSource ?? block.content,
|
|
1593
2217
|
scoped: true
|
|
1594
2218
|
})));
|
|
1595
2219
|
vueShadowModuleCss.push(...analysis.shadowModuleCssTexts, ...analysis.moduleBlocks.map((block) => block.analysisSource ?? block.content));
|
|
@@ -1630,7 +2254,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1630
2254
|
for (const path of importedStyles) {
|
|
1631
2255
|
stylePaths.add(path);
|
|
1632
2256
|
if (extname(path) !== ".css") continue;
|
|
1633
|
-
for (const imported of cssImports(styleSources.get(path) ?? "")) {
|
|
2257
|
+
for (const imported of cssImports(path, styleSources.get(path) ?? "")) {
|
|
1634
2258
|
if (imported.media) continue;
|
|
1635
2259
|
for (const target of stylesheetReferenceTargets(path, imported.href, styleSources)) if (pathOwners.get(target) === packageRoot) importedStyles.add(target);
|
|
1636
2260
|
}
|
|
@@ -1715,6 +2339,19 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1715
2339
|
message
|
|
1716
2340
|
});
|
|
1717
2341
|
}
|
|
2342
|
+
const allStyleBlocks = [
|
|
2343
|
+
...analysis.blocks,
|
|
2344
|
+
...analysis.moduleBlocks,
|
|
2345
|
+
...analysis.unscopedBlocks
|
|
2346
|
+
];
|
|
2347
|
+
let vueAtRuleIdentities;
|
|
2348
|
+
if (analysis.hasOpaqueStyleBlocks) vueAtRuleIdentities = null;
|
|
2349
|
+
else try {
|
|
2350
|
+
const registrations = allStyleBlocks.map((block) => stylesheetAnalysis(`${file.path}.${block.analysisSource ? "css" : block.syntax}`, block.analysisSource ?? block.content));
|
|
2351
|
+
vueAtRuleIdentities = registrations.some((entry) => entry.globalAtRulesUnverifiable) ? null : registrations.flatMap((entry) => entry.globalAtRuleIdentities);
|
|
2352
|
+
} catch {
|
|
2353
|
+
vueAtRuleIdentities = null;
|
|
2354
|
+
}
|
|
1718
2355
|
const vueBlocks = migrateUnscoped ? analysis.unscopedBlocks : analysis.blocks;
|
|
1719
2356
|
if (vueBlocks.length === 0 && !migrateModule) continue;
|
|
1720
2357
|
const vueRetention = migrateUnscoped ? void 0 : retention;
|
|
@@ -1722,19 +2359,20 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1722
2359
|
if (migrateModule) stylesheets.push({
|
|
1723
2360
|
cssPath: file.path,
|
|
1724
2361
|
cssSource: file.source,
|
|
1725
|
-
cssModuleId: normalizedRelativePath(
|
|
2362
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, file.path),
|
|
1726
2363
|
syntax: "css",
|
|
1727
2364
|
isModule: true,
|
|
1728
2365
|
vueBlocks: analysis.moduleBlocks,
|
|
1729
2366
|
vueModule: true,
|
|
1730
2367
|
vueShadowCss: shadowCss,
|
|
1731
2368
|
vueShadowModuleCss,
|
|
1732
|
-
vueShadowUnverifiable
|
|
2369
|
+
vueShadowUnverifiable,
|
|
2370
|
+
vueAtRuleIdentities
|
|
1733
2371
|
});
|
|
1734
2372
|
if (vueBlocks.length > 0) stylesheets.push({
|
|
1735
2373
|
cssPath: file.path,
|
|
1736
2374
|
cssSource: file.source,
|
|
1737
|
-
cssModuleId: normalizedRelativePath(
|
|
2375
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, file.path),
|
|
1738
2376
|
syntax: "css",
|
|
1739
2377
|
isModule: !vueRetention,
|
|
1740
2378
|
vueBlocks,
|
|
@@ -1742,7 +2380,8 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1742
2380
|
vueUnscoped: migrateUnscoped,
|
|
1743
2381
|
vueShadowCss: vueRetention ? void 0 : shadowCss,
|
|
1744
2382
|
vueShadowModuleCss: vueRetention ? void 0 : vueShadowModuleCss,
|
|
1745
|
-
vueShadowUnverifiable: vueRetention ? void 0 : vueShadowUnverifiable
|
|
2383
|
+
vueShadowUnverifiable: vueRetention ? void 0 : vueShadowUnverifiable,
|
|
2384
|
+
vueAtRuleIdentities
|
|
1746
2385
|
});
|
|
1747
2386
|
}
|
|
1748
2387
|
for (const file of ownedVue) {
|
|
@@ -1750,24 +2389,28 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1750
2389
|
if (analysis.retained) continue;
|
|
1751
2390
|
const elements = elementsByFile.get(file.path) ?? [];
|
|
1752
2391
|
const contextPaths = [...new Set(elements.flatMap((element) => element.cssPaths))];
|
|
1753
|
-
if (contextPaths.length
|
|
1754
|
-
await rejectSymlinkTarget(file.path, packageRoot);
|
|
2392
|
+
if (contextPaths.length > 0) await rejectSymlinkTarget(file.path, packageRoot);
|
|
1755
2393
|
files.set(file.path, {
|
|
1756
2394
|
...file,
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
cssPath
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2395
|
+
...contextPaths.length > 0 ? {
|
|
2396
|
+
htmlElements: elements,
|
|
2397
|
+
htmlStylesheets: contextPaths.map((cssPath) => ({
|
|
2398
|
+
cssPath,
|
|
2399
|
+
variants: [],
|
|
2400
|
+
direct: cssPath === file.path,
|
|
2401
|
+
analyzable: true
|
|
2402
|
+
})),
|
|
2403
|
+
htmlReferencesSafe: !analysis.dynamic,
|
|
2404
|
+
htmlScriptText: analysis.scriptText
|
|
2405
|
+
} : {},
|
|
2406
|
+
sourceImports: analysis.scriptImports,
|
|
2407
|
+
sourceImportsUnverifiable: analysis.scriptImportsUnverifiable
|
|
1766
2408
|
});
|
|
1767
2409
|
}
|
|
1768
2410
|
return {
|
|
1769
2411
|
files,
|
|
1770
2412
|
stylesheets,
|
|
2413
|
+
styleRanges: new Map([...analyses].map(([path, analysis]) => [path, analysis.styleRanges])),
|
|
1771
2414
|
stylePaths,
|
|
1772
2415
|
unscopedPaths,
|
|
1773
2416
|
warnings,
|
|
@@ -1783,7 +2426,7 @@ async function verifySnapshots(snapshots) {
|
|
|
1783
2426
|
const read = reads[index];
|
|
1784
2427
|
if (read.status === "rejected") {
|
|
1785
2428
|
const error = read.reason;
|
|
1786
|
-
const detail = errorCode(error) ?? (error
|
|
2429
|
+
const detail = errorCode(error) ?? errorMessage(error);
|
|
1787
2430
|
throw new Error(`Source changed after planning: ${path} (${detail})`);
|
|
1788
2431
|
}
|
|
1789
2432
|
if (read.value !== before) throw new Error(`Source changed after planning: ${path}`);
|
|
@@ -1818,6 +2461,38 @@ async function writeChanges(changes, deletions) {
|
|
|
1818
2461
|
}
|
|
1819
2462
|
//#endregion
|
|
1820
2463
|
//#region src/index.ts
|
|
2464
|
+
function indexWarningPositions(source, offsets, unicodeSeparatorsAreNewlines, formFeedIsNewline, initial = {
|
|
2465
|
+
line: 1,
|
|
2466
|
+
column: 1
|
|
2467
|
+
}) {
|
|
2468
|
+
const targets = [...offsets].sort((left, right) => left - right);
|
|
2469
|
+
const positions = /* @__PURE__ */ new Map();
|
|
2470
|
+
let target = 0;
|
|
2471
|
+
let byte = 0;
|
|
2472
|
+
let { line, column } = initial;
|
|
2473
|
+
let previousWasCarriageReturn = false;
|
|
2474
|
+
const record = () => {
|
|
2475
|
+
while (targets[target] === byte) {
|
|
2476
|
+
positions.set(byte, {
|
|
2477
|
+
line,
|
|
2478
|
+
column
|
|
2479
|
+
});
|
|
2480
|
+
target += 1;
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
record();
|
|
2484
|
+
for (const character of source) {
|
|
2485
|
+
byte += Buffer.byteLength(character);
|
|
2486
|
+
while ((targets[target] ?? Infinity) < byte) target += 1;
|
|
2487
|
+
if (character === "\r" || character === "\n" && !previousWasCarriageReturn || unicodeSeparatorsAreNewlines && (character === "\u2028" || character === "\u2029") || formFeedIsNewline && character === "\f") {
|
|
2488
|
+
line += 1;
|
|
2489
|
+
column = 1;
|
|
2490
|
+
} else if (character !== "\n") column += character.length;
|
|
2491
|
+
previousWasCarriageReturn = character === "\r";
|
|
2492
|
+
record();
|
|
2493
|
+
}
|
|
2494
|
+
return positions;
|
|
2495
|
+
}
|
|
1821
2496
|
async function migrate(options = {}) {
|
|
1822
2497
|
if (options.styleFile && options.workspaces) throw new TypeError("styleFile cannot be combined with workspaces");
|
|
1823
2498
|
if (options.tailwindCss && options.workspaces) throw new TypeError("tailwindCss cannot be combined with workspaces");
|
|
@@ -1835,7 +2510,13 @@ async function migrate(options = {}) {
|
|
|
1835
2510
|
Promise.all(stylePaths.map(async (path) => [path, await snapshotFile(snapshots, path)])).then((entries) => new Map(entries)),
|
|
1836
2511
|
Promise.all(sourcePaths.map(async (path) => {
|
|
1837
2512
|
const source = await readFile(path, "utf8");
|
|
1838
|
-
const mayReferenceModule = extname(path) !== ".html" ||
|
|
2513
|
+
const mayReferenceModule = extname(path) !== ".html" || (() => {
|
|
2514
|
+
try {
|
|
2515
|
+
return parseHtmlSource(path, source).links.length > 0;
|
|
2516
|
+
} catch {
|
|
2517
|
+
return /<link\b/i.test(source);
|
|
2518
|
+
}
|
|
2519
|
+
})();
|
|
1839
2520
|
if (!scope.targetable.has(path) && !mayReferenceModule) return void 0;
|
|
1840
2521
|
return {
|
|
1841
2522
|
path,
|
|
@@ -1847,21 +2528,35 @@ async function migrate(options = {}) {
|
|
|
1847
2528
|
const sourceFiles = sourceCandidates.flatMap((file) => file ? [file] : []);
|
|
1848
2529
|
if (explicitStyle && isStylesheetPath(explicitStyle) && !styleSources.has(explicitStyle)) styleSources.set(explicitStyle, await snapshotFile(snapshots, explicitStyle));
|
|
1849
2530
|
if (configuredEntry && !styleSources.has(configuredEntry)) styleSources.set(configuredEntry, await snapshotFile(snapshots, configuredEntry));
|
|
2531
|
+
const vueStyleRanges = /* @__PURE__ */ new Map();
|
|
1850
2532
|
const context = {
|
|
1851
2533
|
...scope,
|
|
1852
2534
|
options,
|
|
1853
2535
|
snapshots,
|
|
1854
2536
|
styleSources,
|
|
1855
2537
|
sourceFiles,
|
|
1856
|
-
styleDependents: indexStylesheetDependents(styleSources)
|
|
2538
|
+
styleDependents: indexStylesheetDependents(styleSources),
|
|
2539
|
+
vueStyleRanges,
|
|
2540
|
+
entryCatalog: tailwindEntryCatalog(styleSources, scope.pathOwners),
|
|
2541
|
+
ignoredPaths: options.workspaces ? await scannerIgnoredPaths(workspaceRoot, sourceFiles.map((file) => file.path)) : /* @__PURE__ */ new Set()
|
|
1857
2542
|
};
|
|
1858
2543
|
const failures = [];
|
|
1859
2544
|
const plans = [];
|
|
2545
|
+
const prepared = [];
|
|
1860
2546
|
for (const packageRoot of selectedPackages) {
|
|
1861
|
-
const
|
|
1862
|
-
if (
|
|
1863
|
-
else if (
|
|
1864
|
-
|
|
2547
|
+
const preparation = await preparePackage(context, packageRoot);
|
|
2548
|
+
if (preparation.failure) failures.push(preparation.failure);
|
|
2549
|
+
else if (preparation.plan) plans.push(preparation.plan);
|
|
2550
|
+
if (preparation.prepared) prepared.push(preparation.prepared);
|
|
2551
|
+
}
|
|
2552
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2553
|
+
for (const preparation of prepared) {
|
|
2554
|
+
const members = groups.get(preparation.tailwind.path) ?? [];
|
|
2555
|
+
members.push(preparation);
|
|
2556
|
+
groups.set(preparation.tailwind.path, members);
|
|
2557
|
+
}
|
|
2558
|
+
for (const [, members] of [...groups].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) for (const result of await planPreparedGroup(context, members)) if (result.failure) failures.push(result.failure);
|
|
2559
|
+
else if (result.plan) plans.push(result.plan);
|
|
1865
2560
|
const originals = new Map([...styleSources, ...sourceFiles.map((file) => [file.path, file.source])]);
|
|
1866
2561
|
const { filesByPath, deletedPaths, candidates, rules, warnings, convertedRules, retainedRules } = mergePlans(plans, originals);
|
|
1867
2562
|
const changed = [...filesByPath.values()].map((file) => ({
|
|
@@ -1880,6 +2575,42 @@ async function migrate(options = {}) {
|
|
|
1880
2575
|
await writeChanges(changed, deleted);
|
|
1881
2576
|
}
|
|
1882
2577
|
warnings.sort((left, right) => left.file.localeCompare(right.file) || left.start - right.start || left.end - right.end || left.code.localeCompare(right.code));
|
|
2578
|
+
const warningOffsets = /* @__PURE__ */ new Map();
|
|
2579
|
+
for (const warning of warnings) {
|
|
2580
|
+
if (warning.start === 0 && warning.end === 0) continue;
|
|
2581
|
+
const offsets = warningOffsets.get(warning.file) ?? /* @__PURE__ */ new Set();
|
|
2582
|
+
offsets.add(warning.start).add(warning.end);
|
|
2583
|
+
warningOffsets.set(warning.file, offsets);
|
|
2584
|
+
}
|
|
2585
|
+
const warningPositions = new Map([...warningOffsets].flatMap(([file, offsets]) => {
|
|
2586
|
+
const source = snapshots.get(file);
|
|
2587
|
+
if (!source) return [];
|
|
2588
|
+
const extension = extname(file);
|
|
2589
|
+
const javascriptSource = SOURCE_EXTENSIONS.has(extension) && extension !== ".html" && extension !== ".vue";
|
|
2590
|
+
const styleRanges = vueStyleRanges.get(file) ?? [];
|
|
2591
|
+
const defaultOffsets = new Set(offsets);
|
|
2592
|
+
for (const range of styleRanges) defaultOffsets.add(range.start);
|
|
2593
|
+
const defaultPositions = indexWarningPositions(source, defaultOffsets, javascriptSource, isStylesheetPath(file));
|
|
2594
|
+
const bytes = Buffer.from(source);
|
|
2595
|
+
const sortedOffsets = [...offsets].sort((left, right) => left - right);
|
|
2596
|
+
const stylePositions = /* @__PURE__ */ new Map();
|
|
2597
|
+
let offsetIndex = 0;
|
|
2598
|
+
for (const range of styleRanges) {
|
|
2599
|
+
while ((sortedOffsets[offsetIndex] ?? Infinity) < range.start) offsetIndex += 1;
|
|
2600
|
+
const relativeOffsets = /* @__PURE__ */ new Set();
|
|
2601
|
+
while ((sortedOffsets[offsetIndex] ?? Infinity) <= range.end) {
|
|
2602
|
+
relativeOffsets.add(sortedOffsets[offsetIndex] - range.start);
|
|
2603
|
+
offsetIndex += 1;
|
|
2604
|
+
}
|
|
2605
|
+
const initial = defaultPositions.get(range.start);
|
|
2606
|
+
if (!initial) continue;
|
|
2607
|
+
for (const [offset, position] of indexWarningPositions(bytes.subarray(range.start, range.end).toString(), relativeOffsets, false, true, initial)) stylePositions.set(range.start + offset, position);
|
|
2608
|
+
}
|
|
2609
|
+
return [[file, {
|
|
2610
|
+
default: defaultPositions,
|
|
2611
|
+
style: stylePositions
|
|
2612
|
+
}]];
|
|
2613
|
+
}));
|
|
1883
2614
|
failures.sort((left, right) => left.package.localeCompare(right.package));
|
|
1884
2615
|
return {
|
|
1885
2616
|
changedFiles,
|
|
@@ -1891,15 +2622,29 @@ async function migrate(options = {}) {
|
|
|
1891
2622
|
file: normalizedRelativePath(cwd, rule.file)
|
|
1892
2623
|
})),
|
|
1893
2624
|
candidates: [...candidates].sort(),
|
|
1894
|
-
warnings: warnings.map((warning) =>
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2625
|
+
warnings: warnings.map((warning) => {
|
|
2626
|
+
const indexed = warningPositions.get(warning.file);
|
|
2627
|
+
const styleFrom = indexed?.style.get(warning.start);
|
|
2628
|
+
const styleTo = indexed?.style.get(warning.end);
|
|
2629
|
+
const located = warning.start !== 0 || warning.end !== 0;
|
|
2630
|
+
const from = located ? styleFrom ?? indexed?.default.get(warning.start) : void 0;
|
|
2631
|
+
const to = located ? styleFrom && styleTo ? styleTo : indexed?.default.get(warning.end) : void 0;
|
|
2632
|
+
return {
|
|
2633
|
+
...warning,
|
|
2634
|
+
...from && to ? {
|
|
2635
|
+
line: from.line,
|
|
2636
|
+
column: from.column,
|
|
2637
|
+
endLine: to.line,
|
|
2638
|
+
endColumn: to.column
|
|
2639
|
+
} : {},
|
|
2640
|
+
file: normalizedRelativePath(cwd, warning.file)
|
|
2641
|
+
};
|
|
2642
|
+
}),
|
|
1898
2643
|
failures
|
|
1899
2644
|
};
|
|
1900
2645
|
}
|
|
1901
|
-
async function
|
|
1902
|
-
const { options, snapshots, workspaceRoot, explicitStyle, configuredEntry, styleSources, sourceFiles, styleDependents, pathOwners, targetable, selectedPackages, scannedPaths } = context;
|
|
2646
|
+
async function preparePackage(context, packageRoot) {
|
|
2647
|
+
const { options, snapshots, workspaceRoot, explicitStyle, configuredEntry, styleSources, sourceFiles, styleDependents, pathOwners, targetable, selectedPackages, scannedPaths, vueStyleRanges } = context;
|
|
1903
2648
|
const recover = (error, fatal = false) => {
|
|
1904
2649
|
if (!options.force || fatal) throw error;
|
|
1905
2650
|
return { failure: packageFailure(workspaceRoot, packageRoot, error) };
|
|
@@ -1936,25 +2681,96 @@ async function planPackage(context, packageRoot) {
|
|
|
1936
2681
|
} catch (error) {
|
|
1937
2682
|
return recover(error, isIntegrityError(error));
|
|
1938
2683
|
}
|
|
2684
|
+
for (const [path, preparedRanges] of preparedVue.styleRanges) {
|
|
2685
|
+
const ranges = vueStyleRanges.get(path) ?? [];
|
|
2686
|
+
ranges.push(...preparedRanges);
|
|
2687
|
+
ranges.sort((left, right) => left.start - right.start);
|
|
2688
|
+
vueStyleRanges.set(path, ranges);
|
|
2689
|
+
}
|
|
1939
2690
|
if (explicitStyle && extname(explicitStyle) === ".vue" && preparedVue.stylesheets.length === 0 && ![...preparedVue.stylePaths].some((path) => !isStylesheetModule(path))) return vueWarningsOnlyResult(preparedVue);
|
|
1940
2691
|
const packageSources = [...sourceFiles.filter((file) => extname(file.path) !== ".html").map((file) => preparedVue.files.get(file.path) ?? file), ...preparedHtml.files];
|
|
1941
2692
|
const ownedStyles = [...styleSources.keys()].filter((path) => pathOwners.get(path) === packageRoot && (targetable.has(path) || preparedHtml.stylePaths.has(path) || preparedVue.stylePaths.has(path)));
|
|
1942
2693
|
if (ownedStyles.length === 0 && preparedVue.stylesheets.length === 0) return vueWarningsOnlyResult(preparedVue);
|
|
1943
2694
|
let tailwindPath;
|
|
1944
2695
|
let tailwindEntries;
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2696
|
+
let entryOwner = packageRoot;
|
|
2697
|
+
let sharedProofs;
|
|
2698
|
+
const ownEntries = findTailwindEntries(ownedStyles, styleSources);
|
|
2699
|
+
if (ownEntries.length === 0 && configuredEntry === void 0 && options.workspaces && options.extractMediaQueries !== false) {
|
|
2700
|
+
let packageJson = {};
|
|
2701
|
+
try {
|
|
2702
|
+
const rawPackageJson = snapshots.get(join(packageRoot, "package.json"));
|
|
2703
|
+
if (rawPackageJson !== void 0) {
|
|
2704
|
+
const parsed = JSON.parse(rawPackageJson);
|
|
2705
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) packageJson = { ...parsed };
|
|
2706
|
+
}
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
return recover(error);
|
|
2709
|
+
}
|
|
2710
|
+
const ancestors = selectedPackages.filter((root) => root !== packageRoot && isWithin(root, packageRoot)).sort((left, right) => right.length - left.length);
|
|
2711
|
+
for (const ancestor of ancestors) {
|
|
2712
|
+
const proven = [];
|
|
2713
|
+
for (const candidate of context.entryCatalog.get(ancestor) ?? []) {
|
|
2714
|
+
const proofs = proveSharedEntry({
|
|
2715
|
+
packageRoot,
|
|
2716
|
+
entry: candidate,
|
|
2717
|
+
entrySource: styleSources.get(candidate) ?? "",
|
|
2718
|
+
styleSources,
|
|
2719
|
+
packageSources,
|
|
2720
|
+
owned: (path) => pathOwners.get(path) === packageRoot,
|
|
2721
|
+
writable: (path) => targetable.has(path) && pathOwners.get(path) === packageRoot,
|
|
2722
|
+
packageJson,
|
|
2723
|
+
ignoredPaths: context.ignoredPaths
|
|
2724
|
+
});
|
|
2725
|
+
if (proofs) proven.push({
|
|
2726
|
+
entry: candidate,
|
|
2727
|
+
proofs
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
if (proven.length > 1) return recover(/* @__PURE__ */ new Error("Multiple proven ancestor Tailwind entries were found. Pass --tailwind-css."));
|
|
2731
|
+
if (proven.length === 1) {
|
|
2732
|
+
[{entry: tailwindPath, proofs: sharedProofs}] = proven;
|
|
2733
|
+
entryOwner = ancestor;
|
|
2734
|
+
break;
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
if (sharedProofs && tailwindPath !== void 0) tailwindEntries = [];
|
|
2739
|
+
else {
|
|
2740
|
+
tailwindEntries = ownEntries;
|
|
2741
|
+
try {
|
|
2742
|
+
tailwindPath = selectTailwindEntry(ownEntries, configuredEntry);
|
|
2743
|
+
} catch (error) {
|
|
2744
|
+
return recover(error);
|
|
2745
|
+
}
|
|
1949
2746
|
}
|
|
1950
2747
|
const excludedEntries = /* @__PURE__ */ new Set([...tailwindEntries, tailwindPath]);
|
|
1951
2748
|
const explicitCss = explicitStyle && extname(explicitStyle) !== ".vue" ? explicitStyle : void 0;
|
|
1952
|
-
const targets = explicitCss ? [explicitCss] : explicitStyle ? [...preparedVue.stylePaths].filter((path) => !isStylesheetModule(path) && !excludedEntries.has(path)) : ownedStyles.filter((path) => !excludedEntries.has(path) && !preparedHtml.generatedPaths.has(path) && (!isPreprocessorPath(path) || preparedHtml.stylePaths.has(path) || packageSources.some((file) =>
|
|
2749
|
+
const targets = explicitCss ? [explicitCss] : explicitStyle ? [...preparedVue.stylePaths].filter((path) => !isStylesheetModule(path) && !excludedEntries.has(path)) : ownedStyles.filter((path) => !excludedEntries.has(path) && !preparedHtml.generatedPaths.has(path) && (!isPreprocessorPath(path) || preparedHtml.stylePaths.has(path) || preparedVue.stylePaths.has(path) || packageSources.some((file) => importsStylesheet(file, path))));
|
|
1953
2750
|
if (targets.length === 0 && preparedVue.stylesheets.length === 0) return vueWarningsOnlyResult(preparedVue);
|
|
1954
2751
|
if (targets.some((path) => excludedEntries.has(path))) throw new Error("The Tailwind CSS entry cannot be migrated.");
|
|
2752
|
+
const proofWarnings = [];
|
|
2753
|
+
const unprovenFlow = (path) => ({
|
|
2754
|
+
code: "unproven-shared-entry-flow",
|
|
2755
|
+
file: path,
|
|
2756
|
+
start: 0,
|
|
2757
|
+
end: 0,
|
|
2758
|
+
message: "A consumer flow could not be proven to load the shared Tailwind entry, so the stylesheet is retained."
|
|
2759
|
+
});
|
|
2760
|
+
const provenTargets = sharedProofs ? targets.filter((path) => {
|
|
2761
|
+
const proven = sharedProofs.provenStyle(packageSources.filter((file) => importsStylesheet(file, path) || (file.htmlStylesheets ?? []).some((linked) => linked.cssPath === path)));
|
|
2762
|
+
if (!proven) proofWarnings.push(unprovenFlow(path));
|
|
2763
|
+
return proven;
|
|
2764
|
+
}) : targets;
|
|
2765
|
+
const provenVueStylesheets = sharedProofs ? preparedVue.stylesheets.filter((stylesheet) => {
|
|
2766
|
+
const proven = sharedProofs.provenStyle(packageSources.filter((file) => file.path === stylesheet.cssPath));
|
|
2767
|
+
if (!proven && !proofWarnings.some((warning) => warning.file === stylesheet.cssPath)) proofWarnings.push(unprovenFlow(stylesheet.cssPath));
|
|
2768
|
+
return proven;
|
|
2769
|
+
}) : preparedVue.stylesheets;
|
|
2770
|
+
if (provenTargets.length === 0 && provenVueStylesheets.length === 0) return vueWarningsOnlyResult(preparedVue, proofWarnings);
|
|
1955
2771
|
let tailwind;
|
|
1956
2772
|
try {
|
|
1957
|
-
tailwind = await loadTailwind(
|
|
2773
|
+
tailwind = await loadTailwind(entryOwner, tailwindPath, snapshots, workspaceRoot);
|
|
1958
2774
|
} catch (error) {
|
|
1959
2775
|
return recover(error, isIntegrityError(error));
|
|
1960
2776
|
}
|
|
@@ -1969,13 +2785,13 @@ async function planPackage(context, packageRoot) {
|
|
|
1969
2785
|
try {
|
|
1970
2786
|
stylesheets = [];
|
|
1971
2787
|
const compilerDependents = /* @__PURE__ */ new Map();
|
|
1972
|
-
for (const stylePath of
|
|
2788
|
+
for (const stylePath of provenTargets.sort()) {
|
|
1973
2789
|
await rejectSymlinkTarget(stylePath, packageRoot);
|
|
1974
2790
|
const isPartial = isSassPath(stylePath) && basename(stylePath).startsWith("_");
|
|
1975
2791
|
const stylesheet = {
|
|
1976
2792
|
cssPath: stylePath,
|
|
1977
2793
|
cssSource: styleSources.get(stylePath) ?? "",
|
|
1978
|
-
cssModuleId: normalizedRelativePath(
|
|
2794
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, stylePath),
|
|
1979
2795
|
cssDependents: styleDependents.get(stylePath) ?? [],
|
|
1980
2796
|
syntax: stylesheetSyntax(stylePath),
|
|
1981
2797
|
isModule: isStylesheetModule(stylePath),
|
|
@@ -2009,125 +2825,405 @@ async function planPackage(context, packageRoot) {
|
|
|
2009
2825
|
} catch (error) {
|
|
2010
2826
|
return recover(error, isIntegrityError(error));
|
|
2011
2827
|
}
|
|
2012
|
-
stylesheets.push(...
|
|
2013
|
-
|
|
2828
|
+
stylesheets.push(...provenVueStylesheets);
|
|
2829
|
+
return { prepared: {
|
|
2830
|
+
packageRoot,
|
|
2831
|
+
preparedHtml,
|
|
2832
|
+
preparedVue,
|
|
2833
|
+
styleCompilers,
|
|
2834
|
+
files,
|
|
2014
2835
|
stylesheets,
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2836
|
+
tailwind,
|
|
2837
|
+
sharedProofs,
|
|
2838
|
+
warnings: proofWarnings
|
|
2839
|
+
} };
|
|
2840
|
+
}
|
|
2841
|
+
async function planPreparedGroup(context, members) {
|
|
2842
|
+
const { options, workspaceRoot, targetable } = context;
|
|
2843
|
+
const results = [];
|
|
2844
|
+
let active = [...members].sort((left, right) => {
|
|
2845
|
+
const leftPath = normalizedRelativePath(workspaceRoot, left.packageRoot);
|
|
2846
|
+
const rightPath = normalizedRelativePath(workspaceRoot, right.packageRoot);
|
|
2847
|
+
return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
|
|
2848
|
+
});
|
|
2849
|
+
const entry = active[0].tailwind;
|
|
2850
|
+
const recoverGroup = (error, fatal = false) => {
|
|
2851
|
+
if (!options.force || fatal) throw error;
|
|
2852
|
+
for (const member of active) results.push({ failure: packageFailure(workspaceRoot, member.packageRoot, error) });
|
|
2853
|
+
return results;
|
|
2020
2854
|
};
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2855
|
+
const membersByRoot = new Map(active.map((member) => [member.packageRoot, member]));
|
|
2856
|
+
for (const member of options.extractMediaQueries !== false ? active : []) member.stylesheets = member.stylesheets.filter((stylesheet) => {
|
|
2857
|
+
const proven = member.files.filter((file) => context.pathOwners.get(file.path) !== member.packageRoot && (importsStylesheet(file, stylesheet.cssPath) || (file.htmlStylesheets ?? []).some((linked) => linked.cssPath === stylesheet.cssPath))).every((consumer) => {
|
|
2858
|
+
const consumerRoot = context.pathOwners.get(consumer.path);
|
|
2859
|
+
const consumerMember = consumerRoot === void 0 ? void 0 : membersByRoot.get(consumerRoot);
|
|
2860
|
+
if (consumerMember === void 0) return false;
|
|
2861
|
+
if (consumerMember.sharedProofs === void 0) return true;
|
|
2862
|
+
return consumerMember.sharedProofs.provenConsumer(consumer);
|
|
2863
|
+
});
|
|
2864
|
+
if (!proven) member.warnings.push({
|
|
2865
|
+
code: "unproven-shared-entry-flow",
|
|
2866
|
+
file: stylesheet.cssPath,
|
|
2867
|
+
start: 0,
|
|
2868
|
+
end: 0,
|
|
2869
|
+
message: "A consumer flow could not be proven to load the shared Tailwind entry, so the stylesheet is retained."
|
|
2870
|
+
});
|
|
2871
|
+
return proven;
|
|
2872
|
+
});
|
|
2873
|
+
for (const member of active.filter((remaining) => remaining.stylesheets.length === 0)) if (member.warnings.length > 0) results.push({ plan: {
|
|
2874
|
+
files: [],
|
|
2875
|
+
deletedFiles: [],
|
|
2876
|
+
unlinkedFiles: [],
|
|
2877
|
+
candidates: [],
|
|
2878
|
+
rules: [],
|
|
2879
|
+
warnings: [
|
|
2880
|
+
...member.warnings,
|
|
2881
|
+
...member.preparedHtml.warnings,
|
|
2882
|
+
...member.preparedVue.warnings
|
|
2883
|
+
],
|
|
2884
|
+
convertedRules: 0,
|
|
2885
|
+
retainedRules: 0
|
|
2886
|
+
} });
|
|
2887
|
+
active = active.filter((remaining) => remaining.stylesheets.length > 0);
|
|
2888
|
+
if (active.length === 0) return results;
|
|
2889
|
+
const groupWritable = !await entryUnsafe(entry.path, workspaceRoot, targetable);
|
|
2890
|
+
const mediaWarnings = [];
|
|
2891
|
+
let extraction;
|
|
2892
|
+
const collections = [];
|
|
2893
|
+
for (const member of active) try {
|
|
2894
|
+
collections.push(collectMemberMediaConditions(member.stylesheets, entry));
|
|
2024
2895
|
} catch (error) {
|
|
2025
|
-
|
|
2896
|
+
if (!options.force || !isRecoverablePlanningError(error)) throw error;
|
|
2897
|
+
results.push({ failure: packageFailure(workspaceRoot, member.packageRoot, error) });
|
|
2898
|
+
active = active.filter((remaining) => remaining !== member);
|
|
2899
|
+
}
|
|
2900
|
+
if (active.length === 0) return results;
|
|
2901
|
+
let names = extraction?.names;
|
|
2902
|
+
let generated = extraction?.generated ?? [];
|
|
2903
|
+
if (options.extractMediaQueries !== false) if (!groupWritable) {
|
|
2904
|
+
names = {};
|
|
2905
|
+
generated = [];
|
|
2906
|
+
if (collections.some((collection) => collection.components.length > 0)) mediaWarnings.push({
|
|
2907
|
+
code: "media-query-definition-fallback",
|
|
2908
|
+
file: entry.path,
|
|
2909
|
+
start: 0,
|
|
2910
|
+
end: 0,
|
|
2911
|
+
message: "The Tailwind entry could not be edited safely, so media behavior was preserved with arbitrary variants."
|
|
2912
|
+
});
|
|
2913
|
+
} else {
|
|
2914
|
+
try {
|
|
2915
|
+
extraction = planMediaExtraction({
|
|
2916
|
+
collections,
|
|
2917
|
+
tailwind: entry,
|
|
2918
|
+
scannedSources: context.sourceFiles.map((file) => file.source)
|
|
2919
|
+
});
|
|
2920
|
+
} catch (error) {
|
|
2921
|
+
return recoverGroup(error, !isRecoverablePlanningError(error));
|
|
2922
|
+
}
|
|
2923
|
+
names = extraction.names;
|
|
2924
|
+
generated = extraction.generated;
|
|
2026
2925
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2926
|
+
let plan = {
|
|
2927
|
+
files: [],
|
|
2928
|
+
deletedFiles: [],
|
|
2929
|
+
unlinkedFiles: [],
|
|
2930
|
+
candidates: [],
|
|
2931
|
+
rules: [],
|
|
2932
|
+
warnings: [],
|
|
2933
|
+
convertedRules: 0,
|
|
2934
|
+
retainedRules: 0
|
|
2935
|
+
};
|
|
2936
|
+
let augmented = entry.css;
|
|
2937
|
+
const blocked = /* @__PURE__ */ new Map();
|
|
2938
|
+
finalization: for (;;) {
|
|
2939
|
+
const baseIdentities = new Set(atRuleIdentities(`${entry.path}.group-base.css`, [entry.css, ...entry.graphSources.map((graphSource) => graphSource.source)].join("\n")));
|
|
2940
|
+
const sheets = active.flatMap((member) => member.stylesheets.map((stylesheet) => ({
|
|
2941
|
+
member,
|
|
2942
|
+
stylesheet
|
|
2943
|
+
})));
|
|
2944
|
+
const movable = ({ member, stylesheet }) => member.sharedProofs === void 0 && stylesheet.isModule === true && (stylesheet.syntax === void 0 || stylesheet.syntax === "css") && (stylesheet.vueBlocks === void 0 || stylesheet.vueBlocks.length === 0);
|
|
2945
|
+
for (const sheet of sheets) {
|
|
2946
|
+
if (movable(sheet)) continue;
|
|
2947
|
+
for (const identity of stylesheetAtRuleIdentities(sheet.stylesheet)) baseIdentities.add(identity);
|
|
2948
|
+
}
|
|
2949
|
+
const sheetIdentities = sheets.map((sheet) => movable(sheet) ? new Set(stylesheetAtRuleIdentities(sheet.stylesheet)) : /* @__PURE__ */ new Set());
|
|
2950
|
+
const opaqueVueRegistrations = sheets.some(({ stylesheet }) => stylesheet.vueAtRuleIdentities === null);
|
|
2951
|
+
const movesDisabled = new Set(opaqueVueRegistrations ? sheets.flatMap((sheet, index) => movable(sheet) ? [index] : []) : []);
|
|
2952
|
+
for (const [index, identities] of sheetIdentities.entries()) for (const identity of identities) {
|
|
2953
|
+
if (baseIdentities.has(identity)) movesDisabled.add(index);
|
|
2954
|
+
for (const [other, otherIdentities] of sheetIdentities.entries()) if (other !== index && otherIdentities.has(identity)) {
|
|
2955
|
+
movesDisabled.add(index);
|
|
2956
|
+
movesDisabled.add(other);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
const filesByPath = /* @__PURE__ */ new Map();
|
|
2960
|
+
const contextsByPath = /* @__PURE__ */ new Map();
|
|
2961
|
+
for (const member of active) for (const file of member.files) {
|
|
2962
|
+
if (file.htmlStylesheets !== void 0 && file.htmlStylesheets.length > 0) {
|
|
2963
|
+
const contexts = contextsByPath.get(file.path) ?? [];
|
|
2964
|
+
contexts.push(...file.htmlStylesheets);
|
|
2965
|
+
contextsByPath.set(file.path, contexts);
|
|
2966
|
+
}
|
|
2967
|
+
if (!filesByPath.has(file.path) || context.pathOwners.get(file.path) === member.packageRoot) filesByPath.set(file.path, file);
|
|
2968
|
+
}
|
|
2969
|
+
const files = [...filesByPath.values()].map((file) => {
|
|
2970
|
+
const contexts = contextsByPath.get(file.path);
|
|
2971
|
+
if (contexts === void 0) return file;
|
|
2972
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2973
|
+
const merged = contexts.filter((linked) => {
|
|
2974
|
+
const key = `${linked.cssPath}\0${linked.variants.join(",")}\0${linked.direct}\0${linked.analyzable}`;
|
|
2975
|
+
if (seen.has(key)) return false;
|
|
2976
|
+
seen.add(key);
|
|
2977
|
+
return true;
|
|
2978
|
+
});
|
|
2979
|
+
return {
|
|
2980
|
+
...file,
|
|
2981
|
+
htmlStylesheets: merged
|
|
2982
|
+
};
|
|
2983
|
+
}).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
2984
|
+
planning: for (;;) {
|
|
2985
|
+
const request = {
|
|
2986
|
+
stylesheets: sheets.map(({ member, stylesheet }, index) => {
|
|
2987
|
+
const rules = blocked.get(index);
|
|
2988
|
+
const movesOff = member.sharedProofs !== void 0 || movesDisabled.has(index);
|
|
2989
|
+
return {
|
|
2990
|
+
...stylesheet,
|
|
2991
|
+
...rules ? { blockedRules: [...rules.values()].map((blockedRule) => blockedRule.ruleId) } : {},
|
|
2992
|
+
...movesOff ? { globalAtRuleMoves: false } : {}
|
|
2993
|
+
};
|
|
2994
|
+
}),
|
|
2995
|
+
tailwindPath: entry.path,
|
|
2996
|
+
tailwindSource: entry.css,
|
|
2997
|
+
utilityPrefix: entry.designSystem.theme.prefix,
|
|
2998
|
+
themeTokens: entry.themeTokens,
|
|
2999
|
+
...names ? { mediaNames: names } : {},
|
|
3000
|
+
...groupWritable ? {} : { entryWritable: false },
|
|
3001
|
+
files
|
|
3002
|
+
};
|
|
3003
|
+
try {
|
|
3004
|
+
plan = JSON.parse(planBatchMigration(JSON.stringify(request)));
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
return recoverGroup(error, !isRecoverablePlanningError(error));
|
|
3007
|
+
}
|
|
3008
|
+
let composed = entry.css;
|
|
3009
|
+
const plannedEntry = plan.files.findIndex((file) => file.path === entry.path);
|
|
3010
|
+
if (plannedEntry >= 0) {
|
|
3011
|
+
if (!groupWritable) throw new Error(`The planner claimed an unwritable Tailwind entry: ${entry.path}`);
|
|
3012
|
+
composed = plan.files[plannedEntry].source;
|
|
3013
|
+
plan.files.splice(plannedEntry, 1);
|
|
3014
|
+
if (!composed.startsWith(entry.css)) throw new Error(`The planner rewrote the Tailwind entry instead of appending: ${entry.path}`);
|
|
3015
|
+
}
|
|
3016
|
+
const currentExtraction = names !== void 0 ? {
|
|
3017
|
+
names,
|
|
3018
|
+
generated
|
|
3019
|
+
} : void 0;
|
|
3020
|
+
const used = currentExtraction ? usedGeneratedDefinitions(currentExtraction, plan) : [];
|
|
3021
|
+
augmented = currentExtraction ? appendMediaDefinitions(composed, used) : composed;
|
|
3022
|
+
let system;
|
|
3023
|
+
try {
|
|
3024
|
+
system = augmented === entry.css ? entry.designSystem : await entry.loadWith(augmented);
|
|
3025
|
+
} catch (error) {
|
|
3026
|
+
if (isIntegrityError(error)) throw error;
|
|
3027
|
+
if (names !== void 0 && generated.length > 0) {
|
|
3028
|
+
const generatedKeys = new Set(generated.map((definition) => definition.key));
|
|
3029
|
+
names = Object.fromEntries(Object.entries(names).filter(([key]) => !generatedKeys.has(key)));
|
|
3030
|
+
generated = [];
|
|
3031
|
+
continue planning;
|
|
3032
|
+
}
|
|
3033
|
+
return recoverGroup(error);
|
|
3034
|
+
}
|
|
3035
|
+
if (currentExtraction && names !== void 0) {
|
|
3036
|
+
const prefix = entry.designSystem.theme.prefix;
|
|
3037
|
+
const rejected = new Set(used.filter(({ name }) => system.candidatesToCss([probeCandidate(prefix, name)])[0] === null).map(({ key }) => key));
|
|
3038
|
+
if (rejected.size > 0) {
|
|
3039
|
+
names = Object.fromEntries(Object.entries(names).filter(([key]) => !rejected.has(key)));
|
|
3040
|
+
generated = generated.filter(({ key }) => !rejected.has(key));
|
|
3041
|
+
continue planning;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
const failing = invalidCandidates(currentExtraction ? system : entry.designSystem, plan.candidates);
|
|
3045
|
+
if (failing.length > 0) {
|
|
3046
|
+
if (!accumulateBlockedRules(blocked, plan, failing)) throw new Error(`Tailwind did not generate CSS for candidate: ${failing[0]}`);
|
|
3047
|
+
continue planning;
|
|
3048
|
+
}
|
|
3049
|
+
break;
|
|
3050
|
+
}
|
|
3051
|
+
for (const [index, rules] of blocked) {
|
|
3052
|
+
const cssPath = sheets[index].stylesheet.cssPath;
|
|
3053
|
+
for (const { authoredSpan, candidates } of rules.values()) {
|
|
3054
|
+
const failed = [...candidates].sort().map((candidate) => `\`${candidate}\``).join(", ");
|
|
3055
|
+
plan.warnings.push({
|
|
3056
|
+
code: "candidate-compilation-failure",
|
|
3057
|
+
file: cssPath,
|
|
3058
|
+
start: authoredSpan.start,
|
|
3059
|
+
end: authoredSpan.end,
|
|
3060
|
+
message: `Tailwind did not generate CSS for ${failed}, so the rule is retained.`
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
plan.warnings.push(...mediaWarnings);
|
|
3065
|
+
for (const member of active) plan.warnings.push(...member.warnings);
|
|
3066
|
+
removeGroupHtmlLinks(plan, active);
|
|
3067
|
+
const failure = await finishGroupPlan(context, active, plan);
|
|
3068
|
+
if (failure === void 0) break;
|
|
3069
|
+
if (!options.force || failure.fatal) throw failure.error;
|
|
3070
|
+
const failed = failure.member ?? active[0];
|
|
3071
|
+
results.push({ failure: packageFailure(workspaceRoot, failed.packageRoot, failure.error) });
|
|
3072
|
+
active = active.filter((member) => member !== failed);
|
|
3073
|
+
if (active.length === 0) return results;
|
|
3074
|
+
blocked.clear();
|
|
3075
|
+
plan.warnings = [];
|
|
3076
|
+
continue finalization;
|
|
3077
|
+
}
|
|
3078
|
+
const seenWarnings = /* @__PURE__ */ new Set();
|
|
3079
|
+
plan.warnings = plan.warnings.filter((warning) => {
|
|
3080
|
+
const key = `${warning.code}\0${warning.file}\0${warning.start}\0${warning.end}\0${warning.message}`;
|
|
3081
|
+
if (seenWarnings.has(key)) return false;
|
|
3082
|
+
seenWarnings.add(key);
|
|
3083
|
+
return true;
|
|
3084
|
+
});
|
|
3085
|
+
results.push({ plan });
|
|
3086
|
+
if (augmented !== entry.css) results.push({ plan: {
|
|
3087
|
+
files: [{
|
|
3088
|
+
path: entry.path,
|
|
3089
|
+
source: augmented
|
|
3090
|
+
}],
|
|
3091
|
+
deletedFiles: [],
|
|
3092
|
+
unlinkedFiles: [],
|
|
3093
|
+
candidates: [],
|
|
3094
|
+
rules: [],
|
|
3095
|
+
warnings: [],
|
|
3096
|
+
convertedRules: 0,
|
|
3097
|
+
retainedRules: 0
|
|
3098
|
+
} });
|
|
3099
|
+
return results;
|
|
3100
|
+
}
|
|
3101
|
+
function accumulateBlockedRules(blocked, plan, failing) {
|
|
3102
|
+
let progressed = false;
|
|
3103
|
+
for (const rule of plan.rules) {
|
|
3104
|
+
const failed = rule.candidates.filter((candidate) => failing.includes(candidate));
|
|
3105
|
+
if (failed.length === 0) continue;
|
|
3106
|
+
let rules = blocked.get(rule.stylesheet);
|
|
3107
|
+
if (!rules) blocked.set(rule.stylesheet, rules = /* @__PURE__ */ new Map());
|
|
3108
|
+
const key = `${rule.ruleId.start}-${rule.ruleId.end}`;
|
|
3109
|
+
let blockedRule = rules.get(key);
|
|
3110
|
+
if (!blockedRule) {
|
|
3111
|
+
rules.set(key, blockedRule = {
|
|
3112
|
+
ruleId: rule.ruleId,
|
|
3113
|
+
authoredSpan: rule.authoredSpan,
|
|
3114
|
+
candidates: /* @__PURE__ */ new Set()
|
|
3115
|
+
});
|
|
3116
|
+
progressed = true;
|
|
3117
|
+
}
|
|
3118
|
+
for (const candidate of failed) blockedRule.candidates.add(candidate);
|
|
3119
|
+
}
|
|
3120
|
+
return progressed;
|
|
3121
|
+
}
|
|
3122
|
+
async function entryUnsafe(entryPath, workspaceRoot, targetable) {
|
|
3123
|
+
if (!targetable.has(entryPath) || !isProjectInput(workspaceRoot, entryPath)) return true;
|
|
3124
|
+
return (await lstat(entryPath)).isSymbolicLink();
|
|
3125
|
+
}
|
|
3126
|
+
function atRuleIdentities(key, css) {
|
|
3127
|
+
return css.trim() === "" ? [] : stylesheetAnalysis(key, css).globalAtRuleIdentities;
|
|
3128
|
+
}
|
|
3129
|
+
function stylesheetAtRuleIdentities(stylesheet) {
|
|
3130
|
+
if (stylesheet.vueAtRuleIdentities) return stylesheet.vueAtRuleIdentities;
|
|
3131
|
+
if (stylesheet.vueBlocks) return stylesheet.vueBlocks.flatMap((block, index) => atRuleIdentities(`${stylesheet.cssPath}.${index}.at-rules.${block.analysisSource ? "css" : block.syntax}`, block.analysisSource ?? block.content));
|
|
3132
|
+
return stylesheet.analysisSource === void 0 ? atRuleIdentities(`${stylesheet.cssPath}.at-rules.${stylesheet.syntax ?? "css"}`, stylesheet.cssSource) : atRuleIdentities(`${stylesheet.cssPath}.at-rules.css`, stylesheet.analysisSource);
|
|
3133
|
+
}
|
|
3134
|
+
async function finishGroupPlan(context, members, plan) {
|
|
3135
|
+
const { snapshots } = context;
|
|
3136
|
+
const membersByRoot = new Map(members.map((member) => [member.packageRoot, member]));
|
|
3137
|
+
const ownerOf = (path) => {
|
|
3138
|
+
const root = context.pathOwners.get(path);
|
|
3139
|
+
return root === void 0 ? void 0 : membersByRoot.get(root);
|
|
3140
|
+
};
|
|
3141
|
+
for (const member of members) {
|
|
3142
|
+
plan.warnings.push(...member.preparedHtml.warnings);
|
|
3143
|
+
plan.warnings.push(...member.preparedVue.warnings);
|
|
2031
3144
|
}
|
|
2032
|
-
removeMigratedHtmlLinks(plan, preparedHtml);
|
|
2033
|
-
plan.warnings.push(...preparedHtml.warnings);
|
|
2034
|
-
plan.warnings.push(...preparedVue.warnings);
|
|
2035
3145
|
try {
|
|
2036
|
-
for (const file of plan.files.filter((file) => extname(file.path) === ".html"))
|
|
3146
|
+
for (const file of plan.files.filter((file) => extname(file.path) === ".html")) try {
|
|
3147
|
+
parseHtmlSource(file.path, file.source);
|
|
3148
|
+
} catch (error) {
|
|
3149
|
+
return {
|
|
3150
|
+
error,
|
|
3151
|
+
member: ownerOf(file.path),
|
|
3152
|
+
fatal: true
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
2037
3155
|
for (const file of plan.files.filter((file) => extname(file.path) === ".vue")) {
|
|
2038
|
-
const
|
|
3156
|
+
const owner = ownerOf(file.path);
|
|
3157
|
+
const preparedVue = owner?.preparedVue ?? members[0].preparedVue;
|
|
3158
|
+
const vueCompiler = preparedVue.compiler ?? members[0].preparedVue.compiler;
|
|
2039
3159
|
if (!vueCompiler) throw new Error(`No Vue compiler is available to verify ${file.path}`);
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
const
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
for (const [index, block] of blocks.entries()) {
|
|
2048
|
-
const key = `${block.syntax}\0${block.content}`;
|
|
2049
|
-
const remaining = untouched.get(key) ?? 0;
|
|
2050
|
-
if (remaining > 0) {
|
|
2051
|
-
untouched.set(key, remaining - 1);
|
|
2052
|
-
continue;
|
|
3160
|
+
try {
|
|
3161
|
+
const includeUnscoped = preparedVue.unscopedPaths.has(file.path);
|
|
3162
|
+
const blocks = verifyVueSource(vueCompiler, file.path, file.source, includeUnscoped);
|
|
3163
|
+
const untouched = /* @__PURE__ */ new Map();
|
|
3164
|
+
for (const block of verifyVueSource(vueCompiler, file.path, snapshots.get(file.path) ?? "", includeUnscoped)) {
|
|
3165
|
+
const key = `${block.syntax}\0${block.content}`;
|
|
3166
|
+
untouched.set(key, (untouched.get(key) ?? 0) + 1);
|
|
2053
3167
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
3168
|
+
for (const [index, block] of blocks.entries()) {
|
|
3169
|
+
const key = `${block.syntax}\0${block.content}`;
|
|
3170
|
+
const remaining = untouched.get(key) ?? 0;
|
|
3171
|
+
if (remaining > 0) {
|
|
3172
|
+
untouched.set(key, remaining - 1);
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
if (block.syntax === "css") validateCss(block.content);
|
|
3176
|
+
else {
|
|
3177
|
+
const compilerOwner = owner ?? members[0];
|
|
3178
|
+
validateCss((await compileStyleEntry(compilerOwner.styleCompilers, compilerOwner.packageRoot, `${file.path}.${index}.${block.syntax}`, block.content, { virtualEntry: true })).css);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
} catch (error) {
|
|
3182
|
+
return {
|
|
3183
|
+
error,
|
|
3184
|
+
member: owner,
|
|
3185
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3186
|
+
};
|
|
2056
3187
|
}
|
|
2057
3188
|
}
|
|
2058
|
-
for (const stylesheet of stylesheets.filter((stylesheet) => isPreprocessorPath(stylesheet.cssPath))) {
|
|
3189
|
+
for (const member of members) for (const stylesheet of member.stylesheets.filter((stylesheet) => isPreprocessorPath(stylesheet.cssPath))) {
|
|
2059
3190
|
const changed = plan.files.find((file) => file.path === stylesheet.cssPath);
|
|
2060
3191
|
if (!changed && !plan.deletedFiles.includes(stylesheet.cssPath)) continue;
|
|
2061
3192
|
const source = changed?.source ?? "";
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
let plan = initialPlan;
|
|
2071
|
-
const blockedByStylesheet = /* @__PURE__ */ new Map();
|
|
2072
|
-
while (true) {
|
|
2073
|
-
const failing = invalidCandidates(tailwind, plan.candidates);
|
|
2074
|
-
if (failing.length === 0) break;
|
|
2075
|
-
let progressed = false;
|
|
2076
|
-
for (const rule of plan.rules) {
|
|
2077
|
-
const failed = rule.candidates.filter((candidate) => failing.includes(candidate));
|
|
2078
|
-
if (failed.length === 0) continue;
|
|
2079
|
-
let blocked = blockedByStylesheet.get(rule.stylesheet);
|
|
2080
|
-
if (!blocked) blockedByStylesheet.set(rule.stylesheet, blocked = /* @__PURE__ */ new Map());
|
|
2081
|
-
const key = `${rule.ruleId.start}-${rule.ruleId.end}`;
|
|
2082
|
-
let entry = blocked.get(key);
|
|
2083
|
-
if (!entry) {
|
|
2084
|
-
blocked.set(key, entry = {
|
|
2085
|
-
ruleId: rule.ruleId,
|
|
2086
|
-
authoredSpan: rule.authoredSpan,
|
|
2087
|
-
candidates: /* @__PURE__ */ new Set()
|
|
2088
|
-
});
|
|
2089
|
-
progressed = true;
|
|
3193
|
+
try {
|
|
3194
|
+
validateCss((await compileStyleEntry(member.styleCompilers, member.packageRoot, stylesheet.cssPath, source)).css);
|
|
3195
|
+
} catch (error) {
|
|
3196
|
+
return {
|
|
3197
|
+
error,
|
|
3198
|
+
member,
|
|
3199
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3200
|
+
};
|
|
2090
3201
|
}
|
|
2091
|
-
for (const candidate of failed) entry.candidates.add(candidate);
|
|
2092
|
-
}
|
|
2093
|
-
if (!progressed) throw new Error(`Tailwind did not generate CSS for candidate: ${failing[0]}`);
|
|
2094
|
-
plan = JSON.parse(planBatchMigration(JSON.stringify({
|
|
2095
|
-
...request,
|
|
2096
|
-
stylesheets: request.stylesheets.map((stylesheet, index) => ({
|
|
2097
|
-
...stylesheet,
|
|
2098
|
-
blockedRules: [...blockedByStylesheet.get(index)?.values() ?? []].map((entry) => entry.ruleId)
|
|
2099
|
-
}))
|
|
2100
|
-
})));
|
|
2101
|
-
}
|
|
2102
|
-
for (const [index, blocked] of blockedByStylesheet) {
|
|
2103
|
-
const cssPath = request.stylesheets[index].cssPath;
|
|
2104
|
-
for (const { authoredSpan, candidates } of blocked.values()) {
|
|
2105
|
-
const failed = [...candidates].sort().map((candidate) => `\`${candidate}\``).join(", ");
|
|
2106
|
-
plan.warnings.push({
|
|
2107
|
-
code: "candidate-compilation-failure",
|
|
2108
|
-
file: cssPath,
|
|
2109
|
-
start: authoredSpan.start,
|
|
2110
|
-
end: authoredSpan.end,
|
|
2111
|
-
message: `Tailwind did not generate CSS for ${failed}, so the rule is retained.`
|
|
2112
|
-
});
|
|
2113
3202
|
}
|
|
3203
|
+
} catch (error) {
|
|
3204
|
+
return {
|
|
3205
|
+
error,
|
|
3206
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3207
|
+
};
|
|
2114
3208
|
}
|
|
2115
|
-
return plan;
|
|
2116
3209
|
}
|
|
2117
|
-
function
|
|
3210
|
+
function removeGroupHtmlLinks(plan, members) {
|
|
2118
3211
|
const unlinked = new Set(plan.unlinkedFiles);
|
|
2119
3212
|
const linksByFile = /* @__PURE__ */ new Map();
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
const
|
|
2123
|
-
|
|
2124
|
-
|
|
3213
|
+
const originals = /* @__PURE__ */ new Map();
|
|
3214
|
+
for (const member of members) {
|
|
3215
|
+
for (const link of member.preparedHtml.removableLinks) {
|
|
3216
|
+
if (!unlinked.has(link.cssPath)) continue;
|
|
3217
|
+
const links = linksByFile.get(link.filePath) ?? /* @__PURE__ */ new Set();
|
|
3218
|
+
links.add(`${link.href}\0${link.media}`);
|
|
3219
|
+
linksByFile.set(link.filePath, links);
|
|
3220
|
+
}
|
|
3221
|
+
for (const file of member.preparedHtml.files) if (!originals.has(file.path)) originals.set(file.path, file.source);
|
|
2125
3222
|
}
|
|
2126
3223
|
for (const [filePath, removable] of linksByFile) {
|
|
2127
3224
|
const planned = plan.files.find((file) => file.path === filePath);
|
|
2128
|
-
const
|
|
2129
|
-
if (
|
|
2130
|
-
const source = planned?.source ?? original.source;
|
|
3225
|
+
const source = planned?.source ?? originals.get(filePath);
|
|
3226
|
+
if (source === void 0) continue;
|
|
2131
3227
|
const links = parseHtmlSource(filePath, source).links.filter((link) => removable.has(`${link.href}\0${link.media}`)).sort((left, right) => right.tagStart - left.tagStart);
|
|
2132
3228
|
let bytes = Buffer.from(source);
|
|
2133
3229
|
for (const link of links) bytes = Buffer.concat([bytes.subarray(0, link.tagStart), bytes.subarray(link.tagEnd)]);
|
|
@@ -2167,7 +3263,7 @@ function mergePlans(plans, originals) {
|
|
|
2167
3263
|
retainedRules += plan.retainedRules;
|
|
2168
3264
|
rules.push(...plan.rules);
|
|
2169
3265
|
for (const warning of plan.warnings) {
|
|
2170
|
-
const key = `${warning.code}\0${warning.file}\0${warning.start}\0${warning.end}`;
|
|
3266
|
+
const key = `${warning.code}\0${warning.file}\0${warning.start}\0${warning.end}\0${warning.message}`;
|
|
2171
3267
|
if (seenWarnings.has(key)) continue;
|
|
2172
3268
|
seenWarnings.add(key);
|
|
2173
3269
|
warnings.push(warning);
|