diff --git a/docs/config/index.md b/docs/config/index.md index 98fd6234fd480a..99a2d0343a7f97 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -628,3 +628,10 @@ SSR options may be adjusted in minor releases. - **Type:** `string[]` Prevent listed dependencies from being externalized for SSR. + +### ssr.target + +- **Type:** `'node' | 'webworker'` +- **Default:** `node` + + Build target for the SSR server. diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index cecf86ce19bb06..452ddd902ba469 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -240,3 +240,7 @@ export function mySSRPlugin() { } } ``` + +## SSR Target + +The default target for the SSR build is a node environment, but you can also run the server in a Web Worker. Packages entry resolution is different for each platform. You can configure the target to be Web Worker using the `ssr.target` set to `'webworker'`. \ No newline at end of file diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index a0f4ee36659b83..b4a5ed0c9034db 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -162,9 +162,17 @@ export interface UserConfig { dedupe?: string[] } +export type SSRTarget = 'node' | 'webworker' + export interface SSROptions { external?: string[] noExternal?: string[] + /** + * Define the target for the ssr build. The browser field in package.json + * is ignored for node but used if webworker is the target + * Default: 'node' + */ + target?: SSRTarget } export interface InlineConfig extends UserConfig { @@ -342,6 +350,7 @@ export async function resolveConfig( root: resolvedRoot, isProduction, isBuild: command === 'build', + ssrTarget: resolved.ssr?.target, asSrc: true, preferRelative: false, tryIndex: true, diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 44ccb057f2389e..2fe32c382595f2 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -39,6 +39,7 @@ export async function resolvePlugins( root: config.root, isProduction: config.isProduction, isBuild, + ssrTarget: config.ssr?.target, asSrc: true }), htmlInlineScriptProxyPlugin(), diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 7156ff05a24d0f..7f82dd5c921987 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -25,7 +25,7 @@ import { cleanUrl, slash } from '../utils' -import { ViteDevServer } from '..' +import { ViteDevServer, SSRTarget } from '..' import { createFilter } from '@rollup/pluginutils' import { PartialResolvedId } from 'rollup' import { resolve as _resolveExports } from 'resolve.exports' @@ -50,6 +50,7 @@ export interface InternalResolveOptions extends ResolveOptions { root: string isBuild: boolean isProduction: boolean + ssrTarget?: SSRTarget /** * src code mode also attempts the following: * - resolving /xxx as URLs @@ -63,7 +64,13 @@ export interface InternalResolveOptions extends ResolveOptions { } export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { - const { root, isProduction, asSrc, preferRelative = false } = baseOptions + const { + root, + isProduction, + asSrc, + ssrTarget, + preferRelative = false + } = baseOptions const requireOptions: InternalResolveOptions = { ...baseOptions, isRequire: true @@ -87,6 +94,8 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { return } + const targetWeb = !ssr || ssrTarget === 'webworker' + // this is passed by @rollup/plugin-commonjs const isRequire = resolveOpts && @@ -125,7 +134,7 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { // handle browser field mapping for relative imports if ( - !ssr && + targetWeb && (res = tryResolveBrowserMapping(fsPath, importer, options, true)) ) { return res @@ -177,13 +186,13 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { } if ( - !ssr && + targetWeb && (res = tryResolveBrowserMapping(id, importer, options, false)) ) { return res } - if ((res = tryNodeResolve(id, importer, options, ssr, server))) { + if ((res = tryNodeResolve(id, importer, options, targetWeb, server))) { return res } @@ -232,7 +241,7 @@ function tryFsResolve( fsPath: string, options: InternalResolveOptions, tryIndex = true, - ssr?: boolean + targetWeb = true ): string | undefined { let file = fsPath let postfix = '' @@ -253,7 +262,7 @@ function tryFsResolve( postfix, options, false, - ssr, + targetWeb, options.tryPrefix )) ) { @@ -267,7 +276,7 @@ function tryFsResolve( postfix, options, false, - ssr, + targetWeb, options.tryPrefix )) ) { @@ -281,7 +290,7 @@ function tryFsResolve( postfix, options, tryIndex, - ssr, + targetWeb, options.tryPrefix )) ) { @@ -294,7 +303,7 @@ function tryResolveFile( postfix: string, options: InternalResolveOptions, tryIndex: boolean, - ssr?: boolean, + targetWeb: boolean, tryPrefix?: string ): string | undefined { let isReadable = false @@ -313,7 +322,7 @@ function tryResolveFile( if (fs.existsSync(pkgPath)) { // path points to a node package const pkg = loadPackageData(pkgPath) - return resolvePackageEntry(file, pkg, options, ssr) + return resolvePackageEntry(file, pkg, options, targetWeb) } const index = tryFsResolve(file + '/index', options) if (index) return index + postfix @@ -321,7 +330,7 @@ function tryResolveFile( } if (tryPrefix) { const prefixed = `${path.dirname(file)}/${tryPrefix}${path.basename(file)}` - return tryResolveFile(prefixed, postfix, options, tryIndex, ssr) + return tryResolveFile(prefixed, postfix, options, tryIndex, targetWeb) } } @@ -331,7 +340,7 @@ export function tryNodeResolve( id: string, importer: string | undefined, options: InternalResolveOptions, - ssr?: boolean, + targetWeb: boolean, server?: ViteDevServer ): PartialResolvedId | undefined { const { root, dedupe, isBuild } = options @@ -358,8 +367,8 @@ export function tryNodeResolve( } let resolved = deepMatch - ? resolveDeepImport(id, pkg, options, ssr) - : resolvePackageEntry(id, pkg, options, ssr) + ? resolveDeepImport(id, pkg, options, targetWeb) + : resolvePackageEntry(id, pkg, options, targetWeb) if (!resolved) { return } @@ -431,10 +440,10 @@ export function tryOptimizedResolve( export interface PackageData { dir: string hasSideEffects: (id: string) => boolean - resolvedImports: Record - ssrResolvedImports: Record - setResolvedCache: (key: string, entry: string, ssr?: boolean) => void - getResolvedCache: (key: string, ssr?: boolean) => string | undefined + webResolvedImports: Record + nodeResolvedImports: Record + setResolvedCache: (key: string, entry: string, targetWeb: boolean) => void + getResolvedCache: (key: string, targetWeb: boolean) => string | undefined data: { [field: string]: any version: string @@ -481,20 +490,20 @@ function loadPackageData(pkgPath: string, cacheKey = pkgPath) { dir: pkgDir, data, hasSideEffects, - resolvedImports: {}, - ssrResolvedImports: {}, - setResolvedCache(key: string, entry: string, ssr?: boolean) { - if (ssr) { - pkg.ssrResolvedImports[key] = entry + webResolvedImports: {}, + nodeResolvedImports: {}, + setResolvedCache(key: string, entry: string, targetWeb: boolean) { + if (targetWeb) { + pkg.webResolvedImports[key] = entry } else { - pkg.resolvedImports[key] = entry + pkg.nodeResolvedImports[key] = entry } }, - getResolvedCache(key: string, ssr?: boolean) { - if (ssr) { - return pkg.ssrResolvedImports[key] + getResolvedCache(key: string, targetWeb: boolean) { + if (targetWeb) { + return pkg.webResolvedImports[key] } else { - return pkg.resolvedImports[key] + return pkg.nodeResolvedImports[key] } } } @@ -506,9 +515,9 @@ export function resolvePackageEntry( id: string, { dir, data, setResolvedCache, getResolvedCache }: PackageData, options: InternalResolveOptions, - ssr?: boolean + targetWeb: boolean ): string | undefined { - const cached = getResolvedCache('.', ssr) + const cached = getResolvedCache('.', targetWeb) if (cached) { return cached } @@ -524,7 +533,7 @@ export function resolvePackageEntry( // This is because .mjs files can technically import .cjs files which would // make them invalid for pure ESM environments - so if other module/browser // fields are present, prioritize those instead. - if (!ssr && (!entryPoint || entryPoint.endsWith('.mjs'))) { + if (targetWeb && (!entryPoint || entryPoint.endsWith('.mjs'))) { // check browser field // https://github.com/defunctzombie/package-browser-field-spec const browserEntry = @@ -574,7 +583,7 @@ export function resolvePackageEntry( // resolve object browser field in package.json const { browser: browserField } = data - if (!ssr && isObject(browserField)) { + if (targetWeb && isObject(browserField)) { entryPoint = mapWithBrowserField(entryPoint, browserField) || entryPoint } @@ -586,7 +595,7 @@ export function resolvePackageEntry( debug( `[package entry] ${chalk.cyan(id)} -> ${chalk.dim(resolvedEntryPoint)}` ) - setResolvedCache('.', resolvedEntryPoint, ssr) + setResolvedCache('.', resolvedEntryPoint, targetWeb) return resolvedEntryPoint } else { throw new Error( @@ -618,17 +627,17 @@ function resolveExports( function resolveDeepImport( id: string, { - resolvedImports, + webResolvedImports, setResolvedCache, getResolvedCache, dir, data }: PackageData, options: InternalResolveOptions, - ssr?: boolean + targetWeb: boolean ): string | undefined { id = '.' + id.slice(data.name.length) - const cache = getResolvedCache(id, ssr) + const cache = getResolvedCache(id, targetWeb) if (cache) { return cache } @@ -650,12 +659,12 @@ function resolveDeepImport( `${path.join(dir, 'package.json')}.` ) } - } else if (!ssr && isObject(browserField)) { + } else if (targetWeb && isObject(browserField)) { const mapped = mapWithBrowserField(relativeId, browserField) if (mapped) { relativeId = mapped } else if (mapped === false) { - return (resolvedImports[id] = browserExternalId) + return (webResolvedImports[id] = browserExternalId) } } @@ -664,12 +673,12 @@ function resolveDeepImport( path.join(dir, relativeId), options, !exportsField, // try index only if no exports field - ssr + targetWeb ) if (resolved) { isDebug && debug(`[node/deep-import] ${chalk.cyan(id)} -> ${chalk.dim(resolved)}`) - setResolvedCache(id, resolved, ssr) + setResolvedCache(id, resolved, targetWeb) return resolved } }