Skip to content

Commit

Permalink
feat(resolve): support subpath imports
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson committed Nov 14, 2022
1 parent 5ed1379 commit b3ef8f3
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 10 deletions.
23 changes: 22 additions & 1 deletion packages/vite/src/node/packages.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -27,6 +27,7 @@ export interface PackageData {
main: string
module: string
browser: string | Record<string, string | false>
imports: Record<string, any>
exports: string | Record<string, any> | string[]
dependencies: Record<string, string>
}
Expand Down Expand Up @@ -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<string>()
let watchFile = (id: string) => {
Expand Down
64 changes: 55 additions & 9 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ import type { DepsOptimizer } from '../optimizer'
import type { SSROptions } from '..'
import {
findPackageJson,
isNamedPackage,
isWorkspaceRoot,
loadNearestPackageData,
PackageCache,
PackageData
} from '../packages'
Expand Down Expand Up @@ -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<any>(
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()
Expand Down Expand Up @@ -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.
}
Expand Down

0 comments on commit b3ef8f3

Please sign in to comment.