Skip to content

Commit

Permalink
feat: add resolve.preserveSymlinks option
Browse files Browse the repository at this point in the history
  • Loading branch information
ygj6 committed Aug 24, 2021
1 parent 632a50a commit 1095a0e
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 28 deletions.
10 changes: 10 additions & 0 deletions docs/config/index.md
Expand Up @@ -206,6 +206,16 @@ export default defineConfig(async ({ command, mode }) => {

List of file extensions to try for imports that omit extensions. Note it is **NOT** recommended to omit extensions for custom import types (e.g. `.vue`) since it can interfere with IDE and type support.

### resolve.preserveSymlinks

- **Type:** `boolean`
- **Default:** `false`

Enabling this setting causes vite to determine file identity by the original file path (i.e. the path without following symlinks) instead of the real file path (i.e. the path after following symlinks).

- **Related:** [esbuild#preserve-symlinks](https://esbuild.github.io/api/#preserve-symlinks), [webpack#resolve.symlinks
](https://webpack.js.org/configuration/resolve/#resolvesymlinks)

### css.modules

- **Type:**
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/config.ts
Expand Up @@ -437,6 +437,7 @@ export async function resolveConfig(
...config.optimizeDeps,
esbuildOptions: {
keepNames: config.optimizeDeps?.keepNames,
preserveSymlinks: config.resolve?.preserveSymlinks,
...config.optimizeDeps?.esbuildOptions
}
}
Expand Down
99 changes: 79 additions & 20 deletions packages/vite/src/node/plugins/resolve.ts
Expand Up @@ -44,6 +44,7 @@ export interface ResolveOptions {
conditions?: string[]
extensions?: string[]
dedupe?: string[]
preserveSymlinks?: boolean
}

export interface InternalResolveOptions extends ResolveOptions {
Expand Down Expand Up @@ -108,12 +109,14 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {

const options = isRequire ? requireOptions : baseOptions

const preserveSymlinks = !!server?.config.resolve.preserveSymlinks

let res: string | PartialResolvedId | undefined

// explicit fs paths that starts with /@fs/*
if (asSrc && id.startsWith(FS_PREFIX)) {
const fsPath = fsPathFromId(id)
res = tryFsResolve(fsPath, options)
res = tryFsResolve(fsPath, options, preserveSymlinks)
isDebug && debug(`[@fs] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
// always return here even if res doesn't exist since /@fs/ is explicit
// if the file doesn't exist it should be a 404
Expand All @@ -124,7 +127,7 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
// /foo -> /fs-root/foo
if (asSrc && id.startsWith('/')) {
const fsPath = path.resolve(root, id.slice(1))
if ((res = tryFsResolve(fsPath, options))) {
if ((res = tryFsResolve(fsPath, options, preserveSymlinks))) {
isDebug && debug(`[url] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
return res
}
Expand Down Expand Up @@ -159,12 +162,18 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {

if (
targetWeb &&
(res = tryResolveBrowserMapping(fsPath, importer, options, true))
(res = tryResolveBrowserMapping(
fsPath,
importer,
options,
true,
preserveSymlinks
))
) {
return res
}

if ((res = tryFsResolve(fsPath, options))) {
if ((res = tryFsResolve(fsPath, options, preserveSymlinks))) {
isDebug && debug(`[relative] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
const pkg = importer != null && idToPkgMap.get(importer)
if (pkg) {
Expand All @@ -179,7 +188,10 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
}

// absolute fs paths
if (path.isAbsolute(id) && (res = tryFsResolve(id, options))) {
if (
path.isAbsolute(id) &&
(res = tryFsResolve(id, options, preserveSymlinks))
) {
isDebug && debug(`[fs] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
return res
}
Expand Down Expand Up @@ -211,7 +223,13 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {

if (
targetWeb &&
(res = tryResolveBrowserMapping(id, importer, options, false))
(res = tryResolveBrowserMapping(
id,
importer,
options,
false,
preserveSymlinks
))
) {
return res
}
Expand Down Expand Up @@ -278,6 +296,7 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
function tryFsResolve(
fsPath: string,
options: InternalResolveOptions,
preserveSymlinks: boolean,
tryIndex = true,
targetWeb = true
): string | undefined {
Expand All @@ -301,6 +320,7 @@ function tryFsResolve(
options,
false,
targetWeb,
preserveSymlinks,
options.tryPrefix,
options.skipPackageJson
))
Expand All @@ -316,6 +336,7 @@ function tryFsResolve(
options,
false,
targetWeb,
preserveSymlinks,
options.tryPrefix,
options.skipPackageJson
))
Expand All @@ -331,6 +352,7 @@ function tryFsResolve(
options,
tryIndex,
targetWeb,
preserveSymlinks,
options.tryPrefix,
options.skipPackageJson
))
Expand All @@ -345,6 +367,7 @@ function tryResolveFile(
options: InternalResolveOptions,
tryIndex: boolean,
targetWeb: boolean,
preserveSymlinks: boolean,
tryPrefix?: string,
skipPackageJson?: boolean
): string | undefined {
Expand All @@ -365,16 +388,30 @@ function tryResolveFile(
if (fs.existsSync(pkgPath)) {
// path points to a node package
const pkg = loadPackageData(pkgPath)
return resolvePackageEntry(file, pkg, options, targetWeb)
const resolved = resolvePackageEntry(
file,
pkg,
options,
targetWeb,
preserveSymlinks
)
return resolved ? getRealPath(resolved, preserveSymlinks) : resolved
}
}
const index = tryFsResolve(file + '/index', options)
const index = tryFsResolve(file + '/index', options, preserveSymlinks)
if (index) return index + postfix
}
}
if (tryPrefix) {
const prefixed = `${path.dirname(file)}/${tryPrefix}${path.basename(file)}`
return tryResolveFile(prefixed, postfix, options, tryIndex, targetWeb)
return tryResolveFile(
prefixed,
postfix,
options,
tryIndex,
targetWeb,
preserveSymlinks
)
}
}

Expand Down Expand Up @@ -405,18 +442,23 @@ export function tryNodeResolve(
basedir = root
}

const pkg = resolvePackageData(pkgId, basedir)
const preserveSymlinks = !!server?.config.resolve.preserveSymlinks

const pkg = resolvePackageData(pkgId, basedir, preserveSymlinks)

if (!pkg) {
return
}

let resolved = deepMatch
? resolveDeepImport(id, pkg, options, targetWeb)
: resolvePackageEntry(id, pkg, options, targetWeb)
? resolveDeepImport(id, pkg, options, targetWeb, preserveSymlinks)
: resolvePackageEntry(id, pkg, options, targetWeb, preserveSymlinks)
if (!resolved) {
return
}

resolved = getRealPath(resolved, preserveSymlinks)

// link id to pkg for browser field mapping check
idToPkgMap.set(resolved, pkg)
if (isBuild) {
Expand Down Expand Up @@ -504,14 +546,15 @@ const packageCache = new Map<string, PackageData>()

export function resolvePackageData(
id: string,
basedir: string
basedir: string,
preserveSymlinks: boolean
): PackageData | undefined {
const cacheKey = id + basedir
if (packageCache.has(cacheKey)) {
return packageCache.get(cacheKey)
}
try {
const pkgPath = resolveFrom(`${id}/package.json`, basedir)
const pkgPath = resolveFrom(`${id}/package.json`, basedir, preserveSymlinks)
return loadPackageData(pkgPath, cacheKey)
} catch (e) {
isDebug && debug(`${chalk.red(`[failed loading package.json]`)} ${id}`)
Expand Down Expand Up @@ -560,7 +603,8 @@ export function resolvePackageEntry(
id: string,
{ dir, data, setResolvedCache, getResolvedCache }: PackageData,
options: InternalResolveOptions,
targetWeb: boolean
targetWeb: boolean,
preserveSymlinks = false
): string | undefined {
const cached = getResolvedCache('.', targetWeb)
if (cached) {
Expand Down Expand Up @@ -597,7 +641,8 @@ export function resolvePackageEntry(
// instead; Otherwise, assume it's ESM and use it.
const resolvedBrowserEntry = tryFsResolve(
path.join(dir, browserEntry),
options
options,
preserveSymlinks
)
if (resolvedBrowserEntry) {
const content = fs.readFileSync(resolvedBrowserEntry, 'utf-8')
Expand Down Expand Up @@ -643,7 +688,11 @@ export function resolvePackageEntry(
}

entryPoint = path.join(dir, entryPoint)
const resolvedEntryPoint = tryFsResolve(entryPoint, options)
const resolvedEntryPoint = tryFsResolve(
entryPoint,
options,
preserveSymlinks
)

if (resolvedEntryPoint) {
isDebug &&
Expand Down Expand Up @@ -700,7 +749,8 @@ function resolveDeepImport(
data
}: PackageData,
options: InternalResolveOptions,
targetWeb: boolean
targetWeb: boolean,
preserveSymlinks: boolean
): string | undefined {
id = '.' + id.slice(data.name.length)
const cache = getResolvedCache(id, targetWeb)
Expand Down Expand Up @@ -738,6 +788,7 @@ function resolveDeepImport(
const resolved = tryFsResolve(
path.join(dir, relativeId),
options,
preserveSymlinks,
!exportsField, // try index only if no exports field
targetWeb
)
Expand All @@ -754,7 +805,8 @@ function tryResolveBrowserMapping(
id: string,
importer: string | undefined,
options: InternalResolveOptions,
isFilePath: boolean
isFilePath: boolean,
preserveSymlinks: boolean
) {
let res: string | undefined
const pkg = importer && idToPkgMap.get(importer)
Expand All @@ -763,7 +815,7 @@ function tryResolveBrowserMapping(
const browserMappedPath = mapWithBrowserField(mapId, pkg.data.browser)
if (browserMappedPath) {
const fsPath = path.join(pkg.dir, browserMappedPath)
if ((res = tryFsResolve(fsPath, options))) {
if ((res = tryFsResolve(fsPath, options, preserveSymlinks))) {
isDebug &&
debug(`[browser mapped] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
idToPkgMap.set(res, pkg)
Expand Down Expand Up @@ -807,3 +859,10 @@ function mapWithBrowserField(
function equalWithoutSuffix(path: string, key: string, suffix: string) {
return key.endsWith(suffix) && key.slice(0, -suffix.length) === path
}

function getRealPath(resolved: string, preserveSymlinks?: boolean): string {
if (!preserveSymlinks && browserExternalId !== resolved) {
return (resolved = normalizePath(fs.realpathSync(resolved)))
}
return resolved
}
4 changes: 3 additions & 1 deletion packages/vite/src/node/ssr/ssrExternal.ts
Expand Up @@ -96,7 +96,9 @@ export function resolveSSRExternal(
}

for (const id of depsToTrace) {
const depRoot = path.dirname(resolveFrom(`${id}/package.json`, root))
const depRoot = path.dirname(
resolveFrom(`${id}/package.json`, root, !!config.resolve.preserveSymlinks)
)
resolveSSRExternal(
{
...config,
Expand Down
25 changes: 20 additions & 5 deletions packages/vite/src/node/ssr/ssrModuleLoader.ts
Expand Up @@ -91,7 +91,12 @@ async function instantiateModule(

const ssrImport = async (dep: string) => {
if (dep[0] !== '.' && dep[0] !== '/') {
return nodeRequire(dep, mod.file, server.config.root)
return nodeRequire(
dep,
mod.file,
server.config.root,
!!server.config.resolve.preserveSymlinks
)
}
dep = unwrapId(dep)
if (!isCircular(dep) && !pendingImports.get(dep)?.some(isCircular)) {
Expand Down Expand Up @@ -169,8 +174,13 @@ async function instantiateModule(
return Object.freeze(ssrModule)
}

function nodeRequire(id: string, importer: string | null, root: string) {
const mod = require(resolve(id, importer, root))
function nodeRequire(
id: string,
importer: string | null,
root: string,
preserveSymlinks: boolean
) {
const mod = require(resolve(id, importer, root, preserveSymlinks))
const defaultExport = mod.__esModule ? mod.default : mod
// rollup-style default import interop for cjs
return new Proxy(mod, {
Expand All @@ -183,7 +193,12 @@ function nodeRequire(id: string, importer: string | null, root: string) {

const resolveCache = new Map<string, string>()

function resolve(id: string, importer: string | null, root: string) {
function resolve(
id: string,
importer: string | null,
root: string,
preserveSymlinks: boolean
) {
const key = id + importer + root
const cached = resolveCache.get(key)
if (cached) {
Expand All @@ -193,7 +208,7 @@ function resolve(id: string, importer: string | null, root: string) {
importer && fs.existsSync(cleanUrl(importer))
? path.dirname(importer)
: root
const resolved = resolveFrom(id, resolveDir, true)
const resolved = resolveFrom(id, resolveDir, preserveSymlinks, true)
resolveCache.set(key, resolved)
return resolved
}
9 changes: 7 additions & 2 deletions packages/vite/src/node/utils.ts
Expand Up @@ -40,12 +40,17 @@ try {

const ssrExtensions = ['.js', '.json', '.node']

export function resolveFrom(id: string, basedir: string, ssr = false): string {
export function resolveFrom(
id: string,
basedir: string,
preserveSymlinks = false,
ssr = false
): string {
return resolve.sync(id, {
basedir,
extensions: ssr ? ssrExtensions : DEFAULT_EXTENSIONS,
// necessary to work with pnpm
preserveSymlinks: isRunningWithYarnPnp || false
preserveSymlinks: preserveSymlinks || isRunningWithYarnPnp || false
})
}

Expand Down

0 comments on commit 1095a0e

Please sign in to comment.