From f8c92d1f4c1bddb6c29cfd4753079157c4d96922 Mon Sep 17 00:00:00 2001 From: patak Date: Sun, 29 May 2022 15:09:26 +0200 Subject: [PATCH] feat: default esm SSR build, simplified externalization (#8348) --- docs/config/ssr-options.md | 8 ++ docs/vite.config.ts | 7 ++ packages/vite/src/node/build.ts | 84 +++++++------ packages/vite/src/node/config.ts | 12 +- .../vite/src/node/plugins/importAnalysis.ts | 14 ++- packages/vite/src/node/plugins/index.ts | 7 +- packages/vite/src/node/plugins/resolve.ts | 44 +++++-- .../vite/src/node/plugins/ssrRequireHook.ts | 2 + packages/vite/src/node/server/index.ts | 4 +- packages/vite/src/node/ssr/ssrExternal.ts | 115 ++++++++++++++++-- playground/ssr-deps/__tests__/serve.ts | 2 +- playground/ssr-deps/package.json | 1 + playground/ssr-deps/server.js | 18 +-- playground/ssr-html/__tests__/serve.ts | 2 +- playground/ssr-html/package.json | 1 + playground/ssr-html/server.js | 19 ++- playground/ssr-pug/__tests__/serve.ts | 2 +- playground/ssr-pug/package.json | 1 + playground/ssr-pug/server.js | 19 +-- playground/ssr-react/__tests__/serve.ts | 9 +- playground/ssr-react/package.json | 1 + playground/ssr-react/prerender.js | 8 +- playground/ssr-react/server.js | 25 ++-- playground/ssr-react/vite.config.js | 10 +- playground/ssr-vue/__tests__/serve.ts | 2 +- playground/ssr-vue/__tests__/ssr-vue.spec.ts | 12 +- playground/ssr-vue/package.json | 1 + playground/ssr-vue/prerender.js | 8 +- playground/ssr-vue/server.js | 25 ++-- 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 +- 34 files changed, 344 insertions(+), 156 deletions(-) create mode 100644 docs/vite.config.ts diff --git a/docs/config/ssr-options.md b/docs/config/ssr-options.md index 7d828c8c989679..83f90b02c92a37 100644 --- a/docs/config/ssr-options.md +++ b/docs/config/ssr-options.md @@ -24,3 +24,11 @@ Prevent listed dependencies from being externalized for SSR. If `true`, no depen - **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 new file mode 100644 index 00000000000000..3b15cb6f501e73 --- /dev/null +++ b/docs/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + ssr: { + format: 'cjs' + } +}) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 891c8434c03084..6bfdff48c3eeb3 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -31,7 +31,10 @@ 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 +} from './ssr/ssrExternal' import { ssrManifestPlugin } from './ssr/ssrManifestPlugin' import type { DepOptimizationMetadata } from './optimizer' import { @@ -342,7 +345,6 @@ async function doBuild( const config = await resolveConfig(inlineConfig, 'build', 'production') const options = config.build const ssr = !!options.ssr - const esm = config.ssr?.format === 'es' || !ssr const libOptions = options.lib config.logger.info( @@ -374,27 +376,14 @@ 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 - ) + + // 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?.format === 'cjs') { + external = await cjsSsrResolveExternal(config, userExternal) } if (isDepsOptimizerEnabled(config) && !ssr) { @@ -432,10 +421,12 @@ async function doBuild( try { const buildOutputOptions = (output: OutputOptions = {}): OutputOptions => { + const cjsSsrBuild = ssr && config.ssr?.format === 'cjs' return { dir: outDir, - format: esm ? 'es' : 'cjs', - exports: esm ? 'auto' : 'named', + // 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', @@ -697,26 +688,51 @@ export function onRollupWarning( } } -function resolveExternal( - ssrExternals: string[], +async function cjsSsrResolveExternal( + config: ResolvedConfig, user: ExternalOption | undefined -): ExternalOption { +): Promise { + // 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) + 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 3d019fa6f4ac02..d80f5a60583abc 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -230,6 +230,8 @@ export interface ExperimentalOptions { export type SSRTarget = 'node' | 'webworker' +export type SSRFormat = 'esm' | 'cjs' + export interface SSROptions { external?: string[] noExternal?: string | RegExp | (string | RegExp)[] | true @@ -239,12 +241,14 @@ export interface SSROptions { * Default: 'node' */ target?: SSRTarget - /** - * Define the module format for the ssr build. - * Default: 'cjs' + * 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?: 'es' | 'cjs' + format?: SSRFormat } export interface ResolveWorkerOptions { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 8d16668b55d643..5d2fce4ad13e02 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 { shouldExternalizeForSSR } from '../ssr/ssrExternal' +import { + cjsShouldExternalizeForSSR, + shouldExternalizeForSSR +} from '../ssr/ssrExternal' import { transformRequest } from '../server/transformRequest' import { getDepsCacheDir, @@ -362,10 +365,11 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } // skip ssr external if (ssr) { - if ( - server._ssrExternals && - shouldExternalizeForSSR(specifier, server._ssrExternals) - ) { + if (config.ssr?.format === 'cjs') { + if (cjsShouldExternalizeForSSR(specifier, server._ssrExternals)) { + continue + } + } else if (shouldExternalizeForSSR(specifier, config)) { continue } if (isBuiltin(specifier)) { diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 4d717d5e319840..8eca908870ae90 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.build.ssr && config.ssr?.format !== '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..6d2efa3c58b183 100644 --- a/packages/vite/src/node/plugins/ssrRequireHook.ts +++ b/packages/vite/src/node/plugins/ssrRequireHook.ts @@ -12,8 +12,10 @@ 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?.format !== 'cjs' || isBuildOutputEsm(config) ) { return null diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 0e6cb779ba2b9c..983fa05efec6e0 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 @@ -330,7 +330,7 @@ export async function createServer( ...Object.keys(depsOptimizer.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 9b990b2fc0e4d3..512ec038a21244 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[]): 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,98 @@ 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< + ResolvedConfig, + (id: string) => 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 + if (resolveFrom(`${id}/package.json`, root)) { + return true + } + } catch {} + return false + } + + return (id: string) => { + if (processedIds.has(id)) { + return processedIds.get(id) + } + const external = + !id.startsWith('.') && + !path.isAbsolute(id) && + (isBuiltin(id) || (isConfiguredAsExternal(id) && isPackageEntry(id))) + processedIds.set(id, external) + return external + } +} + +// 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, ssrExternals: Set, @@ -192,14 +284,23 @@ 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 diff --git a/playground/ssr-deps/__tests__/serve.ts b/playground/ssr-deps/__tests__/serve.ts index d50cb56328b17c..942bb50aba9b89 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(): Promise<{ close(): Promise }> { 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..f205255320cfe2 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,9 @@ 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 +68,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 8335d2ea4b7b10..9977b6518fda37 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(): Promise<{ close(): Promise }> { 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..facc502c9b4d07 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,9 @@ 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 +95,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 f144844048b521..5fcb8c931267ff 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(): Promise<{ close(): Promise }> { 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..034540e81d47cf 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,9 @@ 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 +75,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 b18041d672b13c..c4ec57191ed74c 100644 --- a/playground/ssr-react/__tests__/serve.ts +++ b/playground/ssr-react/__tests__/serve.ts @@ -29,14 +29,19 @@ export async function serve(): Promise<{ close(): Promise }> { build: { target: 'esnext', ssr: 'src/entry-server.jsx', - outDir: 'dist/server' + outDir: 'dist/server', + rollupOptions: { + output: { + entryFileNames: 'entry-server.js' + } + } } }) } 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..a1aee454acc48c 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,9 @@ 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 +48,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 +69,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 context = {} @@ -96,6 +100,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..676c52ac687f59 100644 --- a/playground/ssr-react/vite.config.js +++ b/playground/ssr-react/vite.config.js @@ -1,11 +1,9 @@ -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 } -} +}) diff --git a/playground/ssr-vue/__tests__/serve.ts b/playground/ssr-vue/__tests__/serve.ts index 037761cb26dfb4..3f610ac719adfa 100644 --- a/playground/ssr-vue/__tests__/serve.ts +++ b/playground/ssr-vue/__tests__/serve.ts @@ -38,7 +38,7 @@ export async function serve(): Promise<{ close(): Promise }> { 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 6b744386124718..a70586a5427df5 100644 --- a/playground/ssr-vue/__tests__/ssr-vue.spec.ts +++ b/playground/ssr-vue/__tests__/ssr-vue.spec.ts @@ -186,10 +186,14 @@ test.runIf(isBuild)('dynamic css file should be preloaded', async () => { const re = /link rel="modulepreload".*?href="\/test\/assets\/(Home\.\w{8}\.js)"/ const filename = re.exec(homeHtml)[1] - const manifest = require(resolve( - process.cwd(), - './playground-temp/ssr-vue/dist/client/ssr-manifest.json' - )) + 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 7c69290ccaa66a..ab1270f9024878 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 22077e1f7ae8bc..5e4e6718bff7bf 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,9 @@ async function createServer( */ let vite if (!isProd) { - vite = await require('vite').createServer({ + vite = await ( + await import('vite') + ).createServer({ base: '/test/', root, logLevel: isTest ? 'error' : 'info', @@ -48,10 +52,10 @@ 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( '/test/', - require('serve-static')(resolve('dist/client'), { + (await import('serve-static')).default(resolve('dist/client'), { index: false }) ) @@ -70,7 +74,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) @@ -97,6 +101,3 @@ if (!isTest) { }) ) } - -// for test use -exports.createServer = createServer diff --git a/playground/ssr-vue/vite.config.js b/playground/ssr-vue/vite.config.js index dd3ff761d3476e..d57a11972d449e 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({ base: '/test/', plugins: [ vuePlugin(), @@ -56,4 +54,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 75304172bdff4c..b104c560fab290 100644 --- a/playground/ssr-webworker/__tests__/serve.ts +++ b/playground/ssr-webworker/__tests__/serve.ts @@ -25,7 +25,7 @@ export async function serve(): Promise<{ close(): Promise }> { } }) - 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