From 96a4c559a8792d68ad46bb0b31692124596cf331 Mon Sep 17 00:00:00 2001 From: Marc MacLeod <847542+marbemac@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:33:18 -0500 Subject: [PATCH 1/8] fix(ssr): respect conditions when target webworker Signed-off-by: Marc MacLeod <847542+marbemac@users.noreply.github.com> --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 11 ++++++++-- .../__tests__/ssr-webworker.spec.ts | 12 +++++++++++ .../ssr-webworker/browser-exports/browser.js | 1 + .../ssr-webworker/browser-exports/node.js | 1 + .../browser-exports/package.json | 12 +++++++++++ playground/ssr-webworker/package.json | 4 +++- playground/ssr-webworker/src/entry-worker.jsx | 4 ++++ playground/ssr-webworker/vite.config.js | 1 + .../ssr-webworker/worker-exports/browser.js | 2 ++ .../ssr-webworker/worker-exports/node.js | 1 + .../ssr-webworker/worker-exports/package.json | 13 ++++++++++++ .../ssr-webworker/worker-exports/worker.js | 1 + pnpm-lock.yaml | 20 +++++++++++++++++++ 13 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 playground/ssr-webworker/browser-exports/browser.js create mode 100644 playground/ssr-webworker/browser-exports/node.js create mode 100644 playground/ssr-webworker/browser-exports/package.json create mode 100644 playground/ssr-webworker/worker-exports/browser.js create mode 100644 playground/ssr-webworker/worker-exports/node.js create mode 100644 playground/ssr-webworker/worker-exports/package.json create mode 100644 playground/ssr-webworker/worker-exports/worker.js diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index 2181b8b6ab9127..4e834851e80b64 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -123,19 +123,24 @@ async function instantiateModule( isProduction, resolve: { dedupe, preserveSymlinks }, root, + ssr, } = server.config + const webTarget = ssr.target === 'webworker' + const overrideConditions = webTarget ? server.config.resolve.conditions : [] + const resolveOptions: InternalResolveOptionsWithOverrideConditions = { mainFields: ['main'], browserField: true, conditions: [], - overrideConditions: ['production', 'development'], + overrideConditions: [...overrideConditions, 'production', 'development'], extensions: ['.js', '.cjs', '.json'], dedupe, preserveSymlinks, isBuild: false, isProduction, root, + ssrConfig: ssr, } // Since dynamic imports can happen in parallel, we need to @@ -271,6 +276,8 @@ async function nodeImport( if (id.startsWith('data:') || isBuiltin(id)) { url = id } else { + const targetWeb = resolveOptions.ssrConfig?.target === 'webworker' + const resolved = tryNodeResolve( id, importer, @@ -280,7 +287,7 @@ async function nodeImport( typeof jest === 'undefined' ? { ...resolveOptions, tryEsmOnly: true } : resolveOptions, - false, + targetWeb, ) if (!resolved) { const err: any = new Error( diff --git a/playground/ssr-webworker/__tests__/ssr-webworker.spec.ts b/playground/ssr-webworker/__tests__/ssr-webworker.spec.ts index e215abe862825b..bd0bc25f3074f8 100644 --- a/playground/ssr-webworker/__tests__/ssr-webworker.spec.ts +++ b/playground/ssr-webworker/__tests__/ssr-webworker.spec.ts @@ -11,6 +11,18 @@ test('/', async () => { expect(await page.textContent('.external')).toMatch('object') }) +test('supports resolve.conditions', async () => { + await page.goto(url) + expect(await page.textContent('.worker-exports')).toMatch('[success] worker') +}) + +test('respects browser export', async () => { + await page.goto(url) + expect(await page.textContent('.browser-exports')).toMatch( + '[success] browser', + ) +}) + test.runIf(isBuild)('inlineDynamicImports', () => { const dynamicJsContent = findAssetFile(/dynamic-\w+\.js/, 'worker') expect(dynamicJsContent).toBe('') diff --git a/playground/ssr-webworker/browser-exports/browser.js b/playground/ssr-webworker/browser-exports/browser.js new file mode 100644 index 00000000000000..a2f54551f2dc4a --- /dev/null +++ b/playground/ssr-webworker/browser-exports/browser.js @@ -0,0 +1 @@ +export default '[success] browser' diff --git a/playground/ssr-webworker/browser-exports/node.js b/playground/ssr-webworker/browser-exports/node.js new file mode 100644 index 00000000000000..b55d59fc6c4b95 --- /dev/null +++ b/playground/ssr-webworker/browser-exports/node.js @@ -0,0 +1 @@ +export default '[fail] should not load me' diff --git a/playground/ssr-webworker/browser-exports/package.json b/playground/ssr-webworker/browser-exports/package.json new file mode 100644 index 00000000000000..daabadafafc095 --- /dev/null +++ b/playground/ssr-webworker/browser-exports/package.json @@ -0,0 +1,12 @@ +{ + "name": "@vitejs/test-browser-exports", + "private": true, + "version": "0.0.0", + "exports": { + ".": { + "browser": "./browser.js", + "node": "./node.js", + "default": "./node.js" + } + } +} diff --git a/playground/ssr-webworker/package.json b/playground/ssr-webworker/package.json index cff63d3117f8b3..5d4b0d75685d60 100644 --- a/playground/ssr-webworker/package.json +++ b/playground/ssr-webworker/package.json @@ -8,7 +8,9 @@ "build:worker": "vite build --ssr src/entry-worker.jsx --outDir dist/worker" }, "dependencies": { - "react": "^18.2.0" + "react": "^18.2.0", + "@vitejs/test-browser-exports": "file:./browser-exports", + "@vitejs/test-worker-exports": "file:./worker-exports" }, "devDependencies": { "miniflare": "^3.20230918.0", diff --git a/playground/ssr-webworker/src/entry-worker.jsx b/playground/ssr-webworker/src/entry-worker.jsx index 9210d7e708e0d7..2b58c6f17a6e0c 100644 --- a/playground/ssr-webworker/src/entry-worker.jsx +++ b/playground/ssr-webworker/src/entry-worker.jsx @@ -1,4 +1,6 @@ import { msg as linkedMsg } from '@vitejs/test-resolve-linked' +import browserExportsMessage from '@vitejs/test-browser-exports' +import workerExportsMessage from '@vitejs/test-worker-exports' import React from 'react' let loaded = false @@ -14,6 +16,8 @@ addEventListener('fetch', function (event) {

${linkedMsg}

${typeof React}

dynamic: ${loaded}

+

${browserExportsMessage}

+

${workerExportsMessage}

`, { headers: { diff --git a/playground/ssr-webworker/vite.config.js b/playground/ssr-webworker/vite.config.js index 034cc512606df4..b0b922d7d1b97e 100644 --- a/playground/ssr-webworker/vite.config.js +++ b/playground/ssr-webworker/vite.config.js @@ -6,6 +6,7 @@ export default defineConfig({ }, resolve: { dedupe: ['react'], + conditions: ['worker'], }, ssr: { target: 'webworker', diff --git a/playground/ssr-webworker/worker-exports/browser.js b/playground/ssr-webworker/worker-exports/browser.js new file mode 100644 index 00000000000000..819b0ae6e9556f --- /dev/null +++ b/playground/ssr-webworker/worker-exports/browser.js @@ -0,0 +1,2 @@ +// conditions are set to worker, and worker is higher up in the exports object in package.json, so should be preferred +export default '[fail] should not load me' diff --git a/playground/ssr-webworker/worker-exports/node.js b/playground/ssr-webworker/worker-exports/node.js new file mode 100644 index 00000000000000..b55d59fc6c4b95 --- /dev/null +++ b/playground/ssr-webworker/worker-exports/node.js @@ -0,0 +1 @@ +export default '[fail] should not load me' diff --git a/playground/ssr-webworker/worker-exports/package.json b/playground/ssr-webworker/worker-exports/package.json new file mode 100644 index 00000000000000..c8e1a93163d5c6 --- /dev/null +++ b/playground/ssr-webworker/worker-exports/package.json @@ -0,0 +1,13 @@ +{ + "name": "@vitejs/test-worker-exports", + "private": true, + "version": "0.0.0", + "exports": { + ".": { + "worker": "./worker.js", + "browser": "./browser.js", + "node": "./node.js", + "default": "./node.js" + } + } +} diff --git a/playground/ssr-webworker/worker-exports/worker.js b/playground/ssr-webworker/worker-exports/worker.js new file mode 100644 index 00000000000000..8cd0fa65046d76 --- /dev/null +++ b/playground/ssr-webworker/worker-exports/worker.js @@ -0,0 +1 @@ +export default '[success] worker' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20255fcd8773d1..baf72c79ea67c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1372,6 +1372,12 @@ importers: playground/ssr-webworker: dependencies: + '@vitejs/test-browser-exports': + specifier: file:./browser-exports + version: file:playground/ssr-webworker/browser-exports + '@vitejs/test-worker-exports': + specifier: file:./worker-exports + version: file:playground/ssr-webworker/worker-exports react: specifier: ^18.2.0 version: 18.2.0 @@ -1383,6 +1389,10 @@ importers: specifier: ^3.20230918.0 version: 3.20230918.0 + playground/ssr-webworker/browser-exports: {} + + playground/ssr-webworker/worker-exports: {} + playground/tailwind: dependencies: autoprefixer: @@ -10502,6 +10512,16 @@ packages: name: '@vitejs/test-resolve-pkg-exports' dev: false + file:playground/ssr-webworker/browser-exports: + resolution: {directory: playground/ssr-webworker/browser-exports, type: directory} + name: '@vitejs/test-browser-exports' + dev: false + + file:playground/ssr-webworker/worker-exports: + resolution: {directory: playground/ssr-webworker/worker-exports, type: directory} + name: '@vitejs/test-worker-exports' + dev: false + file:playground/worker/dep-to-optimize: resolution: {directory: playground/worker/dep-to-optimize, type: directory} name: '@vitejs/test-dep-to-optimize' From 20e890efae5c360e45aaff754612fa793fd2f885 Mon Sep 17 00:00:00 2001 From: Marc MacLeod <847542+marbemac@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:50:52 -0500 Subject: [PATCH 2/8] feat: add ssr conditions and externalConditions Signed-off-by: Marc MacLeod <847542+marbemac@users.noreply.github.com> --- packages/vite/src/node/plugins/resolve.ts | 7 +++++++ packages/vite/src/node/ssr/index.ts | 20 +++++++++++++++++++ packages/vite/src/node/ssr/ssrExternal.ts | 4 ++++ packages/vite/src/node/ssr/ssrModuleLoader.ts | 5 +++-- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 66ccca0aa8551c..525e898053a898 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -173,10 +173,17 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { const isRequire: boolean = resolveOpts?.custom?.['node-resolve']?.isRequire ?? false + // end user can configure different conditions for ssr and client. + // falls back to client conditions if no ssr conditions supplied + const ssrConditions = + resolveOptions.ssrConfig?.resolve?.conditions || + resolveOptions.conditions + const options: InternalResolveOptions = { isRequire, ...resolveOptions, scan: resolveOpts?.scan ?? resolveOptions.scan, + conditions: ssr ? ssrConditions : resolveOptions.conditions, } const resolvedImports = resolveSubpathImports( diff --git a/packages/vite/src/node/ssr/index.ts b/packages/vite/src/node/ssr/index.ts index 60d5fd1ee8be86..59eff0735d4a94 100644 --- a/packages/vite/src/node/ssr/index.ts +++ b/packages/vite/src/node/ssr/index.ts @@ -7,12 +7,14 @@ export type SsrDepOptimizationOptions = DepOptimizationConfig export interface SSROptions { noExternal?: string | RegExp | (string | RegExp)[] | true external?: 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 + /** * Control over which dependencies are optimized during SSR and esbuild options * During build: @@ -22,6 +24,24 @@ export interface SSROptions { * @experimental */ optimizeDeps?: SsrDepOptimizationOptions + + resolve?: { + /** + * Conditions that are used in the plugin pipeline. The default value is the root config's `resolve.conditions`. + * + * Use this to override the default ssr conditions for the ssr build. + * + * @default rootConfig.resolve.conditions + */ + conditions?: string[] + + /** + * Conditions that are used during ssr import (including `ssrLoadModule`) of externalized dependencies. + * + * @default [] + */ + externalConditions?: string[] + } } export interface ResolvedSSROptions extends SSROptions { diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 9ee38434c18a16..76b49dffbd24d6 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -40,11 +40,15 @@ export function createIsConfiguredAsSsrExternal( typeof noExternal !== 'boolean' && createFilter(undefined, noExternal, { resolve: false }) + const targetConditions = + config.ssr.resolve?.externalConditions || config.resolve.conditions + const resolveOptions: InternalResolveOptions = { ...config.resolve, root, isProduction: false, isBuild: true, + conditions: targetConditions, } const isExternalizable = ( diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index 4e834851e80b64..b59f956a07a061 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -126,8 +126,7 @@ async function instantiateModule( ssr, } = server.config - const webTarget = ssr.target === 'webworker' - const overrideConditions = webTarget ? server.config.resolve.conditions : [] + const overrideConditions = ssr.resolve?.externalConditions || [] const resolveOptions: InternalResolveOptionsWithOverrideConditions = { mainFields: ['main'], @@ -288,6 +287,8 @@ async function nodeImport( ? { ...resolveOptions, tryEsmOnly: true } : resolveOptions, targetWeb, + undefined, + true, ) if (!resolved) { const err: any = new Error( From 399d824754653fd2aaa7c2ee26e4d9802d51c985 Mon Sep 17 00:00:00 2001 From: Marc MacLeod <847542+marbemac@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:54:12 -0500 Subject: [PATCH 3/8] add some tests Signed-off-by: Marc MacLeod <847542+marbemac@users.noreply.github.com> --- playground/ssr-conditions/__tests__/serve.ts | 35 ++++++++++ .../__tests__/ssr-conditions.spec.ts | 27 +++++++ playground/ssr-conditions/external/browser.js | 1 + playground/ssr-conditions/external/default.js | 1 + playground/ssr-conditions/external/edge.js | 1 + playground/ssr-conditions/external/node.js | 1 + .../ssr-conditions/external/node.unbundled.js | 1 + .../ssr-conditions/external/package.json | 21 ++++++ playground/ssr-conditions/index.html | 29 ++++++++ .../ssr-conditions/no-external/browser.js | 1 + .../ssr-conditions/no-external/default.js | 1 + playground/ssr-conditions/no-external/edge.js | 1 + playground/ssr-conditions/no-external/node.js | 1 + .../no-external/node.unbundled.js | 1 + .../ssr-conditions/no-external/package.json | 21 ++++++ playground/ssr-conditions/package.json | 18 +++++ playground/ssr-conditions/server.js | 70 +++++++++++++++++++ playground/ssr-conditions/src/app.js | 16 +++++ playground/ssr-conditions/src/direct-load.js | 4 ++ playground/ssr-conditions/vite.config.js | 12 ++++ playground/test-utils.ts | 2 + pnpm-lock.yaml | 27 +++++++ 22 files changed, 292 insertions(+) create mode 100644 playground/ssr-conditions/__tests__/serve.ts create mode 100644 playground/ssr-conditions/__tests__/ssr-conditions.spec.ts create mode 100644 playground/ssr-conditions/external/browser.js create mode 100644 playground/ssr-conditions/external/default.js create mode 100644 playground/ssr-conditions/external/edge.js create mode 100644 playground/ssr-conditions/external/node.js create mode 100644 playground/ssr-conditions/external/node.unbundled.js create mode 100644 playground/ssr-conditions/external/package.json create mode 100644 playground/ssr-conditions/index.html create mode 100644 playground/ssr-conditions/no-external/browser.js create mode 100644 playground/ssr-conditions/no-external/default.js create mode 100644 playground/ssr-conditions/no-external/edge.js create mode 100644 playground/ssr-conditions/no-external/node.js create mode 100644 playground/ssr-conditions/no-external/node.unbundled.js create mode 100644 playground/ssr-conditions/no-external/package.json create mode 100644 playground/ssr-conditions/package.json create mode 100644 playground/ssr-conditions/server.js create mode 100644 playground/ssr-conditions/src/app.js create mode 100644 playground/ssr-conditions/src/direct-load.js create mode 100644 playground/ssr-conditions/vite.config.js diff --git a/playground/ssr-conditions/__tests__/serve.ts b/playground/ssr-conditions/__tests__/serve.ts new file mode 100644 index 00000000000000..5e0546db2bd1a5 --- /dev/null +++ b/playground/ssr-conditions/__tests__/serve.ts @@ -0,0 +1,35 @@ +// this is automatically detected by playground/vitestSetup.ts and will replace +// the default e2e test serve behavior + +import path from 'node:path' +import kill from 'kill-port' +import { hmrPorts, ports, rootDir } from '~utils' + +export const port = ports['ssr-conditions'] + +export async function serve(): Promise<{ close(): Promise }> { + await kill(port) + + const { createServer } = await import(path.resolve(rootDir, 'server.js')) + const { app, vite } = await createServer(rootDir, hmrPorts['ssr-conditions']) + + return new Promise((resolve, reject) => { + try { + const server = app.listen(port, () => { + resolve({ + // for test teardown + async close() { + await new Promise((resolve) => { + server.close(resolve) + }) + if (vite) { + await vite.close() + } + }, + }) + }) + } catch (e) { + reject(e) + } + }) +} diff --git a/playground/ssr-conditions/__tests__/ssr-conditions.spec.ts b/playground/ssr-conditions/__tests__/ssr-conditions.spec.ts new file mode 100644 index 00000000000000..76ec36363f1309 --- /dev/null +++ b/playground/ssr-conditions/__tests__/ssr-conditions.spec.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest' +import { port } from './serve' +import { page } from '~utils' + +const url = `http://localhost:${port}` + +test('ssr.resolve.conditions affect non-externalized imports during ssr', async () => { + await page.goto(url) + expect(await page.textContent('.no-external-react-server')).toMatch( + 'node.unbundled.js', + ) +}) + +test('ssr.resolve.externalConditions affect externalized imports during ssr', async () => { + await page.goto(url) + expect(await page.textContent('.external-react-server')).toMatch('edge.js') +}) + +test('ssr.resolve settings do not affect non-ssr imports', async () => { + await page.goto(url) + expect(await page.textContent('.browser-no-external-react-server')).toMatch( + 'default.js', + ) + expect(await page.textContent('.browser-external-react-server')).toMatch( + 'default.js', + ) +}) diff --git a/playground/ssr-conditions/external/browser.js b/playground/ssr-conditions/external/browser.js new file mode 100644 index 00000000000000..a02fa8e63243a7 --- /dev/null +++ b/playground/ssr-conditions/external/browser.js @@ -0,0 +1 @@ +export default 'browser.js' diff --git a/playground/ssr-conditions/external/default.js b/playground/ssr-conditions/external/default.js new file mode 100644 index 00000000000000..6d647f59df1b54 --- /dev/null +++ b/playground/ssr-conditions/external/default.js @@ -0,0 +1 @@ +export default 'default.js' diff --git a/playground/ssr-conditions/external/edge.js b/playground/ssr-conditions/external/edge.js new file mode 100644 index 00000000000000..358f21d00905b2 --- /dev/null +++ b/playground/ssr-conditions/external/edge.js @@ -0,0 +1 @@ +export default 'edge.js' diff --git a/playground/ssr-conditions/external/node.js b/playground/ssr-conditions/external/node.js new file mode 100644 index 00000000000000..758a33e2709d32 --- /dev/null +++ b/playground/ssr-conditions/external/node.js @@ -0,0 +1 @@ +export default 'node.js' diff --git a/playground/ssr-conditions/external/node.unbundled.js b/playground/ssr-conditions/external/node.unbundled.js new file mode 100644 index 00000000000000..5ea769de6bd97f --- /dev/null +++ b/playground/ssr-conditions/external/node.unbundled.js @@ -0,0 +1 @@ +export default 'node.unbundled.js' diff --git a/playground/ssr-conditions/external/package.json b/playground/ssr-conditions/external/package.json new file mode 100644 index 00000000000000..55b77e3670c375 --- /dev/null +++ b/playground/ssr-conditions/external/package.json @@ -0,0 +1,21 @@ +{ + "name": "@vitejs/test-ssr-conditions-external", + "private": true, + "version": "0.0.0", + "type": "module", + "exports": { + "./server": { + "react-server": { + "workerd": "./edge.js", + "deno": "./browser.js", + "node": { + "webpack": "./node.js", + "default": "./node.unbundled.js" + }, + "edge-light": "./edge.js", + "browser": "./browser.js" + }, + "default": "./default.js" + } + } +} diff --git a/playground/ssr-conditions/index.html b/playground/ssr-conditions/index.html new file mode 100644 index 00000000000000..d2fec18916c81f --- /dev/null +++ b/playground/ssr-conditions/index.html @@ -0,0 +1,29 @@ + + + + + + SSR Resolve Conditions + + +

SSR Resolve Conditions

+
+ + + + diff --git a/playground/ssr-conditions/no-external/browser.js b/playground/ssr-conditions/no-external/browser.js new file mode 100644 index 00000000000000..a02fa8e63243a7 --- /dev/null +++ b/playground/ssr-conditions/no-external/browser.js @@ -0,0 +1 @@ +export default 'browser.js' diff --git a/playground/ssr-conditions/no-external/default.js b/playground/ssr-conditions/no-external/default.js new file mode 100644 index 00000000000000..6d647f59df1b54 --- /dev/null +++ b/playground/ssr-conditions/no-external/default.js @@ -0,0 +1 @@ +export default 'default.js' diff --git a/playground/ssr-conditions/no-external/edge.js b/playground/ssr-conditions/no-external/edge.js new file mode 100644 index 00000000000000..358f21d00905b2 --- /dev/null +++ b/playground/ssr-conditions/no-external/edge.js @@ -0,0 +1 @@ +export default 'edge.js' diff --git a/playground/ssr-conditions/no-external/node.js b/playground/ssr-conditions/no-external/node.js new file mode 100644 index 00000000000000..758a33e2709d32 --- /dev/null +++ b/playground/ssr-conditions/no-external/node.js @@ -0,0 +1 @@ +export default 'node.js' diff --git a/playground/ssr-conditions/no-external/node.unbundled.js b/playground/ssr-conditions/no-external/node.unbundled.js new file mode 100644 index 00000000000000..5ea769de6bd97f --- /dev/null +++ b/playground/ssr-conditions/no-external/node.unbundled.js @@ -0,0 +1 @@ +export default 'node.unbundled.js' diff --git a/playground/ssr-conditions/no-external/package.json b/playground/ssr-conditions/no-external/package.json new file mode 100644 index 00000000000000..45b1d5f706a286 --- /dev/null +++ b/playground/ssr-conditions/no-external/package.json @@ -0,0 +1,21 @@ +{ + "name": "@vitejs/test-ssr-conditions-no-external", + "private": true, + "version": "0.0.0", + "type": "module", + "exports": { + "./server": { + "react-server": { + "workerd": "./edge.js", + "deno": "./browser.js", + "node": { + "webpack": "./node.js", + "default": "./node.unbundled.js" + }, + "edge-light": "./edge.js", + "browser": "./browser.js" + }, + "default": "./default.js" + } + } +} diff --git a/playground/ssr-conditions/package.json b/playground/ssr-conditions/package.json new file mode 100644 index 00000000000000..f30e73cb1731de --- /dev/null +++ b/playground/ssr-conditions/package.json @@ -0,0 +1,18 @@ +{ + "name": "@vitejs/test-ssr-conditions", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "node server", + "serve": "NODE_ENV=production node server", + "debug": "node --inspect-brk server" + }, + "dependencies": { + "@vitejs/test-ssr-conditions-external": "file:./external", + "@vitejs/test-ssr-conditions-no-external": "file:./no-external" + }, + "devDependencies": { + "express": "^4.18.2" + } +} diff --git a/playground/ssr-conditions/server.js b/playground/ssr-conditions/server.js new file mode 100644 index 00000000000000..aa26dfb0cb8607 --- /dev/null +++ b/playground/ssr-conditions/server.js @@ -0,0 +1,70 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import express from 'express' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +const isTest = process.env.VITEST + +export async function createServer(root = process.cwd(), hmrPort) { + const resolve = (p) => path.resolve(__dirname, p) + + const app = express() + + /** + * @type {import('vite').ViteDevServer} + */ + const vite = await ( + await import('vite') + ).createServer({ + root, + logLevel: isTest ? 'error' : 'info', + server: { + middlewareMode: true, + watch: { + // During tests we edit the files too fast and sometimes chokidar + // misses change events, so enforce polling for consistency + usePolling: true, + interval: 100, + }, + hmr: { + port: hmrPort, + }, + }, + appType: 'custom', + }) + + app.use(vite.middlewares) + + app.use('*', async (req, res) => { + try { + const url = req.originalUrl + + let template + template = fs.readFileSync(resolve('index.html'), 'utf-8') + template = await vite.transformIndexHtml(url, template) + const render = (await vite.ssrLoadModule('/src/app.js')).render + + const appHtml = await render(url, __dirname) + + const html = template.replace(``, appHtml) + + res.status(200).set({ 'Content-Type': 'text/html' }).end(html) + } catch (e) { + vite && vite.ssrFixStacktrace(e) + console.log(e.stack) + res.status(500).end(e.stack) + } + }) + + return { app, vite } +} + +if (!isTest) { + createServer().then(({ app }) => + app.listen(5173, () => { + console.log('http://localhost:5173') + }), + ) +} diff --git a/playground/ssr-conditions/src/app.js b/playground/ssr-conditions/src/app.js new file mode 100644 index 00000000000000..43f109c37ea7a9 --- /dev/null +++ b/playground/ssr-conditions/src/app.js @@ -0,0 +1,16 @@ +import noExternalReactServerMessage from '@vitejs/test-ssr-conditions-no-external/server' +import externalReactServerMessage from '@vitejs/test-ssr-conditions-external/server' + +export async function render(url) { + let html = '' + + html += `\n

${noExternalReactServerMessage}

` + + html += `\n

` + + html += `\n

${externalReactServerMessage}

` + + html += `\n

` + + return html + '\n' +} diff --git a/playground/ssr-conditions/src/direct-load.js b/playground/ssr-conditions/src/direct-load.js new file mode 100644 index 00000000000000..d78625fd26d946 --- /dev/null +++ b/playground/ssr-conditions/src/direct-load.js @@ -0,0 +1,4 @@ +import noExternalReactServerMessage from '@vitejs/test-ssr-conditions-no-external/server' +import externalReactServerMessage from '@vitejs/test-ssr-conditions-external/server' + +export { noExternalReactServerMessage, externalReactServerMessage } diff --git a/playground/ssr-conditions/vite.config.js b/playground/ssr-conditions/vite.config.js new file mode 100644 index 00000000000000..41727adaf5f811 --- /dev/null +++ b/playground/ssr-conditions/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + ssr: { + external: ['@vitejs/test-ssr-conditions-external'], + noExternal: ['@vitejs/test-ssr-conditions-no-external'], + resolve: { + conditions: ['react-server'], + externalConditions: ['workerd', 'react-server'], + }, + }, +}) diff --git a/playground/test-utils.ts b/playground/test-utils.ts index 5bb62d402c1563..af5c3f65e42bd6 100644 --- a/playground/test-utils.ts +++ b/playground/test-utils.ts @@ -36,6 +36,7 @@ export const ports = { 'ssr-webworker': 9605, 'proxy-hmr': 9606, // not imported but used in `proxy-hmr/vite.config.js` 'proxy-hmr/other-app': 9607, // not imported but used in `proxy-hmr/other-app/vite.config.js` + 'ssr-conditions': 9608, 'css/postcss-caching': 5005, 'css/postcss-plugins-different-dir': 5006, 'css/dynamic-import': 5007, @@ -50,6 +51,7 @@ export const hmrPorts = { 'ssr-pug': 24685, 'css/lightningcss-proxy': 24686, json: 24687, + 'ssr-conditions': 24688, } const hexToNameMap: Record = {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index baf72c79ea67c8..5a97c2d71c3325 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1171,6 +1171,23 @@ importers: specifier: ^4.18.2 version: 4.18.2 + playground/ssr-conditions: + dependencies: + '@vitejs/test-ssr-conditions-external': + specifier: file:./external + version: file:playground/ssr-conditions/external + '@vitejs/test-ssr-conditions-no-external': + specifier: file:./no-external + version: file:playground/ssr-conditions/no-external + devDependencies: + express: + specifier: ^4.18.2 + version: 4.18.2 + + playground/ssr-conditions/external: {} + + playground/ssr-conditions/no-external: {} + playground/ssr-deps: dependencies: '@vitejs/test-css-lib': @@ -10378,6 +10395,16 @@ packages: dep-a: file:playground/preload/dep-a dev: true + file:playground/ssr-conditions/external: + resolution: {directory: playground/ssr-conditions/external, type: directory} + name: '@vitejs/test-ssr-conditions-external' + dev: false + + file:playground/ssr-conditions/no-external: + resolution: {directory: playground/ssr-conditions/no-external, type: directory} + name: '@vitejs/test-ssr-conditions-no-external' + dev: false + file:playground/ssr-deps/css-lib: resolution: {directory: playground/ssr-deps/css-lib, type: directory} name: '@vitejs/test-css-lib' From 7b225ecc9a3ed37e926df8b197b373e022e0686f Mon Sep 17 00:00:00 2001 From: Marc MacLeod <847542+marbemac@users.noreply.github.com> Date: Sat, 30 Sep 2023 12:11:40 -0500 Subject: [PATCH 4/8] pr feedback Signed-off-by: Marc MacLeod <847542+marbemac@users.noreply.github.com> --- packages/vite/src/node/ssr/ssrExternal.ts | 3 +-- playground/ssr-conditions/src/direct-load.js | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 playground/ssr-conditions/src/direct-load.js diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 76b49dffbd24d6..3035fa972b3d19 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -40,8 +40,7 @@ export function createIsConfiguredAsSsrExternal( typeof noExternal !== 'boolean' && createFilter(undefined, noExternal, { resolve: false }) - const targetConditions = - config.ssr.resolve?.externalConditions || config.resolve.conditions + const targetConditions = config.ssr.resolve?.externalConditions || [] const resolveOptions: InternalResolveOptions = { ...config.resolve, diff --git a/playground/ssr-conditions/src/direct-load.js b/playground/ssr-conditions/src/direct-load.js deleted file mode 100644 index d78625fd26d946..00000000000000 --- a/playground/ssr-conditions/src/direct-load.js +++ /dev/null @@ -1,4 +0,0 @@ -import noExternalReactServerMessage from '@vitejs/test-ssr-conditions-no-external/server' -import externalReactServerMessage from '@vitejs/test-ssr-conditions-external/server' - -export { noExternalReactServerMessage, externalReactServerMessage } From 3fc8cc1e29f13eca811aae47504d2da951e26e01 Mon Sep 17 00:00:00 2001 From: Marc MacLeod <847542+marbemac@users.noreply.github.com> Date: Mon, 2 Oct 2023 09:45:15 -0500 Subject: [PATCH 5/8] pr feedback Signed-off-by: Marc MacLeod <847542+marbemac@users.noreply.github.com> --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index b59f956a07a061..2d64cd5bc1105f 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -275,8 +275,6 @@ async function nodeImport( if (id.startsWith('data:') || isBuiltin(id)) { url = id } else { - const targetWeb = resolveOptions.ssrConfig?.target === 'webworker' - const resolved = tryNodeResolve( id, importer, @@ -286,7 +284,7 @@ async function nodeImport( typeof jest === 'undefined' ? { ...resolveOptions, tryEsmOnly: true } : resolveOptions, - targetWeb, + false, undefined, true, ) From 72c0e5c63d8965169627d3d9ba077a4eb3f60090 Mon Sep 17 00:00:00 2001 From: Marc MacLeod <847542+marbemac@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:44:52 -0500 Subject: [PATCH 6/8] docs Signed-off-by: Marc MacLeod <847542+marbemac@users.noreply.github.com> --- docs/config/ssr-options.md | 16 ++++++++++++++++ docs/guide/ssr.md | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/docs/config/ssr-options.md b/docs/config/ssr-options.md index 356c4e66eabad6..d5f96bdb415fca 100644 --- a/docs/config/ssr-options.md +++ b/docs/config/ssr-options.md @@ -20,3 +20,19 @@ Prevent listed dependencies from being externalized for SSR. If `true`, no depen - **Default:** `node` Build target for the SSR server. + +## ssr.resolve.conditions + +- **Type:** `string[]` +- **Related:** [Resolve Conditions](./shared-options.md#resolve-conditions) + +Defaults to the the root [`resolve.conditions`](./shared-options.md#resolve-conditions). + +These conditions are used in the plugin pipeline, and only affect non-externalized dependencies during the SSR build. Use `ssr.resolve.externalConditions` to affect externalized imports. + +## ssr.resolve.externalConditions + +- **Type:** `string[]` +- **Default:** `[]` + +Conditions that are used during ssr import (including `ssrLoadModule`) of externalized dependencies. diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index d208a9bcd87bb0..d7c4ecd4108653 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -259,6 +259,10 @@ In some cases like `webworker` runtimes, you might want to bundle your SSR build - Treat all dependencies as `noExternal` - Throw an error if any Node.js built-ins are imported +## SSR Resolve conditions + +By default package entry resolution will use the conditions set in [Resolve Conditions](../config/shared-options.md#resolve-conditions) for the SSR build. You can use [`ssr.resolve.conditions`](../config/ssr-options.md#ssr-resolve-conditions) and [`ssr.resolve.externalConditions`](../config/ssr-options.md#ssr-resolve-externalconditions) to customize this behavior. + ## Vite CLI The CLI commands `$ vite dev` and `$ vite preview` can also be used for SSR apps. You can add your SSR middlewares to the development server with [`configureServer`](/guide/api-plugin#configureserver) and to the preview server with [`configurePreviewServer`](/guide/api-plugin#configurepreviewserver). From e6b20fb82fb242e17beadf0f7c8f8aab4e5d6803 Mon Sep 17 00:00:00 2001 From: Marc MacLeod <847542+marbemac@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:52:37 -0500 Subject: [PATCH 7/8] tiny docs Signed-off-by: Marc MacLeod <847542+marbemac@users.noreply.github.com> --- docs/guide/ssr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index d7c4ecd4108653..a2800afd2250ee 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -261,7 +261,7 @@ In some cases like `webworker` runtimes, you might want to bundle your SSR build ## SSR Resolve conditions -By default package entry resolution will use the conditions set in [Resolve Conditions](../config/shared-options.md#resolve-conditions) for the SSR build. You can use [`ssr.resolve.conditions`](../config/ssr-options.md#ssr-resolve-conditions) and [`ssr.resolve.externalConditions`](../config/ssr-options.md#ssr-resolve-externalconditions) to customize this behavior. +By default package entry resolution will use the conditions set in [`resolve.conditions`](../config/shared-options.md#resolve-conditions) for the SSR build. You can use [`ssr.resolve.conditions`](../config/ssr-options.md#ssr-resolve-conditions) and [`ssr.resolve.externalConditions`](../config/ssr-options.md#ssr-resolve-externalconditions) to customize this behavior. ## Vite CLI From 4b4912c42b4d485bcc02b1c69a1a8e6c2fee8ef6 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Wed, 4 Oct 2023 16:44:11 +0800 Subject: [PATCH 8/8] docs: capitalize subheading --- docs/guide/ssr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index a2800afd2250ee..7052c0ae165150 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -259,7 +259,7 @@ In some cases like `webworker` runtimes, you might want to bundle your SSR build - Treat all dependencies as `noExternal` - Throw an error if any Node.js built-ins are imported -## SSR Resolve conditions +## SSR Resolve Conditions By default package entry resolution will use the conditions set in [`resolve.conditions`](../config/shared-options.md#resolve-conditions) for the SSR build. You can use [`ssr.resolve.conditions`](../config/ssr-options.md#ssr-resolve-conditions) and [`ssr.resolve.externalConditions`](../config/ssr-options.md#ssr-resolve-externalconditions) to customize this behavior.