
























@@ -21,9 +21,8 @@ import {
2121unlinkSync,
2222writeFileSync,
2323} from "node:fs";
24-import { basename, dirname, isAbsolute, join, relative } from "node:path";
24+import { basename, dirname, isAbsolute, join, posix, relative } from "node:path";
2525import { fileURLToPath, pathToFileURL } from "node:url";
26-import { expandPackageDistImportClosure } from "./lib/package-dist-imports.mjs";
2726import { resolveNpmRunner } from "./npm-runner.mjs";
28272928export const BUNDLED_PLUGIN_INSTALL_TARGETS = [];
@@ -272,6 +271,137 @@ function pruneEmptyDistDirectories(params = {}) {
272271prune(distRoot.distDir);
273272}
274273274+const JS_DIST_FILE_RE = /^dist\/.*\.(?:cjs|js|mjs)$/u;
275+276+function stripSpecifierSuffix(value) {
277+return value.replace(/[?#].*$/u, "");
278+}
279+280+function resolveDistImportPath(importerPath, specifier) {
281+if (!specifier.startsWith(".")) {
282+return null;
283+}
284+const stripped = stripSpecifierSuffix(specifier);
285+if (!stripped) {
286+return null;
287+}
288+return posix.normalize(posix.join(posix.dirname(importerPath), stripped));
289+}
290+291+function findStatementStart(source, index) {
292+return (
293+Math.max(
294+source.lastIndexOf(";", index),
295+source.lastIndexOf("{", index),
296+source.lastIndexOf("}", index),
297+source.lastIndexOf("\n", index),
298+source.lastIndexOf("\r", index),
299+) + 1
300+);
301+}
302+303+function isImportSpecifierContext(source, index) {
304+const dynamicPrefix = source.slice(Math.max(0, index - 32), index);
305+if (/\bimport\s*\(\s*$/u.test(dynamicPrefix)) {
306+return true;
307+}
308+const statementPrefix = source.slice(findStatementStart(source, index), index).trimStart();
309+return (
310+/^(?:import|export)\b[\s\S]*\bfrom\s*$/u.test(statementPrefix) ||
311+/^import\s*$/u.test(statementPrefix)
312+);
313+}
314+315+function collectImportSpecifiers(source) {
316+const specifiers = [];
317+let inBlockComment = false;
318+let inLineComment = false;
319+for (let index = 0; index < source.length; index += 1) {
320+if (inBlockComment) {
321+if (source[index] === "*" && source[index + 1] === "/") {
322+inBlockComment = false;
323+index += 1;
324+}
325+continue;
326+}
327+if (inLineComment) {
328+if (source[index] === "\n" || source[index] === "\r") {
329+inLineComment = false;
330+}
331+continue;
332+}
333+if (source[index] === "/" && source[index + 1] === "*") {
334+inBlockComment = true;
335+index += 1;
336+continue;
337+}
338+if (source[index] === "/" && source[index + 1] === "/") {
339+inLineComment = true;
340+index += 1;
341+continue;
342+}
343+344+const quote = source[index];
345+if (quote !== '"' && quote !== "'") {
346+continue;
347+}
348+349+let cursor = index + 1;
350+let value = "";
351+while (cursor < source.length) {
352+const char = source[cursor];
353+if (char === "\\") {
354+value += source.slice(cursor, cursor + 2);
355+cursor += 2;
356+continue;
357+}
358+if (char === quote) {
359+break;
360+}
361+value += char;
362+cursor += 1;
363+}
364+if (cursor >= source.length) {
365+break;
366+}
367+368+if (value.startsWith(".") && isImportSpecifierContext(source, index)) {
369+specifiers.push(value);
370+}
371+index = cursor;
372+}
373+return specifiers;
374+}
375+376+function expandInstalledDistImportClosure(params) {
377+const files = [...new Set(params.files)];
378+const fileSet = new Set(files);
379+const expectedSet = new Set(params.seedFiles);
380+let changed = true;
381+382+while (changed) {
383+changed = false;
384+for (const importerPath of [...expectedSet]
385+.filter((file) => fileSet.has(file))
386+.toSorted((left, right) => left.localeCompare(right))) {
387+if (!JS_DIST_FILE_RE.test(importerPath) || importerPath.includes("/node_modules/")) {
388+continue;
389+}
390+const source = params.readText(importerPath);
391+for (const specifier of collectImportSpecifiers(source)) {
392+const importedPath = resolveDistImportPath(importerPath, specifier);
393+if (!importedPath || !fileSet.has(importedPath) || expectedSet.has(importedPath)) {
394+continue;
395+}
396+expectedSet.add(importedPath);
397+changed = true;
398+}
399+}
400+}
401+402+return [...expectedSet].toSorted((left, right) => left.localeCompare(right));
403+}
404+275405export function pruneInstalledPackageDist(params = {}) {
276406const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
277407const removeFile = params.unlinkSync ?? unlinkSync;
@@ -295,7 +425,7 @@ export function pruneInstalledPackageDist(params = {}) {
295425const installedFiles = listInstalledDistFiles(params);
296426const readFile = params.readFileSync ?? readFileSync;
297427expectedFiles = new Set(
298-expandPackageDistImportClosure({
428+expandInstalledDistImportClosure({
299429files: installedFiles,
300430seedFiles: [...expectedFiles],
301431readText(relativePath) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。