From e78738bceb7e11e00be0ae5711756c4b06d518b4 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 16 Mar 2023 20:19:29 +0800 Subject: [PATCH 1/6] refactor: use custom pkg json resolver --- packages/vite/package.json | 1 + packages/vite/src/node/optimizer/index.ts | 22 ++++++++-- packages/vite/src/node/packages.ts | 51 ++++++++++++++++++++++- packages/vite/src/node/ssr/ssrExternal.ts | 8 ++-- packages/vite/src/node/utils.ts | 19 --------- pnpm-lock.yaml | 6 +++ 6 files changed, 79 insertions(+), 28 deletions(-) diff --git a/packages/vite/package.json b/packages/vite/package.json index 31a2edc17f91cf..3d438cddc27929 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -86,6 +86,7 @@ "@rollup/plugin-node-resolve": "15.0.1", "@rollup/plugin-typescript": "^11.0.0", "@rollup/pluginutils": "^5.0.2", + "@types/pnpapi": "^0.0.2", "acorn": "^8.8.2", "acorn-walk": "^8.2.0", "cac": "^6.7.14", diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 62b3a9f14d7494..8ad0d2696ea7e4 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -18,7 +18,6 @@ import { getHash, isOptimizable, lookupFile, - nestedResolveFrom, normalizeId, normalizePath, removeDir, @@ -27,6 +26,7 @@ import { } from '../utils' import { transformWithEsbuild } from '../plugins/esbuild' import { ESBUILD_MODULES_TARGET } from '../constants' +import { resolvePkgJsonPath } from '../packages' import { esbuildCjsExternalPlugin, esbuildDepPlugin } from './esbuildDepPlugin' import { scanImports } from './scan' export { @@ -855,16 +855,32 @@ function createOptimizeDepsIncludeResolver( // 'foo > bar > baz' => 'foo > bar' & 'baz' const nestedRoot = id.substring(0, lastArrowIndex).trim() const nestedPath = id.substring(lastArrowIndex + 1).trim() - const basedir = nestedResolveFrom( + const basedir = nestedResolvePkgJsonPath( nestedRoot, config.root, config.resolve.preserveSymlinks, - ssr, ) return await resolve(nestedPath, basedir, undefined, ssr) } } +/** + * Like `resolvePkgJsonPath`, but supports resolving nested package names with '>' + */ +function nestedResolvePkgJsonPath( + id: string, + basedir: string, + preserveSymlinks = false, +) { + const pkgs = id.split('>').map((pkg) => pkg.trim()) + try { + for (const pkg of pkgs) { + basedir = resolvePkgJsonPath(pkg, basedir, preserveSymlinks) + } + } catch {} + return basedir +} + export function newDepOptimizationProcessing(): DepOptimizationProcessing { let resolve: () => void const promise = new Promise((_resolve) => { diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 1451d18853729a..5c292e35f01742 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -1,9 +1,18 @@ import fs from 'node:fs' import path from 'node:path' -import { createDebugger, createFilter, resolveFrom } from './utils' +import { createRequire } from 'node:module' +import { createDebugger, createFilter } from './utils' import type { ResolvedConfig } from './config' import type { Plugin } from './plugin' +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +let pnp: typeof import('pnpapi') | undefined +if (process.versions.pnp) { + try { + pnp = createRequire(import.meta.url)('pnpapi') + } catch {} +} + const isDebug = process.env.DEBUG const debug = createDebugger('vite:resolve-details', { onlyWhenFocused: true, @@ -62,7 +71,7 @@ export function resolvePackageData( } let pkgPath: string | undefined try { - pkgPath = resolveFrom(`${id}/package.json`, basedir, preserveSymlinks) + pkgPath = resolvePkgJsonPath(id, basedir, preserveSymlinks) pkg = loadPackageData(pkgPath, true, packageCache) if (packageCache) { packageCache.set(cacheKey!, pkg) @@ -178,3 +187,41 @@ export function watchPackageDataPlugin(config: ResolvedConfig): Plugin { }, } } + +export function resolvePkgJsonPath( + pkgName: string, + basedir: string, + preserveSymlinks = false, +): string { + if (pnp) { + const pkg = pnp.resolveToUnqualified(pkgName, basedir) + if (!pkg) { + throw pkgNotFoundError(pkgName, basedir) + } + 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 : fs.realpathSync.native(pkg) + } + } catch {} + const nextRoot = path.dirname(root) + if (nextRoot === root) break + root = nextRoot + } + + throw pkgNotFoundError(pkgName, basedir) +} + +function pkgNotFoundError(pkgName: string, basedir: string) { + const error = new Error( + `Unable to resolve dependency "${pkgName}" from "${basedir}"` + + (pnp ? ' in Yarn PnP' : ''), + ) + ;(error as any).code = 'MODULE_NOT_FOUND' + return error +} diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index ce08b020d17fbf..4829d69c7f0517 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -11,9 +11,9 @@ import { isDefined, lookupFile, normalizePath, - resolveFrom, } from '../utils' import type { Logger, ResolvedConfig } from '..' +import { resolvePkgJsonPath } from '../packages' const debug = createDebugger('vite:ssr-external') @@ -258,7 +258,7 @@ function cjsSsrCollectExternals( } catch (e) { try { // no main entry, but deep imports may be allowed - const pkgPath = resolveFrom(`${id}/package.json`, root) + const pkgPath = resolvePkgJsonPath(id, root) if (pkgPath.includes('node_modules')) { ssrExternals.add(id) } else { @@ -277,7 +277,7 @@ function cjsSsrCollectExternals( } // trace the dependencies of linked packages else if (!esmEntry.includes('node_modules')) { - const pkgPath = resolveFrom(`${id}/package.json`, root) + const pkgPath = resolvePkgJsonPath(id, root) depsToTrace.add(path.dirname(pkgPath)) } // has separate esm/require entry, assume require entry is cjs @@ -288,7 +288,7 @@ 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 = resolveFrom(`${id}/package.json`, root) + const pkgPath = resolvePkgJsonPath(id, root) const pkgContent = fs.readFileSync(pkgPath, 'utf-8') if (!pkgContent) { diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index c12e429c51529d..2b8db66be1d19f 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -153,25 +153,6 @@ export function resolveFrom( }) } -/** - * like `resolveFrom` but supports resolving `>` path in `id`, - * for example: `foo > bar > baz` - */ -export function nestedResolveFrom( - id: string, - basedir: string, - preserveSymlinks = false, - ssr = false, -): string { - const pkgs = id.split('>').map((pkg) => pkg.trim()) - try { - for (const pkg of pkgs) { - basedir = resolveFrom(pkg, basedir, preserveSymlinks, ssr) - } - } catch {} - return basedir -} - // set in bin/vite.js const filter = process.env.VITE_DEBUG_FILTER diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16a012761797ae..e8003654849f45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,6 +177,7 @@ importers: '@rollup/plugin-node-resolve': 15.0.1 '@rollup/plugin-typescript': ^11.0.0 '@rollup/pluginutils': ^5.0.2 + '@types/pnpapi': ^0.0.2 acorn: ^8.8.2 acorn-walk: ^8.2.0 cac: ^6.7.14 @@ -246,6 +247,7 @@ importers: '@rollup/plugin-node-resolve': 15.0.1_rollup@3.18.0 '@rollup/plugin-typescript': 11.0.0_rollup@3.18.0+tslib@2.5.0 '@rollup/pluginutils': 5.0.2_rollup@3.18.0 + '@types/pnpapi': 0.0.2 acorn: 8.8.2 acorn-walk: 8.2.0_acorn@8.8.2 cac: 6.7.14 @@ -3478,6 +3480,10 @@ packages: resolution: {integrity: sha512-O397rnSS9iQI4OirieAtsDqvCj4+3eY1J+EPdNTKuHuRWIfUoGyzX294o8C4KJYaLqgSrd2o60c5EqCU8Zv02g==} dev: true + /@types/pnpapi/0.0.2: + resolution: {integrity: sha512-2lqsQt1iXkiTqwuzw2SS50iduoPZNpV/ou4/vJD443C0weF63Gqd3ErGS811CBNMzLO64zVJ+tiyh+4yimdYkg==} + dev: true + /@types/prompts/2.4.2: resolution: {integrity: sha512-TwNx7qsjvRIUv/BCx583tqF5IINEVjCNqg9ofKHRlSoUHE62WBHrem4B1HGXcIrG511v29d1kJ9a/t2Esz7MIg==} dependencies: From f3bd3087e026a26227615dd92ef65abfd4734773 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 16 Mar 2023 20:21:11 +0800 Subject: [PATCH 2/6] refactor: simplify pnp check --- packages/vite/src/node/utils.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 2b8db66be1d19f..73f5dbea0b7f3e 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -127,15 +127,9 @@ export function isOptimizable( export const bareImportRE = /^[\w@](?!.*:\/\/)/ export const deepImportRE = /^([^@][^/]*)\/|^(@[^/]+\/[^/]+)\// -export let isRunningWithYarnPnp: boolean - // TODO: use import() const _require = createRequire(import.meta.url) -try { - isRunningWithYarnPnp = Boolean(_require('pnpapi')) -} catch {} - const ssrExtensions = ['.js', '.cjs', '.json', '.node'] export function resolveFrom( @@ -149,7 +143,7 @@ export function resolveFrom( paths: [], extensions: ssr ? ssrExtensions : DEFAULT_EXTENSIONS, // necessary to work with pnpm - preserveSymlinks: preserveSymlinks || isRunningWithYarnPnp || false, + preserveSymlinks: preserveSymlinks || !!process.versions.pnp || false, }) } From f2ad8bf0d89d34d6156fea2b5022bf6c5699ba69 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 16 Mar 2023 21:04:26 +0800 Subject: [PATCH 3/6] refactor: remove error generation --- packages/vite/src/node/optimizer/index.ts | 8 +++---- packages/vite/src/node/packages.ts | 27 +++++------------------ packages/vite/src/node/ssr/ssrExternal.ts | 18 ++++++++++----- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 8ad0d2696ea7e4..42620fc2ff7ab2 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -873,11 +873,9 @@ function nestedResolvePkgJsonPath( preserveSymlinks = false, ) { const pkgs = id.split('>').map((pkg) => pkg.trim()) - try { - for (const pkg of pkgs) { - basedir = resolvePkgJsonPath(pkg, basedir, preserveSymlinks) - } - } catch {} + for (const pkg of pkgs) { + basedir = resolvePkgJsonPath(pkg, basedir, preserveSymlinks) || basedir + } return basedir } diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 5c292e35f01742..4ac3a0d26361c8 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -69,9 +69,9 @@ export function resolvePackageData( return pkg } } - let pkgPath: string | undefined + const pkgPath = resolvePkgJsonPath(id, basedir, preserveSymlinks) + if (!pkgPath) return null try { - pkgPath = resolvePkgJsonPath(id, basedir, preserveSymlinks) pkg = loadPackageData(pkgPath, true, packageCache) if (packageCache) { packageCache.set(cacheKey!, pkg) @@ -81,12 +81,8 @@ export function resolvePackageData( if (e instanceof SyntaxError) { isDebug && debug(`Parsing failed: ${pkgPath}`) } - // Ignore error for missing package.json - else if (e.code !== 'MODULE_NOT_FOUND') { - throw e - } + throw e } - return null } export function loadPackageData( @@ -192,12 +188,10 @@ export function resolvePkgJsonPath( pkgName: string, basedir: string, preserveSymlinks = false, -): string { +): string | undefined { if (pnp) { const pkg = pnp.resolveToUnqualified(pkgName, basedir) - if (!pkg) { - throw pkgNotFoundError(pkgName, basedir) - } + if (!pkg) return undefined return path.join(pkg, 'package.json') } @@ -214,14 +208,5 @@ export function resolvePkgJsonPath( root = nextRoot } - throw pkgNotFoundError(pkgName, basedir) -} - -function pkgNotFoundError(pkgName: string, basedir: string) { - const error = new Error( - `Unable to resolve dependency "${pkgName}" from "${basedir}"` + - (pnp ? ' in Yarn PnP' : ''), - ) - ;(error as any).code = 'MODULE_NOT_FOUND' - return error + return undefined } diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 4829d69c7f0517..5dafad5614c811 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -256,16 +256,16 @@ function cjsSsrCollectExternals( // which returns with '/', require.resolve returns with '\\' requireEntry = normalizePath(_require.resolve(id, { paths: [root] })) } catch (e) { - try { - // no main entry, but deep imports may be allowed - const pkgPath = resolvePkgJsonPath(id, root) + // no main entry, but deep imports may be allowed + const pkgPath = resolvePkgJsonPath(id, root) + if (pkgPath) { if (pkgPath.includes('node_modules')) { ssrExternals.add(id) } else { depsToTrace.add(path.dirname(pkgPath)) } continue - } catch {} + } // resolve failed, assume include debug(`Failed to resolve entries for package "${id}"\n`, e) @@ -278,7 +278,10 @@ function cjsSsrCollectExternals( // trace the dependencies of linked packages else if (!esmEntry.includes('node_modules')) { const pkgPath = resolvePkgJsonPath(id, root) - depsToTrace.add(path.dirname(pkgPath)) + // NOTE (temp): if pkg not found, we skip instead of error, is this problematic? + if (pkgPath) { + depsToTrace.add(path.dirname(pkgPath)) + } } // has separate esm/require entry, assume require entry is cjs else if (esmEntry !== requireEntry) { @@ -289,6 +292,11 @@ function cjsSsrCollectExternals( // for now, we'll just leave this as is else if (/\.m?js$/.test(esmEntry)) { const pkgPath = resolvePkgJsonPath(id, root) + // NOTE (temp): if pkg not found, we skip instead of error, is this problematic? + if (!pkgPath) { + continue + } + const pkgContent = fs.readFileSync(pkgPath, 'utf-8') if (!pkgContent) { From 3ab62930cbff7b48a60fa00ddbbf2fc8802833ef Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 16 Mar 2023 21:34:11 +0800 Subject: [PATCH 4/6] refactor: simplify browser field package.json handling --- packages/vite/src/node/plugins/resolve.ts | 42 ++++++----------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 4cdd2043cc41b7..39c74d5b267e93 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -707,12 +707,9 @@ export function tryNodeResolve( const rootPkgId = possiblePkgIds[0] - const rootPkg = resolvePackageData( - rootPkgId, - basedir, - preserveSymlinks, - packageCache, - )! + const rootPkg = rootPkgId + ? resolvePackageData(rootPkgId, basedir, preserveSymlinks, packageCache) + : undefined const nearestPkgId = [...possiblePkgIds].reverse().find((pkgId) => { nearestPkg = resolvePackageData( @@ -1298,38 +1295,21 @@ function getRealPath(resolved: string, preserveSymlinks?: boolean): string { } /** - * if importer was not resolved by vite's resolver previously - * (when esbuild resolved it) - * resolve importer's pkg and add to idToPkgMap + * Load closest `package.json` to `importer` */ function resolvePkg(importer: string, options: InternalResolveOptions) { - const { root, preserveSymlinks, packageCache } = options + const { preserveSymlinks, packageCache } = options if (importer.includes('\x00')) { return null } - const possiblePkgIds: string[] = [] - for (let prevSlashIndex = -1; ; ) { - const slashIndex = importer.indexOf(isWindows ? '\\' : '/', prevSlashIndex) - if (slashIndex < 0) { - break - } - - prevSlashIndex = slashIndex + 1 - - const possiblePkgId = importer.slice(0, slashIndex) - possiblePkgIds.push(possiblePkgId) - } - - let pkg: PackageData | undefined - possiblePkgIds.reverse().find((pkgId) => { - pkg = resolvePackageData(pkgId, root, preserveSymlinks, packageCache)! - return pkg - })! - - if (pkg) { + const pkgPath = lookupFile(importer, ['package.json'], { pathOnly: true }) + if (pkgPath) { + const pkg = loadPackageData(pkgPath, preserveSymlinks, packageCache) idToPkgMap.set(importer, pkg) + return pkg } - return pkg + + return undefined } From 084b664b00da95ec0bcdb952b0073c5c4dc02a52 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 16 Mar 2023 21:38:05 +0800 Subject: [PATCH 5/6] fix: handle realpath in windows network drive --- packages/vite/src/node/packages.ts | 6 +++--- packages/vite/src/node/utils.ts | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 4ac3a0d26361c8..a26c6e50a04350 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 } from './utils' +import { createDebugger, createFilter, safeRealpathSync } from './utils' import type { ResolvedConfig } from './config' import type { Plugin } from './plugin' @@ -91,7 +91,7 @@ export function loadPackageData( packageCache?: PackageCache, ): PackageData { if (!preserveSymlinks) { - pkgPath = fs.realpathSync.native(pkgPath) + pkgPath = safeRealpathSync(pkgPath) } let cached: PackageData | undefined @@ -200,7 +200,7 @@ export function resolvePkgJsonPath( const pkg = path.join(root, 'node_modules', pkgName, 'package.json') try { if (fs.existsSync(pkg)) { - return preserveSymlinks ? pkg : fs.realpathSync.native(pkg) + return preserveSymlinks ? pkg : safeRealpathSync(pkg) } } catch {} const nextRoot = path.dirname(root) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 73f5dbea0b7f3e..acc2f33ccf7449 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -592,6 +592,11 @@ export const removeDir = isWindows } export const renameDir = isWindows ? promisify(gracefulRename) : fs.renameSync +// `fs.realpathSync.native` has a bug in Windows network drive that prepends a slash +export const safeRealpathSync = isWindows + ? fs.realpathSync + : fs.realpathSync.native + export function ensureWatchedFile( watcher: FSWatcher, file: string | null, From a8c38692dbdd499830c77ba45925c8990d39a9a4 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 16 Mar 2023 22:26:39 +0800 Subject: [PATCH 6/6] docs: update comments --- packages/vite/src/node/ssr/ssrExternal.ts | 2 -- packages/vite/src/node/utils.ts | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 5dafad5614c811..f3ecb56f2a7312 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -278,7 +278,6 @@ function cjsSsrCollectExternals( // trace the dependencies of linked packages else if (!esmEntry.includes('node_modules')) { const pkgPath = resolvePkgJsonPath(id, root) - // NOTE (temp): if pkg not found, we skip instead of error, is this problematic? if (pkgPath) { depsToTrace.add(path.dirname(pkgPath)) } @@ -292,7 +291,6 @@ function cjsSsrCollectExternals( // for now, we'll just leave this as is else if (/\.m?js$/.test(esmEntry)) { const pkgPath = resolvePkgJsonPath(id, root) - // NOTE (temp): if pkg not found, we skip instead of error, is this problematic? if (!pkgPath) { continue } diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index acc2f33ccf7449..72a62d5e095a65 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -592,7 +592,9 @@ export const removeDir = isWindows } export const renameDir = isWindows ? promisify(gracefulRename) : fs.renameSync -// `fs.realpathSync.native` has a bug in Windows network drive that prepends a slash +// `fs.realpathSync.native` resolves differently in Windows network drive, +// causing file read errors. skip for now. +// https://github.com/nodejs/node/issues/37737 export const safeRealpathSync = isWindows ? fs.realpathSync : fs.realpathSync.native