From 3b08d34186b2fdc73172f9288f41d55e5bfd51aa Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 26 May 2022 17:23:16 +0200 Subject: [PATCH 01/10] feat: default esm SSR build, simplified externalization --- docs/config/ssr-options.md | 2 +- packages/vite/src/node/build.ts | 94 ++++++++++++------ packages/vite/src/node/config.ts | 2 +- .../vite/src/node/plugins/importAnalysis.ts | 13 ++- packages/vite/src/node/server/index.ts | 4 +- packages/vite/src/node/ssr/ssrExternal.ts | 97 +++++++++++++++++-- 6 files changed, 166 insertions(+), 46 deletions(-) diff --git a/docs/config/ssr-options.md b/docs/config/ssr-options.md index 7d828c8c989679..4308f5b9b2f50b 100644 --- a/docs/config/ssr-options.md +++ b/docs/config/ssr-options.md @@ -20,7 +20,7 @@ Prevent listed dependencies from being externalized for SSR. If `true`, no depen ## ssr.target -- **Type:** `'node' | 'webworker'` +- **Type:** `'node' | 'webworker' | 'node-cjs'` - **Default:** `node` Build target for the SSR server. diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 512f99732ffe8c..f5c0e276e6998a 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -31,7 +31,7 @@ import { manifestPlugin } from './plugins/manifest' import type { Logger } from './logger' import { dataURIPlugin } from './plugins/dataUri' import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild' -import { resolveSSRExternal, shouldExternalizeForSSR } from './ssr/ssrExternal' +import { cjsShouldExternalizeForSSR, cjsSsrResolveExternals, shouldExternalizeForSSR } from './ssr/ssrExternal' import { ssrManifestPlugin } from './ssr/ssrManifestPlugin' import type { DepOptimizationMetadata } from './optimizer' import { findKnownImports, getDepsCacheDir } from './optimizer' @@ -367,27 +367,10 @@ async function doBuild( ssr ? config.plugins.map((p) => injectSsrFlagToHooks(p)) : config.plugins ) as Plugin[] - // inject ssrExternal if present const userExternal = options.rollupOptions?.external let external = userExternal if (ssr) { - // see if we have cached deps data available - let knownImports: string[] | undefined - const dataPath = path.join(getDepsCacheDir(config), '_metadata.json') - try { - const data = JSON.parse( - fs.readFileSync(dataPath, 'utf-8') - ) as DepOptimizationMetadata - knownImports = Object.keys(data.optimized) - } catch (e) {} - if (!knownImports) { - // no dev deps optimization data, do a fresh scan - knownImports = await findKnownImports(config) - } - external = resolveExternal( - resolveSSRExternal(config, knownImports), - userExternal - ) + external = await ssrResolveExternal(config, userExternal) } const rollupOptions: RollupOptions = { @@ -421,10 +404,12 @@ async function doBuild( try { const buildOutputOptions = (output: OutputOptions = {}): OutputOptions => { + const cjsSsrBuild = ssr && config.ssr?.target === 'node-cjs' return { dir: outDir, - format: ssr ? 'cjs' : 'es', - exports: ssr ? 'named' : 'auto', + // Default format is 'es' for regular and for SSR builds + format: cjsSsrBuild ? 'cjs' : 'es', + exports: cjsSsrBuild ? 'named' : 'auto', sourcemap: options.sourcemap, name: libOptions ? libOptions.name : undefined, generatedCode: 'es2015', @@ -686,26 +671,75 @@ export function onRollupWarning( } } -function resolveExternal( +async function ssrResolveExternal( + config: ResolvedConfig, + user: ExternalOption | undefined +): Promise { + if( config.ssr?.target !== 'node-cjs') { + return esmSsrResolveExternal(config, user) + } + else { + // see if we have cached deps data available + let knownImports: string[] | undefined + const dataPath = path.join(getDepsCacheDir(config), '_metadata.json') + try { + const data = JSON.parse( + fs.readFileSync(dataPath, 'utf-8') + ) as DepOptimizationMetadata + knownImports = Object.keys(data.optimized) + } catch (e) {} + if (!knownImports) { + // no dev deps optimization data, do a fresh scan + knownImports = await findKnownImports(config) + } + return cjsSsrResolveExternal( + cjsSsrResolveExternals(config, knownImports), + user + ) + } +} + +function esmSsrResolveExternal( + config: ResolvedConfig, + user: ExternalOption | undefined +): ExternalOption { + return (id, parentId, isResolved) => { + if( user ) { + const isUserExternal = resolveUserExternal(user, id, parentId, isResolved) + if( typeof isUserExternal === 'boolean' ) { + return isUserExternal + } + } + return shouldExternalizeForSSR(id, config) + } +} + +// When ssr.format is node-cjs, this function reverts back to the 2.9 logic for externalization +function cjsSsrResolveExternal( ssrExternals: string[], user: ExternalOption | undefined ): ExternalOption { return (id, parentId, isResolved) => { - if (shouldExternalizeForSSR(id, ssrExternals)) { + const isExternal = cjsShouldExternalizeForSSR(id, ssrExternals) + if (isExternal) { return true } if (user) { - if (typeof user === 'function') { - return user(id, parentId, isResolved) - } else if (Array.isArray(user)) { - return user.some((test) => isExternal(id, test)) - } else { - return isExternal(id, user) - } + return resolveUserExternal(user, id, parentId, isResolved) } } } +function resolveUserExternal(user: ExternalOption, id: string, parentId: string | undefined, isResolved: boolean) { + if (typeof user === 'function') { + return user(id, parentId, isResolved) + } else if (Array.isArray(user)) { + return user.some((test) => isExternal(id, test)) + } else { + return isExternal(id, user) + } +} + function isExternal(id: string, test: string | RegExp) { if (typeof test === 'string') { return id === test diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index f64ff7c00c6502..2753b41bdebf3f 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -223,7 +223,7 @@ export interface ExperimentalOptions { importGlobRestoreExtension?: boolean } -export type SSRTarget = 'node' | 'webworker' +export type SSRTarget = 'node' | 'webworker' | 'node-cjs' export interface SSROptions { external?: string[] diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 4d312636f3e996..aff2c5efaffaed 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -41,7 +41,7 @@ import { } from '../utils' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' -import { shouldExternalizeForSSR } from '../ssr/ssrExternal' +import { cjsShouldExternalizeForSSR, shouldExternalizeForSSR } from '../ssr/ssrExternal' import { transformRequest } from '../server/transformRequest' import { getDepsCacheDir, @@ -363,10 +363,13 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } // skip ssr external if (ssr) { - if ( - server._ssrExternals && - shouldExternalizeForSSR(specifier, server._ssrExternals) - ) { + if (config.ssr?.target === 'node-cjs') { + if (cjsShouldExternalizeForSSR(specifier, server._ssrExternals) + ) { + continue + } + } + else if (shouldExternalizeForSSR(specifier, config)) { continue } if (isBuiltin(specifier)) { diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index a8e16b2b5cf9e5..fdc16705a62083 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -22,7 +22,7 @@ import { resolveHostname } from '../utils' import { ssrLoadModule } from '../ssr/ssrModuleLoader' -import { resolveSSRExternal } from '../ssr/ssrExternal' +import { cjsSsrResolveExternals } from '../ssr/ssrExternal' import { rebindErrorStacktrace, ssrRewriteStacktrace @@ -339,7 +339,7 @@ export async function createServer( ...Object.keys(optimizedDeps.metadata.discovered) ] } - server._ssrExternals = resolveSSRExternal(config, knownImports) + server._ssrExternals = cjsSsrResolveExternals(config, knownImports) } return ssrLoadModule( url, diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 52b7a47566ba2a..6376371374c2a4 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -5,7 +5,9 @@ import { createFilter } from '@rollup/pluginutils' import type { InternalResolveOptions } from '../plugins/resolve' import { tryNodeResolve } from '../plugins/resolve' import { + bareImportRE, createDebugger, + isBuiltin, isDefined, lookupFile, normalizePath, @@ -29,7 +31,7 @@ export function stripNesting(packages: string[]) { * Heuristics for determining whether a dependency should be externalized for * server-side rendering. */ -export function resolveSSRExternal( +export function cjsSsrResolveExternals( config: ResolvedConfig, knownImports: string[] ): string[] { @@ -49,7 +51,7 @@ export function resolveSSRExternal( seen.add(id) }) - collectExternals( + cjsSsrCollectExternals( config.root, config.resolve.preserveSymlinks, ssrExternals, @@ -86,8 +88,86 @@ const CJS_CONTENT_RE = // TODO: use import() const _require = createRequire(import.meta.url) -// do we need to do this ahead of time or could we do it lazily? -function collectExternals( +const isSsrExternalCache = new WeakMap boolean | undefined>() + +export function shouldExternalizeForSSR( + id: string, + config: ResolvedConfig +): boolean | undefined { + let isSsrExternal = isSsrExternalCache.get(config) + if( !isSsrExternal ) { + isSsrExternal = createIsSsrExternal(config) + isSsrExternalCache.set(config, isSsrExternal) + } + return isSsrExternal(id) +} + +function createIsSsrExternal(config: ResolvedConfig): (id: string) => boolean | undefined { + const processedIds = new Map() + + const { ssr, root } = config + + const noExternal = ssr?.noExternal + const noExternalFilter = noExternal !== 'undefined' && typeof noExternal !== 'boolean' && createFilter(undefined, noExternal, { resolve: false }) + + const isConfiguredAsExternal = (id: string) => { + const { ssr } = config + if ( !ssr || ssr.external?.includes(id)) { + return true + } + if ( typeof noExternal === 'boolean' ) { + return !noExternal + } + if ( noExternalFilter ) { + return noExternalFilter(id) + } + return true + } + + const resolveOptions: InternalResolveOptions = { + root, + preserveSymlinks: config.resolve.preserveSymlinks, + isProduction: false, + isBuild: true + } + + const isPackageEntry = (id: string) => { + if (!bareImportRE.test(id) || id.includes('\0')) { + return false + } + if( tryNodeResolve( + id, + undefined, + resolveOptions, + ssr?.target === 'webworker', + undefined, + true + )) { + return true + } + try { + // no main entry, but deep imports may be allowed + const pkgPath = resolveFrom(`${id}/package.json`, root) + if (pkgPath.includes('node_modules')) { + return true + } + } catch {} + return false + } + + return (id: string) => { + if( processedIds.has(id) ) { + return processedIds.get(id) + } + const external = isBuiltin(id) || (isPackageEntry(id) && isConfiguredAsExternal(id)) + processedIds.set(id, external) + return external + } +} + +// When ssr.format is 'node-cjs', this function is used reverting to the Vite 2.9 era +// SSR externalize heuristics +function cjsSsrCollectExternals( root: string, preserveSymlinks: boolean | undefined, ssrExternals: Set, @@ -192,14 +272,17 @@ function collectExternals( } for (const depRoot of depsToTrace) { - collectExternals(depRoot, preserveSymlinks, ssrExternals, seen, logger) + cjsSsrCollectExternals(depRoot, preserveSymlinks, ssrExternals, seen, logger) } } -export function shouldExternalizeForSSR( +export function cjsShouldExternalizeForSSR( id: string, - externals: string[] + externals: string[] | null ): boolean { + if( !externals ) { + return false + } const should = externals.some((e) => { if (id === e) { return true From 7aa0b7a4f5969632da43759677e83b32f064bc92 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 26 May 2022 17:24:14 +0200 Subject: [PATCH 02/10] feat: move SSR tests to type module --- playground/ssr-deps/__tests__/serve.ts | 2 +- playground/ssr-deps/package.json | 1 + playground/ssr-deps/server.js | 16 +++++++------- playground/ssr-html/__tests__/serve.ts | 2 +- playground/ssr-html/package.json | 1 + playground/ssr-html/server.js | 17 ++++++--------- playground/ssr-pug/__tests__/serve.ts | 2 +- playground/ssr-pug/package.json | 1 + playground/ssr-pug/server.js | 17 +++++++-------- playground/ssr-react/__tests__/serve.ts | 9 ++++++-- playground/ssr-react/package.json | 1 + playground/ssr-react/prerender.js | 8 ++++--- playground/ssr-react/server.js | 23 ++++++++++---------- playground/ssr-react/vite.config.js | 13 ++++++----- playground/ssr-vue/__tests__/serve.ts | 2 +- playground/ssr-vue/__tests__/ssr-vue.spec.ts | 4 ++-- playground/ssr-vue/package.json | 1 + playground/ssr-vue/prerender.js | 8 +++---- playground/ssr-vue/server.js | 23 ++++++++++---------- playground/ssr-vue/src/entry-server.js | 4 ++-- playground/ssr-vue/src/main.js | 2 +- playground/ssr-vue/src/router.js | 2 +- playground/ssr-vue/vite.config.js | 12 +++++----- playground/ssr-webworker/__tests__/serve.ts | 2 +- playground/ssr-webworker/package.json | 1 + playground/ssr-webworker/vite.config.js | 9 ++++---- playground/ssr-webworker/worker.js | 13 +++++------ 27 files changed, 100 insertions(+), 96 deletions(-) diff --git a/playground/ssr-deps/__tests__/serve.ts b/playground/ssr-deps/__tests__/serve.ts index 356131f36f374d..2cae7346eb53cb 100644 --- a/playground/ssr-deps/__tests__/serve.ts +++ b/playground/ssr-deps/__tests__/serve.ts @@ -10,7 +10,7 @@ export const port = ports['ssr-deps'] export async function serve() { await kill(port) - const { createServer } = require(path.resolve(rootDir, 'server.js')) + const { createServer } = await import(path.resolve(rootDir, 'server.js')) const { app, vite } = await createServer(rootDir, hmrPorts['ssr-deps']) return new Promise((resolve, reject) => { diff --git a/playground/ssr-deps/package.json b/playground/ssr-deps/package.json index 624f4e208bac51..74bbf77dd97bdd 100644 --- a/playground/ssr-deps/package.json +++ b/playground/ssr-deps/package.json @@ -2,6 +2,7 @@ "name": "test-ssr-deps", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "node server", "serve": "cross-env NODE_ENV=production node server", diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index 975d6959d16a5c..3d8f30d36ece54 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -1,11 +1,14 @@ // @ts-check -const fs = require('fs') -const path = require('path') -const express = require('express') +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import express from 'express' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD -async function createServer(root = process.cwd(), hmrPort) { +export async function createServer(root = process.cwd(), hmrPort) { const resolve = (p) => path.resolve(__dirname, p) const app = express() @@ -13,7 +16,7 @@ async function createServer(root = process.cwd(), hmrPort) { /** * @type {import('vite').ViteDevServer} */ - const vite = await require('vite').createServer({ + const vite = await (await import('vite')).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { @@ -63,6 +66,3 @@ if (!isTest) { }) ) } - -// for test use -exports.createServer = createServer diff --git a/playground/ssr-html/__tests__/serve.ts b/playground/ssr-html/__tests__/serve.ts index 9f18771acd26d4..f8c41aa00a3c77 100644 --- a/playground/ssr-html/__tests__/serve.ts +++ b/playground/ssr-html/__tests__/serve.ts @@ -10,7 +10,7 @@ export const port = ports['ssr-html'] export async function serve() { await kill(port) - const { createServer } = require(path.resolve(rootDir, 'server.js')) + const { createServer } = await import(path.resolve(rootDir, 'server.js')) const { app, vite } = await createServer(rootDir, hmrPorts['ssr-html']) return new Promise((resolve, reject) => { diff --git a/playground/ssr-html/package.json b/playground/ssr-html/package.json index b6d7cabe1b4cf5..73614bab95ca5a 100644 --- a/playground/ssr-html/package.json +++ b/playground/ssr-html/package.json @@ -2,6 +2,7 @@ "name": "test-ssr-html", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "node server", "serve": "cross-env NODE_ENV=production node server", diff --git a/playground/ssr-html/server.js b/playground/ssr-html/server.js index c94701afcfe771..1026ea831f5fc4 100644 --- a/playground/ssr-html/server.js +++ b/playground/ssr-html/server.js @@ -1,8 +1,9 @@ -// @ts-check -const fs = require('fs') -const path = require('path') -const express = require('express') +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import express from 'express' +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD const DYNAMIC_SCRIPTS = ` @@ -22,7 +23,7 @@ const DYNAMIC_STYLES = ` ` -async function createServer(root = process.cwd(), hmrPort) { +export async function createServer(root = process.cwd(), hmrPort) { const resolve = (p) => path.resolve(__dirname, p) const app = express() @@ -30,8 +31,7 @@ async function createServer(root = process.cwd(), hmrPort) { /** * @type {import('vite').ViteDevServer} */ - let vite - vite = await require('vite').createServer({ + const vite = await (await import('vite')).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { @@ -93,6 +93,3 @@ if (!isTest) { }) ) } - -// for test use -exports.createServer = createServer diff --git a/playground/ssr-pug/__tests__/serve.ts b/playground/ssr-pug/__tests__/serve.ts index 7f25677af410a3..7710af9b7674c8 100644 --- a/playground/ssr-pug/__tests__/serve.ts +++ b/playground/ssr-pug/__tests__/serve.ts @@ -10,7 +10,7 @@ export const port = ports['ssr-pug'] export async function serve() { await kill(port) - const { createServer } = require(path.resolve(rootDir, 'server.js')) + const { createServer } = await import(path.resolve(rootDir, 'server.js')) const { app, vite } = await createServer(rootDir, hmrPorts['ssr-pug']) return new Promise((resolve, reject) => { diff --git a/playground/ssr-pug/package.json b/playground/ssr-pug/package.json index afa80df44a18bc..547e8cbf7e94b2 100644 --- a/playground/ssr-pug/package.json +++ b/playground/ssr-pug/package.json @@ -2,6 +2,7 @@ "name": "test-ssr-pug", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "node server", "serve": "cross-env NODE_ENV=production node server", diff --git a/playground/ssr-pug/server.js b/playground/ssr-pug/server.js index 2a3b48e5604b85..1967e1971c25df 100644 --- a/playground/ssr-pug/server.js +++ b/playground/ssr-pug/server.js @@ -1,7 +1,10 @@ // @ts-check -const path = require('path') -const pug = require('pug') -const express = require('express') +import path from 'path' +import { fileURLToPath } from 'url' +import pug from 'pug' +import express from 'express' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD @@ -14,7 +17,7 @@ const DYNAMIC_SCRIPTS = ` ` -async function createServer(root = process.cwd(), hmrPort) { +export async function createServer(root = process.cwd(), hmrPort) { const resolve = (p) => path.resolve(__dirname, p) const app = express() @@ -22,8 +25,7 @@ async function createServer(root = process.cwd(), hmrPort) { /** * @type {import('vite').ViteDevServer} */ - let vite - vite = await require('vite').createServer({ + const vite = await (await import('vite')).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { @@ -71,6 +73,3 @@ if (!isTest) { }) ) } - -// for test use -exports.createServer = createServer diff --git a/playground/ssr-react/__tests__/serve.ts b/playground/ssr-react/__tests__/serve.ts index 21bcf61863e92f..57c09664efdeae 100644 --- a/playground/ssr-react/__tests__/serve.ts +++ b/playground/ssr-react/__tests__/serve.ts @@ -29,14 +29,19 @@ export async function serve() { build: { target: 'esnext', ssr: 'src/entry-server.jsx', - outDir: 'dist/server' + outDir: 'dist/server', + rollupOptions: { + output: { + entryFileNames: 'entry-server.cjs' + } + } } }) } await kill(port) - const { createServer } = require(path.resolve(rootDir, 'server.js')) + const { createServer } = await import(path.resolve(rootDir, 'server.js')) const { app, vite } = await createServer( rootDir, isBuild, diff --git a/playground/ssr-react/package.json b/playground/ssr-react/package.json index e6a954b56c839c..f280db9a1c4676 100644 --- a/playground/ssr-react/package.json +++ b/playground/ssr-react/package.json @@ -2,6 +2,7 @@ "name": "test-ssr-react", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "node server", "build": "npm run build:client && npm run build:server", diff --git a/playground/ssr-react/prerender.js b/playground/ssr-react/prerender.js index ac88ef632ec6f5..b5596f7493435a 100644 --- a/playground/ssr-react/prerender.js +++ b/playground/ssr-react/prerender.js @@ -1,13 +1,15 @@ // Pre-render the app into static HTML. // run `yarn generate` and then `dist/static` can be served as a static site. -const fs = require('fs') -const path = require('path') +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const toAbsolute = (p) => path.resolve(__dirname, p) const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8') -const { render } = require('./dist/server/entry-server.js') +const { render } = await import('./dist/server/entry-server.js') // determine routes to pre-render from src/pages const routesToPrerender = fs diff --git a/playground/ssr-react/server.js b/playground/ssr-react/server.js index 2c36d0ec5add43..215f211aa291b9 100644 --- a/playground/ssr-react/server.js +++ b/playground/ssr-react/server.js @@ -1,13 +1,15 @@ -// @ts-check -const fs = require('fs') -const path = require('path') -const express = require('express') +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import express from 'express' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD process.env.MY_CUSTOM_SECRET = 'API_KEY_qwertyuiop' -async function createServer( +export async function createServer( root = process.cwd(), isProd = process.env.NODE_ENV === 'production', hmrPort @@ -25,7 +27,7 @@ async function createServer( */ let vite if (!isProd) { - vite = await require('vite').createServer({ + vite = await (await import('vite')).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { @@ -44,9 +46,9 @@ async function createServer( // use vite's connect instance as middleware app.use(vite.middlewares) } else { - app.use(require('compression')()) + app.use((await import('compression')).default()) app.use( - require('serve-static')(resolve('dist/client'), { + (await import('serve-static')).default(resolve('dist/client'), { index: false }) ) @@ -65,7 +67,7 @@ async function createServer( } else { template = indexProd // @ts-ignore - render = require('./dist/server/entry-server.js').render + render = (await import('./dist/server/entry-server.cjs')).render } const context = {} @@ -96,6 +98,3 @@ if (!isTest) { }) ) } - -// for test use -exports.createServer = createServer diff --git a/playground/ssr-react/vite.config.js b/playground/ssr-react/vite.config.js index bcc1369313cc5a..94d7192a60ccd1 100644 --- a/playground/ssr-react/vite.config.js +++ b/playground/ssr-react/vite.config.js @@ -1,11 +1,12 @@ -const react = require('@vitejs/plugin-react') +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' -/** - * @type {import('vite').UserConfig} - */ -module.exports = { +export default defineConfig({ plugins: [react()], build: { minify: false + }, + ssr: { + target: 'node-cjs' } -} +}) diff --git a/playground/ssr-vue/__tests__/serve.ts b/playground/ssr-vue/__tests__/serve.ts index c00a0359c5cd74..471929634483e8 100644 --- a/playground/ssr-vue/__tests__/serve.ts +++ b/playground/ssr-vue/__tests__/serve.ts @@ -36,7 +36,7 @@ export async function serve() { await kill(port) - const { createServer } = require(path.resolve(rootDir, 'server.js')) + const { createServer } = await import(path.resolve(rootDir, 'server.js')) const { app, vite } = await createServer( rootDir, isBuild, diff --git a/playground/ssr-vue/__tests__/ssr-vue.spec.ts b/playground/ssr-vue/__tests__/ssr-vue.spec.ts index e4b8170364f009..f1e35e3ab3ed36 100644 --- a/playground/ssr-vue/__tests__/ssr-vue.spec.ts +++ b/playground/ssr-vue/__tests__/ssr-vue.spec.ts @@ -183,10 +183,10 @@ test.runIf(isBuild)('dynamic css file should be preloaded', async () => { const homeHtml = await (await fetch(url)).text() const re = /link rel="modulepreload".*?href="\/assets\/(Home\.\w{8}\.js)"/ const filename = re.exec(homeHtml)[1] - const manifest = require(resolve( + const manifest = (await import(resolve( process.cwd(), './playground-temp/ssr-vue/dist/client/ssr-manifest.json' - )) + ))).default const depFile = manifest[filename] for (const file of depFile) { expect(homeHtml).toMatch(file) diff --git a/playground/ssr-vue/package.json b/playground/ssr-vue/package.json index 0464ebeeb76e05..5850eed7752c81 100644 --- a/playground/ssr-vue/package.json +++ b/playground/ssr-vue/package.json @@ -2,6 +2,7 @@ "name": "test-ssr-vue", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "node server", "build": "npm run build:client && npm run build:server", diff --git a/playground/ssr-vue/prerender.js b/playground/ssr-vue/prerender.js index c4158dbe3357a9..2ba2debb891fa4 100644 --- a/playground/ssr-vue/prerender.js +++ b/playground/ssr-vue/prerender.js @@ -1,14 +1,14 @@ // Pre-render the app into static HTML. // run `npm run generate` and then `dist/static` can be served as a static site. -const fs = require('fs') -const path = require('path') +import fs from 'fs' +import path from 'path' const toAbsolute = (p) => path.resolve(__dirname, p) -const manifest = require('./dist/static/ssr-manifest.json') +const manifest = (await import('./dist/static/ssr-manifest.json')).default const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8') -const { render } = require('./dist/server/entry-server.js') +const { render } = await import('./dist/server/entry-server.js') // determine routes to pre-render from src/pages const routesToPrerender = fs diff --git a/playground/ssr-vue/server.js b/playground/ssr-vue/server.js index a626f8299be022..d409fa0e4f9a1a 100644 --- a/playground/ssr-vue/server.js +++ b/playground/ssr-vue/server.js @@ -1,15 +1,17 @@ // @ts-check -const fs = require('fs') -const path = require('path') -const express = require('express') +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import express from 'express' const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD -async function createServer( +export async function createServer( root = process.cwd(), isProd = process.env.NODE_ENV === 'production', hmrPort ) { + const __dirname = path.dirname(fileURLToPath(import.meta.url)) const resolve = (p) => path.resolve(__dirname, p) const indexProd = isProd @@ -18,7 +20,7 @@ async function createServer( const manifest = isProd ? // @ts-ignore - require('./dist/client/ssr-manifest.json') + (await import('./dist/client/ssr-manifest.json')).default : {} const app = express() @@ -28,7 +30,7 @@ async function createServer( */ let vite if (!isProd) { - vite = await require('vite').createServer({ + vite = await (await import('vite')).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { @@ -47,9 +49,9 @@ async function createServer( // use vite's connect instance as middleware app.use(vite.middlewares) } else { - app.use(require('compression')()) + app.use((await import('compression')).default()) app.use( - require('serve-static')(resolve('dist/client'), { + (await import('serve-static')).default(resolve('dist/client'), { index: false }) ) @@ -68,7 +70,7 @@ async function createServer( } else { template = indexProd // @ts-ignore - render = require('./dist/server/entry-server.js').render + render = (await import('./dist/server/entry-server.js')).render } const [appHtml, preloadLinks] = await render(url, manifest) @@ -95,6 +97,3 @@ if (!isTest) { }) ) } - -// for test use -exports.createServer = createServer diff --git a/playground/ssr-vue/src/entry-server.js b/playground/ssr-vue/src/entry-server.js index 0f4e47711c17a1..f86798a39da6c2 100644 --- a/playground/ssr-vue/src/entry-server.js +++ b/playground/ssr-vue/src/entry-server.js @@ -1,6 +1,6 @@ -import { createApp } from './main' +import { basename } from 'path' import { renderToString } from 'vue/server-renderer' -import path, { basename } from 'path' +import { createApp } from './main' export async function render(url, manifest) { const { app, router } = createApp() diff --git a/playground/ssr-vue/src/main.js b/playground/ssr-vue/src/main.js index dbf4287b0baf3c..ee969e0f1c449e 100644 --- a/playground/ssr-vue/src/main.js +++ b/playground/ssr-vue/src/main.js @@ -1,5 +1,5 @@ -import App from './App.vue' import { createSSRApp } from 'vue' +import App from './App.vue' import { createRouter } from './router' // SSR requires a fresh app instance per request, therefore we export a function diff --git a/playground/ssr-vue/src/router.js b/playground/ssr-vue/src/router.js index b80b76b0bf4e2a..2c4f23f7b07e62 100644 --- a/playground/ssr-vue/src/router.js +++ b/playground/ssr-vue/src/router.js @@ -1,6 +1,6 @@ import { - createMemoryHistory, createRouter as _createRouter, + createMemoryHistory, createWebHistory } from 'vue-router' diff --git a/playground/ssr-vue/vite.config.js b/playground/ssr-vue/vite.config.js index 83128683536388..e9fc4c1e309e0f 100644 --- a/playground/ssr-vue/vite.config.js +++ b/playground/ssr-vue/vite.config.js @@ -1,14 +1,12 @@ -const vuePlugin = require('@vitejs/plugin-vue') -const vueJsx = require('@vitejs/plugin-vue-jsx') +import { defineConfig } from 'vite' +import vuePlugin from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' const virtualFile = '@virtual-file' const virtualId = '\0' + virtualFile const nestedVirtualFile = '@nested-virtual-file' const nestedVirtualId = '\0' + nestedVirtualFile -/** - * @type {import('vite').UserConfig} - */ -module.exports = { +export default defineConfig({ plugins: [ vuePlugin(), vueJsx(), @@ -55,4 +53,4 @@ module.exports = { optimizeDeps: { exclude: ['example-external-component'] } -} +}) diff --git a/playground/ssr-webworker/__tests__/serve.ts b/playground/ssr-webworker/__tests__/serve.ts index 09578b58499f6c..e22aab83e51082 100644 --- a/playground/ssr-webworker/__tests__/serve.ts +++ b/playground/ssr-webworker/__tests__/serve.ts @@ -25,7 +25,7 @@ export async function serve() { } }) - const { createServer } = require(path.resolve(rootDir, 'worker.js')) + const { createServer } = await import(path.resolve(rootDir, 'worker.js')) const { app } = await createServer(rootDir, isBuild) return new Promise((resolve, reject) => { diff --git a/playground/ssr-webworker/package.json b/playground/ssr-webworker/package.json index a7ebdf27ea22aa..00439576fbac69 100644 --- a/playground/ssr-webworker/package.json +++ b/playground/ssr-webworker/package.json @@ -2,6 +2,7 @@ "name": "test-ssr-webworker", "private": true, "version": "0.0.0", + "type": "module", "scripts": { "dev": "DEV=1 node worker", "build:worker": "vite build --ssr src/entry-worker.jsx --outDir dist/worker" diff --git a/playground/ssr-webworker/vite.config.js b/playground/ssr-webworker/vite.config.js index 91a0571380608e..3a476198813c35 100644 --- a/playground/ssr-webworker/vite.config.js +++ b/playground/ssr-webworker/vite.config.js @@ -1,7 +1,6 @@ -/** - * @type {import('vite').UserConfig} - */ -module.exports = { +import { defineConfig } from 'vite' + +export default defineConfig({ build: { minify: false }, @@ -32,4 +31,4 @@ module.exports = { } } ] -} +}) diff --git a/playground/ssr-webworker/worker.js b/playground/ssr-webworker/worker.js index baa4fe485e02de..920ab21184e8a3 100644 --- a/playground/ssr-webworker/worker.js +++ b/playground/ssr-webworker/worker.js @@ -1,10 +1,12 @@ -// @ts-check -const path = require('path') -const { Miniflare } = require('miniflare') +import { fileURLToPath } from 'url' +import path from 'path' +import { Miniflare } from 'miniflare' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const isTest = !!process.env.TEST -async function createServer() { +export async function createServer() { const mf = new Miniflare({ scriptPath: path.resolve(__dirname, 'dist/worker/entry-worker.js') }) @@ -21,6 +23,3 @@ if (!isTest) { }) ) } - -// for test use -exports.createServer = createServer From 92cfb81e96b68f8f71cc8d6e22c6058fba627475 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 26 May 2022 17:27:51 +0200 Subject: [PATCH 03/10] chore: format --- packages/vite/src/node/build.ts | 22 +++++-- .../vite/src/node/plugins/importAnalysis.ts | 11 ++-- packages/vite/src/node/ssr/ssrExternal.ts | 65 ++++++++++++------- playground/ssr-deps/server.js | 4 +- playground/ssr-html/server.js | 4 +- playground/ssr-pug/server.js | 4 +- playground/ssr-react/server.js | 4 +- playground/ssr-vue/__tests__/ssr-vue.spec.ts | 12 ++-- playground/ssr-vue/server.js | 4 +- 9 files changed, 85 insertions(+), 45 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index f5c0e276e6998a..fd7f625870f24a 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -31,7 +31,11 @@ import { manifestPlugin } from './plugins/manifest' import type { Logger } from './logger' import { dataURIPlugin } from './plugins/dataUri' import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild' -import { cjsShouldExternalizeForSSR, cjsSsrResolveExternals, shouldExternalizeForSSR } from './ssr/ssrExternal' +import { + cjsShouldExternalizeForSSR, + cjsSsrResolveExternals, + shouldExternalizeForSSR +} from './ssr/ssrExternal' import { ssrManifestPlugin } from './ssr/ssrManifestPlugin' import type { DepOptimizationMetadata } from './optimizer' import { findKnownImports, getDepsCacheDir } from './optimizer' @@ -675,10 +679,9 @@ async function ssrResolveExternal( config: ResolvedConfig, user: ExternalOption | undefined ): Promise { - if( config.ssr?.target !== 'node-cjs') { + if (config.ssr?.target !== 'node-cjs') { return esmSsrResolveExternal(config, user) - } - else { + } else { // see if we have cached deps data available let knownImports: string[] | undefined const dataPath = path.join(getDepsCacheDir(config), '_metadata.json') @@ -704,9 +707,9 @@ function esmSsrResolveExternal( user: ExternalOption | undefined ): ExternalOption { return (id, parentId, isResolved) => { - if( user ) { + if (user) { const isUserExternal = resolveUserExternal(user, id, parentId, isResolved) - if( typeof isUserExternal === 'boolean' ) { + if (typeof isUserExternal === 'boolean') { return isUserExternal } } @@ -730,7 +733,12 @@ function cjsSsrResolveExternal( } } -function resolveUserExternal(user: ExternalOption, id: string, parentId: string | undefined, isResolved: boolean) { +function resolveUserExternal( + user: ExternalOption, + id: string, + parentId: string | undefined, + isResolved: boolean +) { if (typeof user === 'function') { return user(id, parentId, isResolved) } else if (Array.isArray(user)) { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index aff2c5efaffaed..0697ba5115f7b1 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -41,7 +41,10 @@ import { } from '../utils' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' -import { cjsShouldExternalizeForSSR, shouldExternalizeForSSR } from '../ssr/ssrExternal' +import { + cjsShouldExternalizeForSSR, + shouldExternalizeForSSR +} from '../ssr/ssrExternal' import { transformRequest } from '../server/transformRequest' import { getDepsCacheDir, @@ -364,12 +367,10 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // skip ssr external if (ssr) { if (config.ssr?.target === 'node-cjs') { - if (cjsShouldExternalizeForSSR(specifier, server._ssrExternals) - ) { + if (cjsShouldExternalizeForSSR(specifier, server._ssrExternals)) { continue } - } - else if (shouldExternalizeForSSR(specifier, config)) { + } else if (shouldExternalizeForSSR(specifier, config)) { continue } if (isBuiltin(specifier)) { diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 6376371374c2a4..62e91ab034f6b6 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -88,37 +88,45 @@ const CJS_CONTENT_RE = // TODO: use import() const _require = createRequire(import.meta.url) -const isSsrExternalCache = new WeakMap boolean | undefined>() +const isSsrExternalCache = new WeakMap< + ResolvedConfig, + (id: string) => boolean | undefined +>() export function shouldExternalizeForSSR( id: string, config: ResolvedConfig ): boolean | undefined { let isSsrExternal = isSsrExternalCache.get(config) - if( !isSsrExternal ) { + if (!isSsrExternal) { isSsrExternal = createIsSsrExternal(config) isSsrExternalCache.set(config, isSsrExternal) } return isSsrExternal(id) } -function createIsSsrExternal(config: ResolvedConfig): (id: string) => boolean | undefined { +function createIsSsrExternal( + config: ResolvedConfig +): (id: string) => boolean | undefined { const processedIds = new Map() - + const { ssr, root } = config const noExternal = ssr?.noExternal - const noExternalFilter = noExternal !== 'undefined' && typeof noExternal !== 'boolean' && createFilter(undefined, noExternal, { resolve: false }) - + const noExternalFilter = + noExternal !== 'undefined' && + typeof noExternal !== 'boolean' && + createFilter(undefined, noExternal, { resolve: false }) + const isConfiguredAsExternal = (id: string) => { const { ssr } = config - if ( !ssr || ssr.external?.includes(id)) { + if (!ssr || ssr.external?.includes(id)) { return true } - if ( typeof noExternal === 'boolean' ) { + if (typeof noExternal === 'boolean') { return !noExternal } - if ( noExternalFilter ) { + if (noExternalFilter) { return noExternalFilter(id) } return true @@ -135,14 +143,16 @@ function createIsSsrExternal(config: ResolvedConfig): (id: string) => boolean | if (!bareImportRE.test(id) || id.includes('\0')) { return false } - if( tryNodeResolve( - id, - undefined, - resolveOptions, - ssr?.target === 'webworker', - undefined, - true - )) { + if ( + tryNodeResolve( + id, + undefined, + resolveOptions, + ssr?.target === 'webworker', + undefined, + true + ) + ) { return true } try { @@ -151,21 +161,22 @@ function createIsSsrExternal(config: ResolvedConfig): (id: string) => boolean | if (pkgPath.includes('node_modules')) { return true } - } catch {} + } catch {} return false } - + return (id: string) => { - if( processedIds.has(id) ) { + if (processedIds.has(id)) { return processedIds.get(id) } - const external = isBuiltin(id) || (isPackageEntry(id) && isConfiguredAsExternal(id)) + const external = + isBuiltin(id) || (isPackageEntry(id) && isConfiguredAsExternal(id)) processedIds.set(id, external) return external } } -// When ssr.format is 'node-cjs', this function is used reverting to the Vite 2.9 era +// When ssr.format is 'node-cjs', this function is used reverting to the Vite 2.9 era // SSR externalize heuristics function cjsSsrCollectExternals( root: string, @@ -272,7 +283,13 @@ function cjsSsrCollectExternals( } for (const depRoot of depsToTrace) { - cjsSsrCollectExternals(depRoot, preserveSymlinks, ssrExternals, seen, logger) + cjsSsrCollectExternals( + depRoot, + preserveSymlinks, + ssrExternals, + seen, + logger + ) } } @@ -280,7 +297,7 @@ export function cjsShouldExternalizeForSSR( id: string, externals: string[] | null ): boolean { - if( !externals ) { + if (!externals) { return false } const should = externals.some((e) => { diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index 3d8f30d36ece54..f205255320cfe2 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -16,7 +16,9 @@ export async function createServer(root = process.cwd(), hmrPort) { /** * @type {import('vite').ViteDevServer} */ - const vite = await (await import('vite')).createServer({ + const vite = await ( + await import('vite') + ).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { diff --git a/playground/ssr-html/server.js b/playground/ssr-html/server.js index 1026ea831f5fc4..facc502c9b4d07 100644 --- a/playground/ssr-html/server.js +++ b/playground/ssr-html/server.js @@ -31,7 +31,9 @@ export async function createServer(root = process.cwd(), hmrPort) { /** * @type {import('vite').ViteDevServer} */ - const vite = await (await import('vite')).createServer({ + const vite = await ( + await import('vite') + ).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { diff --git a/playground/ssr-pug/server.js b/playground/ssr-pug/server.js index 1967e1971c25df..034540e81d47cf 100644 --- a/playground/ssr-pug/server.js +++ b/playground/ssr-pug/server.js @@ -25,7 +25,9 @@ export async function createServer(root = process.cwd(), hmrPort) { /** * @type {import('vite').ViteDevServer} */ - const vite = await (await import('vite')).createServer({ + const vite = await ( + await import('vite') + ).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { diff --git a/playground/ssr-react/server.js b/playground/ssr-react/server.js index 215f211aa291b9..8445ebc61d88fb 100644 --- a/playground/ssr-react/server.js +++ b/playground/ssr-react/server.js @@ -27,7 +27,9 @@ export async function createServer( */ let vite if (!isProd) { - vite = await (await import('vite')).createServer({ + vite = await ( + await import('vite') + ).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { diff --git a/playground/ssr-vue/__tests__/ssr-vue.spec.ts b/playground/ssr-vue/__tests__/ssr-vue.spec.ts index f1e35e3ab3ed36..0eda84b3c78b26 100644 --- a/playground/ssr-vue/__tests__/ssr-vue.spec.ts +++ b/playground/ssr-vue/__tests__/ssr-vue.spec.ts @@ -183,10 +183,14 @@ test.runIf(isBuild)('dynamic css file should be preloaded', async () => { const homeHtml = await (await fetch(url)).text() const re = /link rel="modulepreload".*?href="\/assets\/(Home\.\w{8}\.js)"/ const filename = re.exec(homeHtml)[1] - const manifest = (await import(resolve( - process.cwd(), - './playground-temp/ssr-vue/dist/client/ssr-manifest.json' - ))).default + const manifest = ( + await import( + resolve( + process.cwd(), + './playground-temp/ssr-vue/dist/client/ssr-manifest.json' + ) + ) + ).default const depFile = manifest[filename] for (const file of depFile) { expect(homeHtml).toMatch(file) diff --git a/playground/ssr-vue/server.js b/playground/ssr-vue/server.js index d409fa0e4f9a1a..fb0fd58554b276 100644 --- a/playground/ssr-vue/server.js +++ b/playground/ssr-vue/server.js @@ -30,7 +30,9 @@ export async function createServer( */ let vite if (!isProd) { - vite = await (await import('vite')).createServer({ + vite = await ( + await import('vite') + ).createServer({ root, logLevel: isTest ? 'error' : 'info', server: { From 8dc7d98e8f6e6b87ddaa1ed6a42fc98257fcae3a Mon Sep 17 00:00:00 2001 From: patak Date: Thu, 26 May 2022 19:05:04 +0200 Subject: [PATCH 04/10] perf: avoid some cycles if the id is noExternal Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/vite/src/node/ssr/ssrExternal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 62e91ab034f6b6..e40f0cbad9d103 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -170,7 +170,7 @@ function createIsSsrExternal( return processedIds.get(id) } const external = - isBuiltin(id) || (isPackageEntry(id) && isConfiguredAsExternal(id)) + isBuiltin(id) || (isConfiguredAsExternal(id) && isPackageEntry(id)) processedIds.set(id, external) return external } From ac494acd3b7afb3e227db1a1a6450a7604eda48a Mon Sep 17 00:00:00 2001 From: patak-dev Date: Thu, 26 May 2022 21:22:51 +0200 Subject: [PATCH 05/10] fix: use CJS SSR build for VitePress --- docs/vite.config.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/vite.config.ts diff --git a/docs/vite.config.ts b/docs/vite.config.ts new file mode 100644 index 00000000000000..4493991d9a6cca --- /dev/null +++ b/docs/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + ssr: { + target: 'node-cjs' + } +}) From ef6e8ea697d3b4afff12b9259aee37655fad7b45 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Fri, 27 May 2022 00:02:43 +0200 Subject: [PATCH 06/10] fix: externalize in resolveId to add resolved extension for deep imports --- packages/vite/src/node/build.ts | 67 ++++++------------- packages/vite/src/node/plugins/index.ts | 7 +- packages/vite/src/node/plugins/resolve.ts | 44 +++++++++--- .../vite/src/node/plugins/ssrRequireHook.ts | 1 + playground/ssr-react/__tests__/serve.ts | 2 +- playground/ssr-react/server.js | 2 +- playground/ssr-react/vite.config.js | 3 - 7 files changed, 65 insertions(+), 61 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index b174b284396b7f..fa8ee91c690026 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -33,8 +33,7 @@ import { dataURIPlugin } from './plugins/dataUri' import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild' import { cjsShouldExternalizeForSSR, - cjsSsrResolveExternals, - shouldExternalizeForSSR + cjsSsrResolveExternals } from './ssr/ssrExternal' import { ssrManifestPlugin } from './ssr/ssrManifestPlugin' import type { DepOptimizationMetadata } from './optimizer' @@ -379,8 +378,12 @@ async function doBuild( const userExternal = options.rollupOptions?.external let external = userExternal - if (ssr) { - external = await ssrResolveExternal(config, userExternal) + + // In CJS, we can pass the externals to rollup as is. In ESM, we need to + // do it in the resolve plugin so we can add the resolved extension for + // deep node_modules imports + if (ssr && config.ssr?.target === 'node-cjs') { + external = await cjsSsrResolveExternal(config, userExternal) } if (isDepsOptimizerEnabled(config) && !ssr) { @@ -685,53 +688,25 @@ export function onRollupWarning( } } -async function ssrResolveExternal( +async function cjsSsrResolveExternal( config: ResolvedConfig, user: ExternalOption | undefined ): Promise { - if (config.ssr?.target !== 'node-cjs') { - return esmSsrResolveExternal(config, user) - } else { - // see if we have cached deps data available - let knownImports: string[] | undefined - const dataPath = path.join(getDepsCacheDir(config), '_metadata.json') - try { - const data = JSON.parse( - fs.readFileSync(dataPath, 'utf-8') - ) as DepOptimizationMetadata - knownImports = Object.keys(data.optimized) - } catch (e) {} - if (!knownImports) { - // no dev deps optimization data, do a fresh scan - knownImports = await findKnownImports(config) - } - return cjsSsrResolveExternal( - cjsSsrResolveExternals(config, knownImports), - user - ) - } -} - -function esmSsrResolveExternal( - config: ResolvedConfig, - user: ExternalOption | undefined -): ExternalOption { - return (id, parentId, isResolved) => { - if (user) { - const isUserExternal = resolveUserExternal(user, id, parentId, isResolved) - if (typeof isUserExternal === 'boolean') { - return isUserExternal - } - } - return shouldExternalizeForSSR(id, config) + // see if we have cached deps data available + let knownImports: string[] | undefined + const dataPath = path.join(getDepsCacheDir(config), '_metadata.json') + try { + const data = JSON.parse( + fs.readFileSync(dataPath, 'utf-8') + ) as DepOptimizationMetadata + knownImports = Object.keys(data.optimized) + } catch (e) {} + if (!knownImports) { + // no dev deps optimization data, do a fresh scan + knownImports = await findKnownImports(config) } -} + const ssrExternals = cjsSsrResolveExternals(config, knownImports) -// When ssr.format is node-cjs, this function reverts back to the 2.9 logic for externalization -function cjsSsrResolveExternal( - ssrExternals: string[], - user: ExternalOption | undefined -): ExternalOption { return (id, parentId, isResolved) => { const isExternal = cjsShouldExternalizeForSSR(id, ssrExternals) if (isExternal) { diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 4d717d5e319840..8c009ff3293885 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -3,6 +3,7 @@ import type { ResolvedConfig } from '../config' import { isDepsOptimizerEnabled } from '../config' import type { Plugin } from '../plugin' import { getDepsOptimizer } from '../optimizer' +import { shouldExternalizeForSSR } from '../ssr/ssrExternal' import { jsonPlugin } from './json' import { resolvePlugin } from './resolve' import { optimizedDepsBuildPlugin, optimizedDepsPlugin } from './optimizedDeps' @@ -61,7 +62,11 @@ export async function resolvePlugins( packageCache: config.packageCache, ssrConfig: config.ssr, asSrc: true, - getDepsOptimizer: () => getDepsOptimizer(config) + getDepsOptimizer: () => getDepsOptimizer(config), + shouldExternalize: + isBuild && config.ssr?.target !== 'node-cjs' + ? (id) => shouldExternalizeForSSR(id, config) + : undefined }), htmlInlineProxyPlugin(config), cssPlugin(config), diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 551dd8dab211b1..f1f940c218716a 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -83,6 +83,7 @@ export interface InternalResolveOptions extends ResolveOptions { scan?: boolean // Resolve using esbuild deps optimization getDepsOptimizer?: () => DepsOptimizer | undefined + shouldExternalize?: (id: string) => boolean | undefined } export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { @@ -105,6 +106,7 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { const depsOptimizer = baseOptions.getDepsOptimizer?.() const ssr = resolveOpts?.ssr === true + if (id.startsWith(browserExternalId)) { return id } @@ -258,7 +260,10 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { // bare package imports, perform node resolve if (bareImportRE.test(id)) { + const external = options.shouldExternalize?.(id) + if ( + !external && asSrc && depsOptimizer && !ssr && @@ -270,7 +275,13 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { if ( targetWeb && - (res = tryResolveBrowserMapping(id, importer, options, false)) + (res = tryResolveBrowserMapping( + id, + importer, + options, + false, + external + )) ) { return res } @@ -282,7 +293,8 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { options, targetWeb, depsOptimizer, - ssr + ssr, + external )) ) { return res @@ -523,7 +535,8 @@ export function tryNodeResolve( options: InternalResolveOptions, targetWeb: boolean, depsOptimizer?: DepsOptimizer, - ssr?: boolean + ssr?: boolean, + externalize?: boolean ): PartialResolvedId | undefined { const { root, dedupe, isBuild, preserveSymlinks, packageCache } = options @@ -591,7 +604,8 @@ export function tryNodeResolve( let resolveId = resolvePackageEntry let unresolvedId = pkgId - if (unresolvedId !== nestedPath) { + const isDeepImport = unresolvedId !== nestedPath + if (isDeepImport) { resolveId = resolveDeepImport unresolvedId = '.' + nestedPath.slice(pkgId.length) } @@ -616,15 +630,25 @@ export function tryNodeResolve( return } + const processResult = (resolved: PartialResolvedId) => { + if (!externalize) { + return resolved + } + const resolvedExt = path.extname(resolved.id) + const resolvedId = + isDeepImport && path.extname(id) !== resolvedExt ? id + resolvedExt : id + return { ...resolved, id: resolvedId, external: true } + } + // link id to pkg for browser field mapping check idToPkgMap.set(resolved, pkg) - if (isBuild && !depsOptimizer) { + if ((isBuild && !depsOptimizer) || externalize) { // Resolve package side effects for build so that rollup can better // perform tree-shaking - return { + return processResult({ id: resolved, moduleSideEffects: pkg.hasSideEffects(resolved) - } + }) } if ( @@ -940,7 +964,8 @@ function tryResolveBrowserMapping( id: string, importer: string | undefined, options: InternalResolveOptions, - isFilePath: boolean + isFilePath: boolean, + externalize?: boolean ) { let res: string | undefined const pkg = importer && idToPkgMap.get(importer) @@ -953,10 +978,11 @@ function tryResolveBrowserMapping( isDebug && debug(`[browser mapped] ${colors.cyan(id)} -> ${colors.dim(res)}`) idToPkgMap.set(res, pkg) - return { + const result = { id: res, moduleSideEffects: pkg.hasSideEffects(res) } + return externalize ? { ...result, external: true } : result } } else if (browserMappedPath === false) { return browserExternalId diff --git a/packages/vite/src/node/plugins/ssrRequireHook.ts b/packages/vite/src/node/plugins/ssrRequireHook.ts index 5dbeb77180cb36..10835bae62e14b 100644 --- a/packages/vite/src/node/plugins/ssrRequireHook.ts +++ b/packages/vite/src/node/plugins/ssrRequireHook.ts @@ -14,6 +14,7 @@ export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null { config.command !== 'build' || !config.resolve.dedupe?.length || config.ssr?.noExternal === true || + config.ssr?.target !== 'node-cjs' || isBuildOutputEsm(config) ) { return null diff --git a/playground/ssr-react/__tests__/serve.ts b/playground/ssr-react/__tests__/serve.ts index 57c09664efdeae..3f1f4f68941414 100644 --- a/playground/ssr-react/__tests__/serve.ts +++ b/playground/ssr-react/__tests__/serve.ts @@ -32,7 +32,7 @@ export async function serve() { outDir: 'dist/server', rollupOptions: { output: { - entryFileNames: 'entry-server.cjs' + entryFileNames: 'entry-server.js' } } } diff --git a/playground/ssr-react/server.js b/playground/ssr-react/server.js index 8445ebc61d88fb..a1aee454acc48c 100644 --- a/playground/ssr-react/server.js +++ b/playground/ssr-react/server.js @@ -69,7 +69,7 @@ export async function createServer( } else { template = indexProd // @ts-ignore - render = (await import('./dist/server/entry-server.cjs')).render + render = (await import('./dist/server/entry-server.js')).render } const context = {} diff --git a/playground/ssr-react/vite.config.js b/playground/ssr-react/vite.config.js index 94d7192a60ccd1..676c52ac687f59 100644 --- a/playground/ssr-react/vite.config.js +++ b/playground/ssr-react/vite.config.js @@ -5,8 +5,5 @@ export default defineConfig({ plugins: [react()], build: { minify: false - }, - ssr: { - target: 'node-cjs' } }) From 2923000d770ecdd908b2a096a2481dbe4ce61408 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Fri, 27 May 2022 11:08:38 +0200 Subject: [PATCH 07/10] fix: externalize only in ssr --- packages/vite/src/node/plugins/index.ts | 2 +- packages/vite/src/node/plugins/ssrRequireHook.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 8c009ff3293885..83970ada2154f5 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -64,7 +64,7 @@ export async function resolvePlugins( asSrc: true, getDepsOptimizer: () => getDepsOptimizer(config), shouldExternalize: - isBuild && config.ssr?.target !== 'node-cjs' + isBuild && config.build.ssr && config.ssr?.target !== 'node-cjs' ? (id) => shouldExternalizeForSSR(id, config) : undefined }), diff --git a/packages/vite/src/node/plugins/ssrRequireHook.ts b/packages/vite/src/node/plugins/ssrRequireHook.ts index 10835bae62e14b..90c0f7421c1447 100644 --- a/packages/vite/src/node/plugins/ssrRequireHook.ts +++ b/packages/vite/src/node/plugins/ssrRequireHook.ts @@ -12,6 +12,7 @@ import { arraify } from '../utils' export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null { if ( config.command !== 'build' || + !config.build.ssr || !config.resolve.dedupe?.length || config.ssr?.noExternal === true || config.ssr?.target !== 'node-cjs' || From d8adf62d4a6e0dd3c081d332ded066f68a8c6908 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 28 May 2022 14:54:14 +0200 Subject: [PATCH 08/10] fix: windows --- packages/vite/src/node/ssr/ssrExternal.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index e40f0cbad9d103..121fd696c56a6e 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -170,7 +170,9 @@ function createIsSsrExternal( return processedIds.get(id) } const external = - isBuiltin(id) || (isConfiguredAsExternal(id) && isPackageEntry(id)) + !id.startsWith('.') && + !path.isAbsolute(id) && + (isBuiltin(id) || (isConfiguredAsExternal(id) && isPackageEntry(id))) processedIds.set(id, external) return external } From ad9f17ec432e878cb6e3041e860def7f0145068b Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 28 May 2022 19:51:12 +0200 Subject: [PATCH 09/10] fix: externalize packages out of node_modules --- packages/vite/src/node/ssr/ssrExternal.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 121fd696c56a6e..348eb72197a21d 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -157,8 +157,7 @@ function createIsSsrExternal( } try { // no main entry, but deep imports may be allowed - const pkgPath = resolveFrom(`${id}/package.json`, root) - if (pkgPath.includes('node_modules')) { + if (resolveFrom(`${id}/package.json`, root)) { return true } } catch {} From 773f2344f1f50516eefaddd47d6eba6597c572e8 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Sat, 28 May 2022 21:18:27 +0200 Subject: [PATCH 10/10] chore: ssr.format --- docs/config/ssr-options.md | 10 +++++++++- docs/vite.config.ts | 2 +- packages/vite/src/node/build.ts | 4 ++-- packages/vite/src/node/config.ts | 12 +++++++++++- packages/vite/src/node/plugins/importAnalysis.ts | 2 +- packages/vite/src/node/plugins/index.ts | 2 +- packages/vite/src/node/plugins/ssrRequireHook.ts | 2 +- packages/vite/src/node/ssr/ssrExternal.ts | 4 ++-- 8 files changed, 28 insertions(+), 10 deletions(-) diff --git a/docs/config/ssr-options.md b/docs/config/ssr-options.md index 4308f5b9b2f50b..83f90b02c92a37 100644 --- a/docs/config/ssr-options.md +++ b/docs/config/ssr-options.md @@ -20,7 +20,15 @@ Prevent listed dependencies from being externalized for SSR. If `true`, no depen ## ssr.target -- **Type:** `'node' | 'webworker' | 'node-cjs'` +- **Type:** `'node' | 'webworker'` - **Default:** `node` Build target for the SSR server. + +## ssr.format + +- **Type:** `'esm' | 'cjs'` +- **Default:** `esm` +- **Experimental** + +Build format for the SSR server. Since Vite v3 the SSR build generates ESM by default. `'cjs'` can be selected to generate a CJS build, but it isn't recommended. The option is left marked as experimental to give users more time to update to ESM. CJS builds requires complex externalization heuristics that aren't present in the ESM format. diff --git a/docs/vite.config.ts b/docs/vite.config.ts index 4493991d9a6cca..3b15cb6f501e73 100644 --- a/docs/vite.config.ts +++ b/docs/vite.config.ts @@ -2,6 +2,6 @@ import { defineConfig } from 'vite' export default defineConfig({ ssr: { - target: 'node-cjs' + format: 'cjs' } }) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index fa8ee91c690026..6bfdff48c3eeb3 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -382,7 +382,7 @@ async function doBuild( // In CJS, we can pass the externals to rollup as is. In ESM, we need to // do it in the resolve plugin so we can add the resolved extension for // deep node_modules imports - if (ssr && config.ssr?.target === 'node-cjs') { + if (ssr && config.ssr?.format === 'cjs') { external = await cjsSsrResolveExternal(config, userExternal) } @@ -421,7 +421,7 @@ async function doBuild( try { const buildOutputOptions = (output: OutputOptions = {}): OutputOptions => { - const cjsSsrBuild = ssr && config.ssr?.target === 'node-cjs' + const cjsSsrBuild = ssr && config.ssr?.format === 'cjs' return { dir: outDir, // Default format is 'es' for regular and for SSR builds diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e4d3ef04211c87..b178ba9825d4b2 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -228,7 +228,9 @@ export interface ExperimentalOptions { importGlobRestoreExtension?: boolean } -export type SSRTarget = 'node' | 'webworker' | 'node-cjs' +export type SSRTarget = 'node' | 'webworker' + +export type SSRFormat = 'esm' | 'cjs' export interface SSROptions { external?: string[] @@ -239,6 +241,14 @@ export interface SSROptions { * Default: 'node' */ target?: SSRTarget + /** + * Define the format for the ssr build. Since Vite v3 the SSR build generates ESM by default. + * `'cjs'` can be selected to generate a CJS build, but it isn't recommended. This option is + * left marked as experimental to give users more time to update to ESM. CJS builds requires + * complex externalization heuristics that aren't present in the ESM format. + * @experimental + */ + format?: SSRFormat } export interface ResolveWorkerOptions { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 0fbe5a86b79665..93da02147ae854 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -362,7 +362,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } // skip ssr external if (ssr) { - if (config.ssr?.target === 'node-cjs') { + if (config.ssr?.format === 'cjs') { if (cjsShouldExternalizeForSSR(specifier, server._ssrExternals)) { continue } diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 83970ada2154f5..8eca908870ae90 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -64,7 +64,7 @@ export async function resolvePlugins( asSrc: true, getDepsOptimizer: () => getDepsOptimizer(config), shouldExternalize: - isBuild && config.build.ssr && config.ssr?.target !== 'node-cjs' + isBuild && config.build.ssr && config.ssr?.format !== 'cjs' ? (id) => shouldExternalizeForSSR(id, config) : undefined }), diff --git a/packages/vite/src/node/plugins/ssrRequireHook.ts b/packages/vite/src/node/plugins/ssrRequireHook.ts index 90c0f7421c1447..6d2efa3c58b183 100644 --- a/packages/vite/src/node/plugins/ssrRequireHook.ts +++ b/packages/vite/src/node/plugins/ssrRequireHook.ts @@ -15,7 +15,7 @@ export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null { !config.build.ssr || !config.resolve.dedupe?.length || config.ssr?.noExternal === true || - config.ssr?.target !== 'node-cjs' || + config.ssr?.format !== 'cjs' || isBuildOutputEsm(config) ) { return null diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 348eb72197a21d..c6be41bd990e01 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -177,8 +177,8 @@ function createIsSsrExternal( } } -// When ssr.format is 'node-cjs', this function is used reverting to the Vite 2.9 era -// SSR externalize heuristics +// When ssr.format is 'cjs', this function is used reverting to the Vite 2.9 +// SSR externalization heuristics function cjsSsrCollectExternals( root: string, preserveSymlinks: boolean | undefined,