Skip to content

Commit

Permalink
fix(ssr): use tryNodeResolve instead of resolveFrom (vitejs#3951)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson committed Jul 30, 2021
1 parent 7ef25e7 commit a6e3078
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 30 deletions.
2 changes: 2 additions & 0 deletions packages/vite/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill'
import { webWorkerPlugin } from './worker'
import { preAliasPlugin } from './preAlias'
import { definePlugin } from './define'
import { ssrRequireHookPlugin } from './ssrRequireHook'

export async function resolvePlugins(
config: ResolvedConfig,
Expand Down Expand Up @@ -42,6 +43,7 @@ export async function resolvePlugins(
ssrTarget: config.ssr?.target,
asSrc: true
}),
config.build.ssr ? ssrRequireHookPlugin(config) : null,
htmlInlineScriptProxyPlugin(),
cssPlugin(config),
config.esbuild !== false ? esbuildPlugin(config.esbuild) : null,
Expand Down
17 changes: 11 additions & 6 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
normalizePath,
fsPathFromId,
ensureVolumeInPath,
resolveFrom,
isDataUrl,
cleanUrl,
slash
Expand All @@ -29,6 +28,7 @@ import { ViteDevServer, SSRTarget } from '..'
import { createFilter } from '@rollup/pluginutils'
import { PartialResolvedId } from 'rollup'
import { resolve as _resolveExports } from 'resolve.exports'
import resolve from 'resolve'

// special id for paths marked with browser: false
// https://github.com/defunctzombie/package-browser-field-spec#ignore-a-module
Expand Down Expand Up @@ -61,6 +61,7 @@ export interface InternalResolveOptions extends ResolveOptions {
tryPrefix?: string
preferRelative?: boolean
isRequire?: boolean
preserveSymlinks?: boolean
}

export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
Expand Down Expand Up @@ -361,7 +362,7 @@ export const idToPkgMap = new Map<string, PackageData>()

export function tryNodeResolve(
id: string,
importer: string | undefined,
importer: string | null | undefined,
options: InternalResolveOptions,
targetWeb: boolean,
server?: ViteDevServer,
Expand All @@ -379,12 +380,12 @@ export function tryNodeResolve(
path.isAbsolute(importer) &&
fs.existsSync(cleanUrl(importer))
) {
basedir = path.dirname(importer)
basedir = fs.realpathSync.native(path.dirname(importer))
} else {
basedir = root
}

const pkg = resolvePackageData(pkgId, basedir)
const pkg = resolvePackageData(pkgId, basedir, options.preserveSymlinks)

if (!pkg) {
return
Expand Down Expand Up @@ -483,14 +484,18 @@ const packageCache = new Map<string, PackageData>()

export function resolvePackageData(
id: string,
basedir: string
basedir: string,
preserveSymlinks = false
): PackageData | undefined {
const cacheKey = id + basedir
if (packageCache.has(cacheKey)) {
return packageCache.get(cacheKey)
}
try {
const pkgPath = resolveFrom(`${id}/package.json`, basedir)
const pkgPath = resolve.sync(`${id}/package.json`, {
basedir,
preserveSymlinks
})
return loadPackageData(pkgPath, cacheKey)
} catch (e) {
isDebug && debug(`${chalk.red(`[failed loading package.json]`)} ${id}`)
Expand Down
64 changes: 64 additions & 0 deletions packages/vite/src/node/plugins/ssrRequireHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import MagicString from 'magic-string'
import { ResolvedConfig } from '..'
import { Plugin } from '../plugin'

export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null {
if (config.command !== 'build' || !config.resolve.dedupe?.length) {
return null
}
return {
name: 'vite:ssr-require-hook',
transform(code, id) {
const moduleInfo = this.getModuleInfo(id)
if (moduleInfo?.isEntry) {
const s = new MagicString(code)
s.prepend(
`;(${dedupeRequire.toString()})(${JSON.stringify(
config.resolve.dedupe
)});\n`
)
return {
code: s.toString(),
map: s.generateMap({
source: id
})
}
}
}
}
}

type NodeResolveFilename = (
request: string,
parent: NodeModule,
isMain: boolean,
options?: Record<string, any>
) => string

/** Respect the `resolve.dedupe` option in production SSR. */
function dedupeRequire(dedupe: string[]) {
const Module = require('module') as { _resolveFilename: NodeResolveFilename }
const resolveFilename = Module._resolveFilename
Module._resolveFilename = function (request, parent, isMain, options) {
if (request[0] !== '.' && request[0] !== '/') {
const parts = request.split('/')
const pkgName = parts[0][0] === '@' ? parts[0] + '/' + parts[1] : parts[0]
if (dedupe.includes(pkgName)) {
// Use this module as the parent.
parent = module
}
}
return resolveFilename!(request, parent, isMain, options)
}
}

export function hookNodeResolve(
getResolver: (resolveFilename: NodeResolveFilename) => NodeResolveFilename
): () => void {
const Module = require('module') as { _resolveFilename: NodeResolveFilename }
const prevResolver = Module._resolveFilename
Module._resolveFilename = getResolver(prevResolver)
return () => {
Module._resolveFilename = prevResolver
}
}
1 change: 1 addition & 0 deletions packages/vite/src/node/ssr/ssrExternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function resolveSSRExternal(

const resolveOptions: InternalResolveOptions = {
root,
preserveSymlinks: true,
isProduction: false,
isBuild: true
}
Expand Down
75 changes: 51 additions & 24 deletions packages/vite/src/node/ssr/ssrModuleLoader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs'
import path from 'path'
import { Module } from 'module'
import { ViteDevServer } from '..'
import { cleanUrl, resolveFrom, unwrapId } from '../utils'
import { unwrapId } from '../utils'
import { rebindErrorStacktrace, ssrRewriteStacktrace } from './ssrStacktrace'
import {
ssrExportAllKey,
Expand All @@ -11,6 +11,8 @@ import {
ssrDynamicImportKey
} from './ssrTransform'
import { transformRequest } from '../server/transformRequest'
import { InternalResolveOptions, tryNodeResolve } from '../plugins/resolve'
import { hookNodeResolve } from '../plugins/ssrRequireHook'

interface SSRContext {
global: NodeJS.Global
Expand Down Expand Up @@ -80,7 +82,24 @@ async function instantiateModule(
// referenced before it's been instantiated.
mod.ssrModule = ssrModule

const ssrImportMeta = { url }
const {
isProduction,
resolve: { dedupe },
root
} = server.config

const resolveOptions: InternalResolveOptions = {
conditions: ['node'],
dedupe,
// Prefer CommonJS modules.
extensions: ['.js', '.mjs', '.ts', '.jsx', '.tsx', '.json'],
isBuild: true,
isProduction,
// Disable "module" condition.
isRequire: true,
mainFields: ['main'],
root
}

urlStack = urlStack.concat(url)
const isCircular = (url: string) => urlStack.includes(url)
Expand All @@ -91,7 +110,7 @@ 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, resolveOptions)
}
dep = unwrapId(dep)
if (!isCircular(dep) && !pendingImports.get(dep)?.some(isCircular)) {
Expand Down Expand Up @@ -132,6 +151,7 @@ async function instantiateModule(
}
}

const ssrImportMeta = { url }
try {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const AsyncFunction = async function () {}.constructor as typeof Function
Expand Down Expand Up @@ -168,31 +188,38 @@ async function instantiateModule(
return Object.freeze(ssrModule)
}

function nodeRequire(id: string, importer: string | null, root: string) {
const mod = require(resolve(id, importer, root))
const defaultExport = mod.__esModule ? mod.default : mod
function nodeRequire(
id: string,
importer: string | null,
resolveOptions: InternalResolveOptions
) {
const loadModule = Module.createRequire(importer || resolveOptions.root + '/')
const unhookNodeResolve = hookNodeResolve(
(nodeResolve) => (id, parent, isMain, options) => {
if (id[0] === '.' || Module.builtinModules.includes(id)) {
return nodeResolve(id, parent, isMain, options)
}
const resolved = tryNodeResolve(id, parent.id, resolveOptions, false)
if (!resolved) {
throw Error(`Cannot find module '${id}' imported from '${parent.id}'`)
}
return resolved.id
}
)

let mod: any
try {
mod = loadModule(id)
} finally {
unhookNodeResolve()
}

// rollup-style default import interop for cjs
const defaultExport = mod.__esModule ? mod.default : mod
return new Proxy(mod, {
get(mod, prop) {
if (prop === 'default') return defaultExport
return mod[prop]
}
})
}

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

function resolve(id: string, importer: string | null, root: string) {
const key = id + importer + root
const cached = resolveCache.get(key)
if (cached) {
return cached
}
const resolveDir =
importer && fs.existsSync(cleanUrl(importer))
? path.dirname(importer)
: root
const resolved = resolveFrom(id, resolveDir, true)
resolveCache.set(key, resolved)
return resolved
}

0 comments on commit a6e3078

Please sign in to comment.