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${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