mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-28 07:09:50 +00:00
207 lines
5.5 KiB
TypeScript
207 lines
5.5 KiB
TypeScript
// Derived from vitefu v1.1.3 (MIT License).
|
|
// Copyright (c) 2026 Bjorn and Dominik.
|
|
import fs from 'node:fs/promises';
|
|
import fsSync from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
interface CrawlFrameworkPkgsOptions {
|
|
root: string;
|
|
isFrameworkPkgByJson?: (pkgJson: Record<string, unknown>) => boolean;
|
|
isFrameworkPkgByName?: (pkgName: string) => boolean | undefined;
|
|
isSemiFrameworkPkgByJson?: (pkgJson: Record<string, unknown>) => boolean;
|
|
isSemiFrameworkPkgByName?: (pkgName: string) => boolean | undefined;
|
|
}
|
|
|
|
interface CrawlFrameworkPkgsResult {
|
|
optimizeDeps: {
|
|
include: string[];
|
|
exclude: string[];
|
|
};
|
|
ssr: {
|
|
noExternal: string[];
|
|
};
|
|
}
|
|
|
|
interface PackageJson {
|
|
dependencies?: Record<string, string>;
|
|
devDependencies?: Record<string, string>;
|
|
exports?: unknown;
|
|
main?: string;
|
|
module?: string;
|
|
private?: boolean;
|
|
type?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export async function crawlFrameworkPkgs(
|
|
options: CrawlFrameworkPkgsOptions,
|
|
): Promise<CrawlFrameworkPkgsResult> {
|
|
const pkgJsonPath = await findClosestPkgJsonPath(options.root);
|
|
|
|
if (!pkgJsonPath) {
|
|
return {
|
|
optimizeDeps: { include: [], exclude: [] },
|
|
ssr: { noExternal: [] },
|
|
};
|
|
}
|
|
|
|
const pkgJson = await readJson(pkgJsonPath).catch((e: unknown) => {
|
|
throw new Error(`Unable to read ${pkgJsonPath}`, { cause: e });
|
|
});
|
|
|
|
const optimizeDepsInclude: string[] = [];
|
|
const optimizeDepsExclude: string[] = [];
|
|
const ssrNoExternal: string[] = [];
|
|
|
|
await crawl(pkgJsonPath, pkgJson);
|
|
|
|
optimizeDepsInclude.sort();
|
|
optimizeDepsExclude.sort();
|
|
ssrNoExternal.sort();
|
|
|
|
return {
|
|
optimizeDeps: {
|
|
include: optimizeDepsInclude,
|
|
exclude: optimizeDepsExclude,
|
|
},
|
|
ssr: {
|
|
noExternal: ssrNoExternal,
|
|
},
|
|
};
|
|
|
|
async function crawl(
|
|
currentPkgJsonPath: string,
|
|
currentPkgJson: PackageJson,
|
|
parentDepNames: string[] = [],
|
|
_parentIsFrameworkPkg = false,
|
|
hasFrameworkAncestor = false,
|
|
) {
|
|
const deps = Object.keys(currentPkgJson.dependencies ?? {}).filter(
|
|
(dep) => !parentDepNames.includes(dep),
|
|
);
|
|
|
|
await Promise.all(
|
|
deps.map(async (dep) => {
|
|
const frameworkByName = options.isFrameworkPkgByName?.(dep);
|
|
const semiFrameworkByName = options.isSemiFrameworkPkgByName?.(dep);
|
|
|
|
if (frameworkByName === false || semiFrameworkByName === false) {
|
|
return;
|
|
}
|
|
|
|
const depPkgJsonPath = await findDepPkgJsonPath(dep, currentPkgJsonPath);
|
|
if (!depPkgJsonPath) return;
|
|
|
|
const depPkgJson = await readJson(depPkgJsonPath).catch(() => {});
|
|
if (!depPkgJson) return;
|
|
|
|
const isFrameworkPkg =
|
|
frameworkByName === true || options.isFrameworkPkgByJson?.(depPkgJson) === true;
|
|
const isSemiFrameworkPkg =
|
|
semiFrameworkByName === true || options.isSemiFrameworkPkgByJson?.(depPkgJson) === true;
|
|
const depChain = parentDepNames.concat(dep);
|
|
|
|
if (isFrameworkPkg || isSemiFrameworkPkg) {
|
|
if (isFrameworkPkg) {
|
|
pushUnique(optimizeDepsExclude, dep);
|
|
pushUnique(ssrNoExternal, dep);
|
|
} else {
|
|
pushUnique(ssrNoExternal, dep);
|
|
}
|
|
|
|
await crawl(depPkgJsonPath, depPkgJson, depChain, true, true);
|
|
return;
|
|
}
|
|
|
|
if (!hasFrameworkAncestor) return;
|
|
|
|
const needsOptimization = await pkgNeedsOptimization(depPkgJson);
|
|
|
|
if (needsOptimization) {
|
|
addOptimizedDep(depChain);
|
|
} else {
|
|
await crawl(depPkgJsonPath, depPkgJson, depChain, false, true);
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
|
|
function addOptimizedDep(depChain: string[]) {
|
|
for (const item of optimizeDepsInclude) {
|
|
const segs = item.split(' > ');
|
|
if (
|
|
segs[0] === depChain[0] &&
|
|
segs[segs.length - 1] === depChain[depChain.length - 1] &&
|
|
segs.length <= depChain.length
|
|
) {
|
|
// skip
|
|
return;
|
|
}
|
|
}
|
|
|
|
optimizeDepsInclude.push(depChain.join(' > '));
|
|
}
|
|
}
|
|
|
|
export async function findClosestPkgJsonPath(
|
|
dir: string,
|
|
predicate?: (pkgJsonPath: string) => boolean | Promise<boolean>,
|
|
): Promise<string | undefined> {
|
|
let currentDir = dir.endsWith('package.json') ? path.dirname(dir) : dir;
|
|
|
|
while (currentDir) {
|
|
const pkg = path.join(currentDir, 'package.json');
|
|
|
|
try {
|
|
const stat = await fs.stat(pkg);
|
|
if (stat.isFile() && (!predicate || (await predicate(pkg)))) {
|
|
return pkg;
|
|
}
|
|
} catch {
|
|
// Keep walking up.
|
|
}
|
|
|
|
const nextDir = path.dirname(currentDir);
|
|
if (nextDir === currentDir) break;
|
|
currentDir = nextDir;
|
|
}
|
|
}
|
|
|
|
export async function pkgNeedsOptimization(pkgJson: PackageJson): Promise<boolean> {
|
|
if (pkgJson.module || pkgJson.exports || pkgJson.type === 'module') return false;
|
|
|
|
if (pkgJson.main) {
|
|
const entryExt = path.extname(pkgJson.main);
|
|
return !entryExt || entryExt === '.js' || entryExt === '.cjs';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async function findDepPkgJsonPath(dep: string, parent: string): Promise<string | undefined> {
|
|
let root = parent;
|
|
|
|
while (root) {
|
|
const pkg = path.join(root, 'node_modules', dep, 'package.json');
|
|
|
|
try {
|
|
await fs.access(pkg);
|
|
return fsSync.realpathSync(pkg);
|
|
} catch {
|
|
// Keep walking up.
|
|
}
|
|
|
|
const nextRoot = path.dirname(root);
|
|
if (nextRoot === root) break;
|
|
root = nextRoot;
|
|
}
|
|
}
|
|
|
|
export async function readJson(pkgJsonPath: string): Promise<PackageJson> {
|
|
return JSON.parse(await fs.readFile(pkgJsonPath, 'utf8')) as PackageJson;
|
|
}
|
|
|
|
function pushUnique(array: string[], value: string) {
|
|
if (!array.includes(value)) array.push(value);
|
|
}
|