From b3ef8f33c8c52cbe6e845b2e18ace7ace0ffd3ca Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Mon, 14 Nov 2022 15:27:27 -0500 Subject: [PATCH] feat(resolve): support subpath imports https://nodejs.org/api/packages.html#subpath-imports --- packages/vite/src/node/packages.ts | 23 +++++++- packages/vite/src/node/plugins/resolve.ts | 64 +++++++++++++++++++---- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 996809db638d5e..b2a020199f848e 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -1,6 +1,6 @@ import fs from 'node:fs' import path from 'node:path' -import { createDebugger, createFilter, resolveFrom } from './utils' +import { createDebugger, createFilter, lookupFile, resolveFrom } from './utils' import type { ResolvedConfig } from './config' import type { Plugin } from './plugin' @@ -27,6 +27,7 @@ export interface PackageData { main: string module: string browser: string | Record + imports: Record exports: string | Record | string[] dependencies: Record } @@ -131,6 +132,26 @@ export function loadPackageData( return pkg } +export function loadNearestPackageData( + startDir: string, + options?: { preserveSymlinks?: boolean }, + predicate?: (pkg: PackageData) => boolean +): PackageData | null { + let importerPkg: PackageData | undefined + lookupFile(startDir, ['package.json'], { + pathOnly: true, + predicate(pkgPath) { + importerPkg = loadPackageData(pkgPath, options) + return !predicate || predicate(importerPkg) + } + }) + return importerPkg || null +} + +export function isNamedPackage(pkg: PackageData): boolean { + return !!pkg.data.name +} + export function watchPackageDataPlugin(config: ResolvedConfig): Plugin { const watchQueue = new Set() let watchFile = (id: string) => { diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index c2a7cc21cec0e4..5f59761671388f 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -45,7 +45,9 @@ import type { DepsOptimizer } from '../optimizer' import type { SSROptions } from '..' import { findPackageJson, + isNamedPackage, isWorkspaceRoot, + loadNearestPackageData, PackageCache, PackageData } from '../packages' @@ -277,6 +279,58 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { } } + // handle subpath imports + // https://nodejs.org/api/packages.html#subpath-imports + if (id[0] === '#') { + if (!importer) { + return // Module not found. + } + const importerPkg = loadNearestPackageData( + path.dirname(importer), + options, + isNamedPackage + ) + if (!importerPkg || !importerPkg.data.imports) { + return // Module not found. + } + // Rewrite the `imports` object to be compatible with the + // `resolve.exports` package. + const { name, imports } = importerPkg.data + const exports = Object.fromEntries( + Object.entries(imports).map((entry) => { + entry[0] = entry[0].replace(/^#/, './') + return entry + }) + ) + const possiblePaths = resolveExports( + { name, exports }, + id.replace(/^#/, './'), + options, + getInlineConditions(options, targetWeb), + options.overrideConditions + ) + if (!possiblePaths.length) { + throw new Error( + `Package subpath '${id}' is not defined by "imports" in ` + + `${path.join(importerPkg.dir, 'package.json')}.` + ) + } + for (const possiblePath of possiblePaths) { + if (possiblePath[0] === '#') { + continue // Subpath imports cannot be recursive. + } + const resolved = await this.resolve( + possiblePath, + importer, + resolveOpts + ) + if (resolved) { + return resolved + } + } + return // Module not found. + } + // drive relative fs paths (only windows) if (isWindows && id.startsWith('/')) { const basedir = importer ? path.dirname(importer) : process.cwd() @@ -817,15 +871,7 @@ export function tryNodeResolve( // Some projects (like Svelte) have nameless package.json files to // appease older Node.js versions and they don't have the list of // optional peer dependencies like the root package.json does. - let basePkg: PackageData | undefined - lookupFile(basedir, ['package.json'], { - pathOnly: true, - predicate(pkgPath) { - basePkg = loadPackageData(pkgPath, preserveSymlinks, packageCache) - return !!basePkg.data.name - } - }) - + const basePkg = loadNearestPackageData(basedir, options, isNamedPackage) if (!basePkg) { return // Module not found. }