Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ssr): improved dependency resolution #5220

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions packages/vite/src/node/plugins/importAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,16 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
return [url, url]
}

// if the resolved id is not a valid browser import specifier,
// prefix it to make it valid. We will strip this before feeding it
// back into the transform pipeline
if (!url.startsWith('.') && !url.startsWith('/')) {
url =
VALID_ID_PREFIX + resolved.id.replace('\0', NULL_BYTE_PLACEHOLDER)
}

// make the URL browser-valid if not SSR
if (!ssr) {
// if the resolved id is not a valid browser import specifier,
// prefix it to make it valid. We will strip this before feeding it
// back into the transform pipeline
if (!url.startsWith('.') && !url.startsWith('/')) {
url =
VALID_ID_PREFIX + resolved.id.replace('\0', NULL_BYTE_PLACEHOLDER)
}

// mark non-js/css imports with `?import`
url = markExplicitImport(url)

Expand Down
94 changes: 42 additions & 52 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,65 +213,55 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {

// bare package imports, perform node resolve
if (bareImportRE.test(id)) {
if (
asSrc &&
server &&
!ssr &&
(res = tryOptimizedResolve(id, server, importer))
) {
return res
}
res =
(asSrc &&
server &&
!ssr &&
tryOptimizedResolve(id, server, importer)) ||
(targetWeb &&
tryResolveBrowserMapping(
id,
importer,
options,
false,
preserveSymlinks
)) ||
tryNodeResolve(id, importer, options, targetWeb, server, ssr)

if (
targetWeb &&
(res = tryResolveBrowserMapping(
id,
importer,
options,
false,
preserveSymlinks
))
) {
return res
if (res) {
if (!ssr) {
return res
}
const resId = typeof res === 'string' ? res : res.id
if (path.isAbsolute(resId) && resId.startsWith(root + '/')) {
return res
}
}

if (
(res = tryNodeResolve(id, importer, options, targetWeb, server, ssr))
) {
return res
if (ssr) {
if (isBuiltin(id) && ssrNoExternal === true) {
let message = `Cannot bundle Node.js built-in "${id}"`
if (importer) {
message += ` imported from "${path.relative(
process.cwd(),
importer
)}"`
}
message += `. Consider disabling ssr.noExternal or remove the built-in dependency.`
this.error(message)
}
return { id, external: true }
}

// node built-ins.
// externalize if building for SSR, otherwise redirect to empty module
// redirect node built-ins to empty module
if (isBuiltin(id)) {
if (ssr) {
if (ssrNoExternal === true) {
let message = `Cannot bundle Node.js built-in "${id}"`
if (importer) {
message += ` imported from "${path.relative(
process.cwd(),
importer
)}"`
}
message += `. Consider disabling ssr.noExternal or remove the built-in dependency.`
this.error(message)
}

return {
id,
external: true
}
} else {
if (!asSrc) {
debug(
`externalized node built-in "${id}" to empty module. ` +
`(imported by: ${chalk.white.dim(importer)})`
)
}
return isProduction
? browserExternalId
: `${browserExternalId}:${id}`
if (!asSrc) {
debug(
`externalized node built-in "${id}" to empty module. ` +
`(imported by: ${chalk.white.dim(importer)})`
)
}
return isProduction ? browserExternalId : `${browserExternalId}:${id}`
}
}

Expand Down
22 changes: 21 additions & 1 deletion packages/vite/src/node/ssr/ssrExternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
resolveFrom,
unique
} from '../utils'
import { ResolvedConfig } from '..'
import { ResolvedConfig, SSROptions } from '..'
import { createFilter } from '@rollup/pluginutils'

const debug = createDebugger('vite:ssr-external')
Expand Down Expand Up @@ -133,6 +133,26 @@ export function resolveSSRExternal(
return externals.filter((id) => id !== 'vite')
}

/**
* Create a function that returns true when the given bare import
* should be imported like a Node dependency normally is.
*/
export function createSSRExternalsFilter(
externals: string[],
noExternal?: SSROptions['noExternal']
) {
// Deep imports of an externalized package may be defined
// in `ssr.noExternal` so we have to check for that.
const noExternalFilter =
noExternal && noExternal !== true
? createFilter(undefined, noExternal, { resolve: false })
: null

return (dep: string) =>
(!noExternalFilter || noExternalFilter(dep)) &&
shouldExternalizeForSSR(dep, externals)
}

export function shouldExternalizeForSSR(
id: string,
externals: string[]
Expand Down
14 changes: 11 additions & 3 deletions packages/vite/src/node/ssr/ssrModuleLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ssrDynamicImportKey
} from './ssrTransform'
import { transformRequest } from '../server/transformRequest'
import { createSSRExternalsFilter } from './ssrExternal'

interface SSRContext {
global: NodeJS.Global
Expand Down Expand Up @@ -85,20 +86,24 @@ async function instantiateModule(
urlStack = urlStack.concat(url)
const isCircular = (url: string) => urlStack.includes(url)

const isExternal = createSSRExternalsFilter(
server._ssrExternals!,
server.config.ssr?.noExternal
)

// Since dynamic imports can happen in parallel, we need to
// account for multiple pending deps and duplicate imports.
const pendingDeps: string[] = []

const ssrImport = async (dep: string) => {
if (dep[0] !== '.' && dep[0] !== '/') {
if (dep[0] !== '/' && isExternal(dep)) {
return nodeRequire(
dep,
mod.file,
server.config.root,
!!server.config.resolve.preserveSymlinks
)
}
dep = unwrapId(dep)
if (!isCircular(dep) && !pendingImports.get(dep)?.some(isCircular)) {
pendingDeps.push(dep)
if (pendingDeps.length === 1) {
Expand All @@ -111,7 +116,10 @@ async function instantiateModule(
pendingDeps.splice(pendingDeps.indexOf(dep), 1)
}
}
return moduleGraph.urlToModuleMap.get(dep)?.ssrModule
// Use `getModuleByUrl` instead of accessing `urlToModuleMap` directly
// so that bare imports added to `ssr.noExternal` are normalized.
const depModule = await moduleGraph.getModuleByUrl(dep)
return depModule?.ssrModule
}

const ssrDynamicImport = (dep: string) => {
Expand Down