From 4857b1540932a028714a2f2f414e7ec9a71440f9 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 14:55:58 +0800 Subject: [PATCH 01/15] wip: setup cache key functions --- packages/vite/src/node/packages.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index a26c6e50a04350..1ac72b4ea30ed3 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -210,3 +210,17 @@ export function resolvePkgJsonPath( return undefined } + +// package cache key for `resolvePackageData` +function getRpdCacheKey( + pkgName: string, + basedir: string, + preserveSymlinks: boolean, +) { + return `rpd_${pkgName}_${basedir}_${preserveSymlinks}` +} + +// package cache key for `findNearestPackageData` +function getFnpdCacheKey(basedir: string) { + return `fnpd_${basedir}` +} From 06c887d15f08412b370791dc0a007ed61dc21856 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 15:08:57 +0800 Subject: [PATCH 02/15] wip: setup cached get function --- packages/vite/src/node/packages.ts | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 1ac72b4ea30ed3..bc76eedb849055 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -211,6 +211,34 @@ export function resolvePkgJsonPath( return undefined } +/** + * Get cached `resolvePackageData` value based on `basedir`. When one is found, + * and we've already traversed some directories between `basedir` and `originalBasedir`, + * we cache the value for those in-between directories as well. + * + * This makes it so the fs is only read once for a shared `basedir`. + */ +function getRpdCachedValue( + packageCache: PackageCache, + pkgName: string, + basedir: string, + originalBasedir: string, + preserveSymlinks: boolean, +) { + const cacheKey = getRpdCacheKey(pkgName, basedir, preserveSymlinks) + const pkgData = packageCache.get(cacheKey) + if (pkgData) { + while (originalBasedir !== basedir) { + packageCache.set( + getRpdCacheKey(pkgName, originalBasedir, preserveSymlinks), + pkgData, + ) + originalBasedir = path.dirname(originalBasedir) + } + return pkgData + } +} + // package cache key for `resolvePackageData` function getRpdCacheKey( pkgName: string, @@ -220,6 +248,29 @@ function getRpdCacheKey( return `rpd_${pkgName}_${basedir}_${preserveSymlinks}` } +/** + * Get cached `findNearestPackageData` value based on `basedir`. When one is found, + * and we've already traversed some directories between `basedir` and `originalBasedir`, + * we cache the value for those in-between directories as well. + * + * This makes it so the fs is only read once for a shared `basedir`. + */ +function getFnpdCachedValue( + packageCache: PackageCache, + basedir: string, + originalBasedir: string, +) { + const cacheKey = getFnpdCacheKey(basedir) + const pkgData = packageCache.get(cacheKey) + if (pkgData) { + while (originalBasedir !== basedir) { + packageCache.set(getFnpdCacheKey(originalBasedir), pkgData) + originalBasedir = path.dirname(originalBasedir) + } + return pkgData + } +} + // package cache key for `findNearestPackageData` function getFnpdCacheKey(basedir: string) { return `fnpd_${basedir}` From 00c22544d08711eb66858b67bb4a9681071bead3 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 15:13:07 +0800 Subject: [PATCH 03/15] wip: abstract traverseBetweenDirs --- packages/vite/src/node/packages.ts | 31 ++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index bc76eedb849055..844a7a8688194a 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -228,13 +228,9 @@ function getRpdCachedValue( const cacheKey = getRpdCacheKey(pkgName, basedir, preserveSymlinks) const pkgData = packageCache.get(cacheKey) if (pkgData) { - while (originalBasedir !== basedir) { - packageCache.set( - getRpdCacheKey(pkgName, originalBasedir, preserveSymlinks), - pkgData, - ) - originalBasedir = path.dirname(originalBasedir) - } + traverseBetweenDirs(originalBasedir, basedir, (dir) => { + packageCache.set(getRpdCacheKey(pkgName, dir, preserveSymlinks), pkgData) + }) return pkgData } } @@ -263,10 +259,9 @@ function getFnpdCachedValue( const cacheKey = getFnpdCacheKey(basedir) const pkgData = packageCache.get(cacheKey) if (pkgData) { - while (originalBasedir !== basedir) { + traverseBetweenDirs(originalBasedir, basedir, () => { packageCache.set(getFnpdCacheKey(originalBasedir), pkgData) - originalBasedir = path.dirname(originalBasedir) - } + }) return pkgData } } @@ -275,3 +270,19 @@ function getFnpdCachedValue( function getFnpdCacheKey(basedir: string) { return `fnpd_${basedir}` } + +/** + * Traverse between `longerDir` (inclusive) and `shorterDir` (exclusive) and call `cb` for each dir. + * @param longerDir Longer dir path, e.g. `/User/foo/bar/baz` + * @param shorterDir Shorter dir path, e.g. `/User/foo` + */ +function traverseBetweenDirs( + longerDir: string, + shorterDir: string, + cb: (dir: string) => void, +) { + while (longerDir !== shorterDir) { + cb(longerDir) + longerDir = path.dirname(longerDir) + } +} From 841f896bb2b33eef7a765385891f84adb350c324 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 15:24:23 +0800 Subject: [PATCH 04/15] wip: setup cached set function --- packages/vite/src/node/packages.ts | 34 ++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 844a7a8688194a..536d2febe775fa 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -218,7 +218,7 @@ export function resolvePkgJsonPath( * * This makes it so the fs is only read once for a shared `basedir`. */ -function getRpdCachedValue( +function getRpdCache( packageCache: PackageCache, pkgName: string, basedir: string, @@ -235,6 +235,20 @@ function getRpdCachedValue( } } +function setRpdCache( + packageCache: PackageCache, + pkgData: PackageData, + pkgName: string, + basedir: string, + originalBasedir: string, + preserveSymlinks: boolean, +) { + packageCache.set(getRpdCacheKey(pkgName, basedir, preserveSymlinks), pkgData) + traverseBetweenDirs(originalBasedir, basedir, (dir) => { + packageCache.set(getRpdCacheKey(pkgName, dir, preserveSymlinks), pkgData) + }) +} + // package cache key for `resolvePackageData` function getRpdCacheKey( pkgName: string, @@ -251,7 +265,7 @@ function getRpdCacheKey( * * This makes it so the fs is only read once for a shared `basedir`. */ -function getFnpdCachedValue( +function getFnpdCache( packageCache: PackageCache, basedir: string, originalBasedir: string, @@ -259,13 +273,25 @@ function getFnpdCachedValue( const cacheKey = getFnpdCacheKey(basedir) const pkgData = packageCache.get(cacheKey) if (pkgData) { - traverseBetweenDirs(originalBasedir, basedir, () => { - packageCache.set(getFnpdCacheKey(originalBasedir), pkgData) + traverseBetweenDirs(originalBasedir, basedir, (dir) => { + packageCache.set(getFnpdCacheKey(dir), pkgData) }) return pkgData } } +function setFnpdCache( + packageCache: PackageCache, + pkgData: PackageData, + basedir: string, + originalBasedir: string, +) { + packageCache.set(getFnpdCacheKey(basedir), pkgData) + traverseBetweenDirs(originalBasedir, basedir, (dir) => { + packageCache.set(getFnpdCacheKey(dir), pkgData) + }) +} + // package cache key for `findNearestPackageData` function getFnpdCacheKey(basedir: string) { return `fnpd_${basedir}` From e1e8f3e0d40839e854f31524005a790a87c3d5d4 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 15:26:02 +0800 Subject: [PATCH 05/15] wip: move resolvePkgJsonPath into resolvePackageData --- packages/vite/src/node/packages.ts | 72 ++++++++---------------------- 1 file changed, 19 insertions(+), 53 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 536d2febe775fa..52cd6e97e91c83 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -13,11 +13,6 @@ if (process.versions.pnp) { } catch {} } -const isDebug = process.env.DEBUG -const debug = createDebugger('vite:resolve-details', { - onlyWhenFocused: true, -}) - /** Cache for package.json resolution and package.json contents */ export type PackageCache = Map @@ -56,33 +51,31 @@ export function invalidatePackageData( } export function resolvePackageData( - id: string, + pkgName: string, basedir: string, preserveSymlinks = false, packageCache?: PackageCache, ): PackageData | null { - let pkg: PackageData | undefined - let cacheKey: string | undefined - if (packageCache) { - cacheKey = `${id}&${basedir}&${preserveSymlinks}` - if ((pkg = packageCache.get(cacheKey))) { - return pkg - } + if (pnp) { + const pkg = pnp.resolveToUnqualified(pkgName, basedir) + if (!pkg) return undefined + return path.join(pkg, 'package.json') } - const pkgPath = resolvePkgJsonPath(id, basedir, preserveSymlinks) - if (!pkgPath) return null - try { - pkg = loadPackageData(pkgPath, true, packageCache) - if (packageCache) { - packageCache.set(cacheKey!, pkg) - } - return pkg - } catch (e) { - if (e instanceof SyntaxError) { - isDebug && debug(`Parsing failed: ${pkgPath}`) - } - throw e + + let root = basedir + while (root) { + const pkg = path.join(root, 'node_modules', pkgName, 'package.json') + try { + if (fs.existsSync(pkg)) { + return preserveSymlinks ? pkg : safeRealpathSync(pkg) + } + } catch {} + const nextRoot = path.dirname(root) + if (nextRoot === root) break + root = nextRoot } + + return undefined } export function loadPackageData( @@ -184,33 +177,6 @@ export function watchPackageDataPlugin(config: ResolvedConfig): Plugin { } } -export function resolvePkgJsonPath( - pkgName: string, - basedir: string, - preserveSymlinks = false, -): string | undefined { - if (pnp) { - const pkg = pnp.resolveToUnqualified(pkgName, basedir) - if (!pkg) return undefined - return path.join(pkg, 'package.json') - } - - let root = basedir - while (root) { - const pkg = path.join(root, 'node_modules', pkgName, 'package.json') - try { - if (fs.existsSync(pkg)) { - return preserveSymlinks ? pkg : safeRealpathSync(pkg) - } - } catch {} - const nextRoot = path.dirname(root) - if (nextRoot === root) break - root = nextRoot - } - - return undefined -} - /** * Get cached `resolvePackageData` value based on `basedir`. When one is found, * and we've already traversed some directories between `basedir` and `originalBasedir`, From 3d280f8d6535bfc7d7dae4ffb0b88983e742a7eb Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 15:35:00 +0800 Subject: [PATCH 06/15] wip: simplify loadPackageData --- packages/vite/src/node/packages.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 52cd6e97e91c83..5235f43fbb9f82 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -78,20 +78,7 @@ export function resolvePackageData( return undefined } -export function loadPackageData( - pkgPath: string, - preserveSymlinks?: boolean, - packageCache?: PackageCache, -): PackageData { - if (!preserveSymlinks) { - pkgPath = safeRealpathSync(pkgPath) - } - - let cached: PackageData | undefined - if ((cached = packageCache?.get(pkgPath))) { - return cached - } - +export function loadPackageData(pkgPath: string): PackageData { const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) const pkgDir = path.dirname(pkgPath) const { sideEffects } = data From f4f370b5a8685a16a446c0ce16b09571eee72967 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 15:36:27 +0800 Subject: [PATCH 07/15] wip: fix resolvePackageData yarn pnp --- packages/vite/src/node/packages.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 5235f43fbb9f82..450bbef5a8133d 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -57,9 +57,16 @@ export function resolvePackageData( packageCache?: PackageCache, ): PackageData | null { if (pnp) { + const cacheKey = getRpdCacheKey(pkgName, basedir, preserveSymlinks) + if (packageCache?.has(cacheKey)) return packageCache.get(cacheKey)! + const pkg = pnp.resolveToUnqualified(pkgName, basedir) - if (!pkg) return undefined - return path.join(pkg, 'package.json') + if (!pkg) return null + + const pkgData = loadPackageData(path.join(pkg, 'package.json')) + packageCache?.set(cacheKey, pkgData) + + return pkgData } let root = basedir From 30af744287b365af80a5ffee751574cce402081f Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 20:07:05 +0800 Subject: [PATCH 08/15] wip: finish rest of resolvePackageData --- packages/vite/src/node/packages.ts | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 450bbef5a8133d..026a3c0971cc0d 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -71,18 +71,44 @@ export function resolvePackageData( let root = basedir while (root) { + if (packageCache) { + const cached = getRpdCache( + packageCache, + pkgName, + root, + basedir, + preserveSymlinks, + ) + if (cached) return cached + } + const pkg = path.join(root, 'node_modules', pkgName, 'package.json') try { if (fs.existsSync(pkg)) { - return preserveSymlinks ? pkg : safeRealpathSync(pkg) + const pkgPath = preserveSymlinks ? pkg : safeRealpathSync(pkg) + const pkgData = loadPackageData(pkgPath) + + if (packageCache) { + setRpdCache( + packageCache, + pkgData, + pkgName, + root, + basedir, + preserveSymlinks, + ) + } + + return pkgData } } catch {} + const nextRoot = path.dirname(root) if (nextRoot === root) break root = nextRoot } - return undefined + return null } export function loadPackageData(pkgPath: string): PackageData { From 077a9fb06c2f50db855d9ef2bfb2d5a01ba429d1 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 20:34:40 +0800 Subject: [PATCH 09/15] wip: setup findNearestPackageData --- packages/vite/src/node/packages.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 026a3c0971cc0d..d519ee6949eb63 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -111,6 +111,22 @@ export function resolvePackageData( return null } +export function findNearestPackageData(basedir: string): PackageData | null { + let root = basedir + while (root) { + const pkgPath = path.join(root, 'package.json') + try { + if (fs.statSync(pkgPath).isFile()) { + return loadPackageData(pkgPath) + } + } catch {} + const nextDir = path.dirname(root) + if (nextDir === root) break + root = nextDir + } + return null +} + export function loadPackageData(pkgPath: string): PackageData { const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) const pkgDir = path.dirname(pkgPath) @@ -160,7 +176,6 @@ export function loadPackageData(pkgPath: string): PackageData { }, } - packageCache?.set(pkgPath, pkg) return pkg } From a2ef7d28062e591babe3eb5a5aecc2c3c412c81e Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 20:36:10 +0800 Subject: [PATCH 10/15] wip: add cache to findNearestPackageData --- packages/vite/src/node/packages.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index d519ee6949eb63..91c2983bda9b2a 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -111,19 +111,35 @@ export function resolvePackageData( return null } -export function findNearestPackageData(basedir: string): PackageData | null { +export function findNearestPackageData( + basedir: string, + packageCache?: PackageCache, +): PackageData | null { let root = basedir while (root) { + if (packageCache) { + const cached = getFnpdCache(packageCache, root, basedir) + if (cached) return cached + } + const pkgPath = path.join(root, 'package.json') try { if (fs.statSync(pkgPath).isFile()) { - return loadPackageData(pkgPath) + const pkgData = loadPackageData(pkgPath) + + if (packageCache) { + setFnpdCache(packageCache, pkgData, root, basedir) + } + + return pkgData } } catch {} + const nextDir = path.dirname(root) if (nextDir === root) break root = nextDir } + return null } From 1dc2de46d299c6734982f7331ef25142d2dd0b5c Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 20:38:38 +0800 Subject: [PATCH 11/15] wip: rename variables for clarity --- packages/vite/src/node/packages.ts | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 91c2983bda9b2a..8aa2a2e8caf11b 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -69,20 +69,20 @@ export function resolvePackageData( return pkgData } - let root = basedir - while (root) { + const originalBasedir = basedir + while (basedir) { if (packageCache) { const cached = getRpdCache( packageCache, pkgName, - root, basedir, + originalBasedir, preserveSymlinks, ) if (cached) return cached } - const pkg = path.join(root, 'node_modules', pkgName, 'package.json') + const pkg = path.join(basedir, 'node_modules', pkgName, 'package.json') try { if (fs.existsSync(pkg)) { const pkgPath = preserveSymlinks ? pkg : safeRealpathSync(pkg) @@ -93,8 +93,8 @@ export function resolvePackageData( packageCache, pkgData, pkgName, - root, basedir, + originalBasedir, preserveSymlinks, ) } @@ -103,9 +103,9 @@ export function resolvePackageData( } } catch {} - const nextRoot = path.dirname(root) - if (nextRoot === root) break - root = nextRoot + const nextBasedir = path.dirname(basedir) + if (nextBasedir === basedir) break + basedir = nextBasedir } return null @@ -115,29 +115,29 @@ export function findNearestPackageData( basedir: string, packageCache?: PackageCache, ): PackageData | null { - let root = basedir - while (root) { + const originalBasedir = basedir + while (basedir) { if (packageCache) { - const cached = getFnpdCache(packageCache, root, basedir) + const cached = getFnpdCache(packageCache, basedir, originalBasedir) if (cached) return cached } - const pkgPath = path.join(root, 'package.json') + const pkgPath = path.join(basedir, 'package.json') try { if (fs.statSync(pkgPath).isFile()) { const pkgData = loadPackageData(pkgPath) if (packageCache) { - setFnpdCache(packageCache, pkgData, root, basedir) + setFnpdCache(packageCache, pkgData, basedir, originalBasedir) } return pkgData } } catch {} - const nextDir = path.dirname(root) - if (nextDir === root) break - root = nextDir + const nextBasedir = path.dirname(basedir) + if (nextBasedir === basedir) break + basedir = nextBasedir } return null From 001c4dd0737a7d9da8ca9d39775c929e908bb934 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 20:48:53 +0800 Subject: [PATCH 12/15] fix: update api usage --- packages/vite/src/node/optimizer/index.ts | 10 ++--- packages/vite/src/node/packages.ts | 2 +- packages/vite/src/node/plugins/resolve.ts | 51 +++++++++-------------- packages/vite/src/node/ssr/ssrExternal.ts | 27 +++++------- 4 files changed, 36 insertions(+), 54 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 42620fc2ff7ab2..04ba88cb38eb08 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -26,7 +26,7 @@ import { } from '../utils' import { transformWithEsbuild } from '../plugins/esbuild' import { ESBUILD_MODULES_TARGET } from '../constants' -import { resolvePkgJsonPath } from '../packages' +import { resolvePackageData } from '../packages' import { esbuildCjsExternalPlugin, esbuildDepPlugin } from './esbuildDepPlugin' import { scanImports } from './scan' export { @@ -855,7 +855,7 @@ function createOptimizeDepsIncludeResolver( // 'foo > bar > baz' => 'foo > bar' & 'baz' const nestedRoot = id.substring(0, lastArrowIndex).trim() const nestedPath = id.substring(lastArrowIndex + 1).trim() - const basedir = nestedResolvePkgJsonPath( + const basedir = nestedResolveBasedir( nestedRoot, config.root, config.resolve.preserveSymlinks, @@ -865,16 +865,16 @@ function createOptimizeDepsIncludeResolver( } /** - * Like `resolvePkgJsonPath`, but supports resolving nested package names with '>' + * Continously resolve the basedir of packages separated by '>' */ -function nestedResolvePkgJsonPath( +function nestedResolveBasedir( id: string, basedir: string, preserveSymlinks = false, ) { const pkgs = id.split('>').map((pkg) => pkg.trim()) for (const pkg of pkgs) { - basedir = resolvePkgJsonPath(pkg, basedir, preserveSymlinks) || basedir + basedir = resolvePackageData(pkg, basedir, preserveSymlinks)?.dir || basedir } return basedir } diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 8aa2a2e8caf11b..1e6c82856ff9b7 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -1,7 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import { createRequire } from 'node:module' -import { createDebugger, createFilter, safeRealpathSync } from './utils' +import { createFilter, safeRealpathSync } from './utils' import type { ResolvedConfig } from './config' import type { Plugin } from './plugin' diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index d1b43ce08e407f..a1fc5fbcad44a5 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -36,13 +36,18 @@ import { lookupFile, normalizePath, resolveFrom, + safeRealpathSync, slash, } from '../utils' import { optimizedDepInfoFromFile, optimizedDepInfoFromId } from '../optimizer' import type { DepsOptimizer } from '../optimizer' import type { SSROptions } from '..' import type { PackageCache, PackageData } from '../packages' -import { loadPackageData, resolvePackageData } from '../packages' +import { + findNearestPackageData, + loadPackageData, + resolvePackageData, +} from '../packages' import { isWorkerRequest } from './worker' const normalizedClientEntry = normalizePath(CLIENT_ENTRY) @@ -166,12 +171,9 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { const resolveSubpathImports = (id: string, importer?: string) => { if (!importer || !id.startsWith(subpathImportsPrefix)) return const basedir = path.dirname(importer) - const pkgJsonPath = lookupFile(basedir, ['package.json'], { - pathOnly: true, - }) - if (!pkgJsonPath) return + const pkgData = findNearestPackageData(basedir, options.packageCache) + if (!pkgData) return - const pkgData = loadPackageData(pkgJsonPath, options.preserveSymlinks) let importsPath = resolveExportsOrImports( pkgData.data, id, @@ -183,7 +185,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { if (importsPath?.startsWith('.')) { importsPath = path.relative( basedir, - path.join(path.dirname(pkgJsonPath), importsPath), + path.join(pkgData.dir, importsPath), ) if (!importsPath.startsWith('.')) { @@ -605,10 +607,13 @@ function tryResolveFile( return getRealPath(file, options.preserveSymlinks) + postfix } else if (tryIndex) { if (!skipPackageJson) { - const pkgPath = file + '/package.json' + let pkgPath = file + '/package.json' try { + if (!options.preserveSymlinks) { + pkgPath = safeRealpathSync(pkgPath) + } // path points to a node package - const pkg = loadPackageData(pkgPath, options.preserveSymlinks) + const pkg = loadPackageData(pkgPath) const resolved = resolvePackageEntry(file, pkg, targetWeb, options) return resolved } catch (e) { @@ -819,7 +824,9 @@ export function tryNodeResolve( (ssr && !( ext === '.cjs' || - (ext === '.js' && resolvePkg(resolved, options)?.data.type !== 'module') + (ext === '.js' && + findNearestPackageData(resolved, options.packageCache)?.data.type !== + 'module') ) && !(include?.includes(pkgId) || include?.includes(id))) @@ -1188,7 +1195,9 @@ function tryResolveBrowserMapping( ) { let res: string | undefined const pkg = - importer && (idToPkgMap.get(importer) || resolvePkg(importer, options)) + importer && + (idToPkgMap.get(importer) || + findNearestPackageData(importer, options.packageCache)) if (pkg && isObject(pkg.data.browser)) { const mapId = isFilePath ? './' + slash(path.relative(pkg.dir, id)) : id const browserMappedPath = mapWithBrowserField(mapId, pkg.data.browser) @@ -1250,23 +1259,3 @@ function getRealPath(resolved: string, preserveSymlinks?: boolean): string { } return normalizePath(resolved) } - -/** - * Load closest `package.json` to `importer` - */ -function resolvePkg(importer: string, options: InternalResolveOptions) { - const { preserveSymlinks, packageCache } = options - - if (importer.includes('\x00')) { - return null - } - - const pkgPath = lookupFile(importer, ['package.json'], { pathOnly: true }) - if (pkgPath) { - const pkg = loadPackageData(pkgPath, preserveSymlinks, packageCache) - idToPkgMap.set(importer, pkg) - return pkg - } - - return undefined -} diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index f3ecb56f2a7312..f7448c6e1a5a4f 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -13,7 +13,7 @@ import { normalizePath, } from '../utils' import type { Logger, ResolvedConfig } from '..' -import { resolvePkgJsonPath } from '../packages' +import { resolvePackageData } from '../packages' const debug = createDebugger('vite:ssr-external') @@ -257,12 +257,12 @@ function cjsSsrCollectExternals( requireEntry = normalizePath(_require.resolve(id, { paths: [root] })) } catch (e) { // no main entry, but deep imports may be allowed - const pkgPath = resolvePkgJsonPath(id, root) - if (pkgPath) { - if (pkgPath.includes('node_modules')) { + const pkgDir = resolvePackageData(id, root)?.dir + if (pkgDir) { + if (pkgDir.includes('node_modules')) { ssrExternals.add(id) } else { - depsToTrace.add(path.dirname(pkgPath)) + depsToTrace.add(path.dirname(pkgDir)) } continue } @@ -277,9 +277,9 @@ function cjsSsrCollectExternals( } // trace the dependencies of linked packages else if (!esmEntry.includes('node_modules')) { - const pkgPath = resolvePkgJsonPath(id, root) - if (pkgPath) { - depsToTrace.add(path.dirname(pkgPath)) + const pkgDir = resolvePackageData(id, root)?.dir + if (pkgDir) { + depsToTrace.add(pkgDir) } } // has separate esm/require entry, assume require entry is cjs @@ -290,18 +290,11 @@ function cjsSsrCollectExternals( // or are there others like SystemJS / AMD that we'd need to handle? // for now, we'll just leave this as is else if (/\.m?js$/.test(esmEntry)) { - const pkgPath = resolvePkgJsonPath(id, root) - if (!pkgPath) { + const pkg = resolvePackageData(id, root)?.data + if (!pkg) { continue } - const pkgContent = fs.readFileSync(pkgPath, 'utf-8') - - if (!pkgContent) { - continue - } - const pkg = JSON.parse(pkgContent) - if (pkg.type === 'module' || esmEntry.endsWith('.mjs')) { ssrExternals.add(id) continue From 82e02063af073ead5142c36b8f3841a5f305a8f5 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 20:56:23 +0800 Subject: [PATCH 13/15] refactor: use package cache for createResolver and separate cache for optimizer --- packages/vite/src/node/config.ts | 1 + .../src/node/optimizer/esbuildDepPlugin.ts | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index fb56c92bca3ccf..fd365af58aad31 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -583,6 +583,7 @@ export async function resolveConfig( asSrc: true, preferRelative: false, tryIndex: true, + packageCache: resolved.packageCache, ...options, }), ], diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index e6378bc4e94930..38aa1a1f1c407f 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -2,7 +2,7 @@ import path from 'node:path' import type { ImportKind, Plugin } from 'esbuild' import { CSS_LANGS_RE, KNOWN_ASSET_TYPES } from '../constants' import { getDepOptimizationConfig } from '..' -import type { ResolvedConfig } from '..' +import type { PackageCache, ResolvedConfig } from '..' import { flattenId, isBuiltin, @@ -57,14 +57,23 @@ export function esbuildDepPlugin( ? externalTypes.filter((type) => !extensions?.includes('.' + type)) : externalTypes + // use separate package cache for optimizer as it caches paths around node_modules + // and it's unlikely for the core Vite process to traverse into node_modules again + const packageCache: PackageCache = new Map() + // default resolver which prefers ESM - const _resolve = config.createResolver({ asSrc: false, scan: true }) + const _resolve = config.createResolver({ + asSrc: false, + scan: true, + packageCache, + }) // cjs resolver that prefers Node const _resolveRequire = config.createResolver({ asSrc: false, isRequire: true, scan: true, + packageCache, }) const resolve = ( @@ -116,6 +125,11 @@ export function esbuildDepPlugin( return { name: 'vite:dep-pre-bundle', setup(build) { + // clear package cache when esbuild is finished + build.onEnd(() => { + packageCache.clear() + }) + // externalize assets and commonly known non-js file types // See #8459 for more details about this require-import conversion build.onResolve( From a468b808a0731053b4fe58364190470161e07cb2 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 20:58:57 +0800 Subject: [PATCH 14/15] perf: improve some more parts --- packages/vite/src/node/packages.ts | 2 +- packages/vite/src/node/plugins/resolve.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 1e6c82856ff9b7..728c941051f72f 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -124,7 +124,7 @@ export function findNearestPackageData( const pkgPath = path.join(basedir, 'package.json') try { - if (fs.statSync(pkgPath).isFile()) { + if (fs.statSync(pkgPath, { throwIfNoEntry: false })?.isFile()) { const pkgData = loadPackageData(pkgPath) if (packageCache) { diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index a1fc5fbcad44a5..de2b30b89d2d7a 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -609,13 +609,15 @@ function tryResolveFile( if (!skipPackageJson) { let pkgPath = file + '/package.json' try { - if (!options.preserveSymlinks) { - pkgPath = safeRealpathSync(pkgPath) + if (fs.existsSync(pkgPath)) { + if (!options.preserveSymlinks) { + pkgPath = safeRealpathSync(pkgPath) + } + // path points to a node package + const pkg = loadPackageData(pkgPath) + const resolved = resolvePackageEntry(file, pkg, targetWeb, options) + return resolved } - // path points to a node package - const pkg = loadPackageData(pkgPath) - const resolved = resolvePackageEntry(file, pkg, targetWeb, options) - return resolved } catch (e) { if (e.code !== 'ENOENT') { throw e From 43d880253ab0ea9bcc665a5f073c8737c1d288a8 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 21 Mar 2023 22:30:05 +0800 Subject: [PATCH 15/15] fix: remove packageCache in createResolver and split cache for optimizer esm and cjs resolvers --- packages/vite/src/node/config.ts | 1 - packages/vite/src/node/optimizer/esbuildDepPlugin.ts | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 0fc43b697ba06a..4b9ac11f904fb4 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -583,7 +583,6 @@ export async function resolveConfig( asSrc: true, preferRelative: false, tryIndex: true, - packageCache: resolved.packageCache, ...options, }), ], diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index 38aa1a1f1c407f..a01f48eb5a556b 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -59,13 +59,14 @@ export function esbuildDepPlugin( // use separate package cache for optimizer as it caches paths around node_modules // and it's unlikely for the core Vite process to traverse into node_modules again - const packageCache: PackageCache = new Map() + const esmPackageCache: PackageCache = new Map() + const cjsPackageCache: PackageCache = new Map() // default resolver which prefers ESM const _resolve = config.createResolver({ asSrc: false, scan: true, - packageCache, + packageCache: esmPackageCache, }) // cjs resolver that prefers Node @@ -73,7 +74,7 @@ export function esbuildDepPlugin( asSrc: false, isRequire: true, scan: true, - packageCache, + packageCache: cjsPackageCache, }) const resolve = ( @@ -127,7 +128,8 @@ export function esbuildDepPlugin( setup(build) { // clear package cache when esbuild is finished build.onEnd(() => { - packageCache.clear() + esmPackageCache.clear() + cjsPackageCache.clear() }) // externalize assets and commonly known non-js file types