From 0d4d6c0e1a0d275babcb656351454c58fe8dd5bf Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 8 Apr 2025 11:14:42 +0800 Subject: [PATCH 01/76] feat: rolldown full bundle mode --- packages/vite/src/node/build.ts | 57 +- packages/vite/src/node/cli.ts | 23 +- packages/vite/src/node/plugins/index.ts | 37 +- packages/vite/src/node/server/hmr.ts | 588 ++++++------ packages/vite/src/node/server/index.ts | 401 ++++---- .../src/node/server/middlewares/indexHtml.ts | 879 +++++++++--------- packages/vite/src/node/server/warmup.ts | 30 +- 7 files changed, 1046 insertions(+), 969 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 476b01ec8f30d3..d36cc667820742 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -89,6 +89,7 @@ import { import type { Plugin } from './plugin' import type { RollupPluginHooks } from './typeUtils' import { buildOxcPlugin } from './plugins/oxc' +import type { ViteDevServer } from './server' export interface BuildEnvironmentOptions { /** @@ -555,20 +556,21 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{ export async function build( inlineConfig: InlineConfig = {}, ): Promise { - const builder = await createBuilder(inlineConfig, true) + const builder = await createBuilder(inlineConfig, true, 'build') const environment = Object.values(builder.environments)[0] if (!environment) throw new Error('No environment found') return builder.build(environment) } function resolveConfigToBuild( + command: 'build' | 'serve', inlineConfig: InlineConfig = {}, patchConfig?: (config: ResolvedConfig) => void, patchPlugins?: (resolvedPlugins: Plugin[]) => void, ): Promise { return resolveConfig( inlineConfig, - 'build', + command, 'production', 'production', false, @@ -582,6 +584,7 @@ function resolveConfigToBuild( **/ async function buildEnvironment( environment: BuildEnvironment, + server?: ViteDevServer ): Promise { const { root, packageCache } = environment.config const options = environment.config.build @@ -667,6 +670,12 @@ async function buildEnvironment( ...options.rollupOptions.moduleTypes, '.css': 'js', }, + experimental: { + hmr: server ? { + host: server._currentServerHost!, + port: server._currentServerPort!, + } : false, + } } /** @@ -902,6 +911,29 @@ async function buildEnvironment( logger.info( `${colors.green(`✓ built in ${displayTime(Date.now() - startTime)}`)}`, ) + + if (server) { + for(const output of res) { + for (const outputFile of output.output) { + server.memoryFiles[outputFile.fileName] = outputFile.type === 'chunk' ? outputFile.code : outputFile.source; + } + } + server.watcher.on('change', async (file) => { + const patch = await bundle!.generateHmrPatch([file]); + if (patch) { + const url = `${Date.now()}.js`; + server.memoryFiles[url] = patch; + // TODO(underfin): fix ws msg typing + // @ts-expect-error + server.ws.send({ + type: 'update', + url + }) + } + }) + // server.watcher = watcher + } + return Array.isArray(outputs) ? res : res[0] } catch (e) { enhanceRollupError(e) @@ -1628,9 +1660,10 @@ export class BuildEnvironment extends BaseEnvironment { export interface ViteBuilder { environments: Record config: ResolvedConfig - buildApp(): Promise + buildApp(server?: ViteDevServer): Promise build( environment: BuildEnvironment, + server?: ViteDevServer ): Promise } @@ -1649,12 +1682,12 @@ export interface BuilderOptions { * @experimental */ sharedPlugins?: boolean - buildApp?: (builder: ViteBuilder) => Promise + buildApp?: (builder: ViteBuilder, server?: ViteDevServer) => Promise } -async function defaultBuildApp(builder: ViteBuilder): Promise { +async function defaultBuildApp(builder: ViteBuilder, server?: ViteDevServer): Promise { for (const environment of Object.values(builder.environments)) { - await builder.build(environment) + await builder.build(environment, server) } } @@ -1683,6 +1716,7 @@ export type ResolvedBuilderOptions = Required export async function createBuilder( inlineConfig: InlineConfig = {}, useLegacyBuilder: null | boolean = false, + command: 'build' | 'serve', ): Promise { const patchConfig = (resolved: ResolvedConfig) => { if (!(useLegacyBuilder ?? !resolved.builder)) return @@ -1696,7 +1730,7 @@ export async function createBuilder( ...resolved.environments[environmentName].build, } } - const config = await resolveConfigToBuild(inlineConfig, patchConfig) + const config = await resolveConfigToBuild(command, inlineConfig, patchConfig) useLegacyBuilder ??= !config.builder const configBuilder = config.builder ?? resolveBuilderOptions({})! @@ -1705,11 +1739,11 @@ export async function createBuilder( const builder: ViteBuilder = { environments, config, - async buildApp() { - return configBuilder.buildApp(builder) + async buildApp(server?: ViteDevServer) { + return configBuilder.buildApp(builder, server) }, - async build(environment: BuildEnvironment) { - return buildEnvironment(environment) + async build(environment: BuildEnvironment, server?: ViteDevServer) { + return buildEnvironment(environment, server) }, } @@ -1759,6 +1793,7 @@ export async function createBuilder( } } environmentConfig = await resolveConfigToBuild( + command, inlineConfig, patchConfig, patchPlugins, diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 29401d0e588343..8f00bb6f018a44 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -176,7 +176,8 @@ cli '--force', `[boolean] force the optimizer to ignore the cache and re-bundle`, ) - .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => { + // TODO(underfin): Consider how to merge the build option into dev command. + .action(async (root: string, options: BuildEnvironmentOptions & BuilderCLIOptions & ServerOptions & GlobalCLIOptions) => { filterDuplicateOptions(options) // output structure is preserved even after bundling so require() // is ok here @@ -198,6 +199,24 @@ cli throw new Error('HTTP server not available') } + const { createBuilder } = await import('./build') + + const buildOptions: BuildEnvironmentOptions = cleanBuilderCLIOptions(options) + + const inlineConfig: InlineConfig = { + root, + base: options.base, + mode: options.mode, + configFile: options.config, + configLoader: options.configLoader, + logLevel: options.logLevel, + clearScreen: options.clearScreen, + build: buildOptions, + ...(options.app ? { builder: {} } : {}), + } + const builder = await createBuilder(inlineConfig, null, 'serve') + await builder.buildApp(server) + await server.listen() const info = server.config.logger.info @@ -322,7 +341,7 @@ cli build: buildOptions, ...(options.app ? { builder: {} } : {}), } - const builder = await createBuilder(inlineConfig, null) + const builder = await createBuilder(inlineConfig, null, 'build') await builder.buildApp() } catch (e) { createLogger(options.logLevel).error( diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index ae1af8e3034a76..fdb496104867b0 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -23,16 +23,18 @@ import { watchPackageDataPlugin } from '../packages' import { normalizePath } from '../utils' import { jsonPlugin } from './json' import { oxcResolvePlugin, resolvePlugin } from './resolve' -import { optimizedDepsPlugin } from './optimizedDeps' -import { importAnalysisPlugin } from './importAnalysis' -import { cssAnalysisPlugin, cssPlugin, cssPostPlugin } from './css' +// import { optimizedDepsPlugin } from './optimizedDeps' +// import { importAnalysisPlugin } from './importAnalysis' +import { + // cssAnalysisPlugin, + cssPlugin, cssPostPlugin } from './css' import { assetPlugin } from './asset' -import { clientInjectionsPlugin } from './clientInjections' +// import { clientInjectionsPlugin } from './clientInjections' import { buildHtmlPlugin, htmlInlineProxyPlugin } from './html' import { wasmFallbackPlugin, wasmHelperPlugin } from './wasm' import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill' import { webWorkerPlugin } from './worker' -import { preAliasPlugin } from './preAlias' +// import { preAliasPlugin } from './preAlias' import { definePlugin } from './define' import { workerImportMetaUrlPlugin } from './workerImportMetaUrl' import { assetImportMetaUrlPlugin } from './assetImportMetaUrl' @@ -53,7 +55,7 @@ export async function resolvePlugins( normalPlugins: Plugin[], postPlugins: Plugin[], ): Promise { - const isBuild = config.command === 'build' + const isBuild = true const isWorker = config.isWorker const buildPlugins = isBuild ? await (await import('../build')).resolveBuildPlugins(config) @@ -62,9 +64,9 @@ export async function resolvePlugins( const enableNativePlugin = config.experimental.enableNativePlugin return [ - !isBuild ? optimizedDepsPlugin() : null, + // !isBuild ? optimizedDepsPlugin() : null, !isWorker ? watchPackageDataPlugin(config.packageCache) : null, - !isBuild ? preAliasPlugin(config) : null, + // !isBuild ? preAliasPlugin(config) : null, enableNativePlugin === true ? nativeAliasPlugin({ entries: config.resolve.alias.map((item) => { @@ -172,7 +174,8 @@ export async function resolvePlugins( : wasmFallbackPlugin(), definePlugin(config), cssPostPlugin(config), - isBuild && buildHtmlPlugin(config), + // isBuild && + buildHtmlPlugin(config), workerImportMetaUrlPlugin(config), assetImportMetaUrlPlugin(config), ...buildPlugins.pre, @@ -190,14 +193,14 @@ export async function resolvePlugins( ...buildPlugins.post, - // internal server-only plugins are always applied after everything else - ...(isBuild - ? [] - : [ - clientInjectionsPlugin(config), - cssAnalysisPlugin(config), - importAnalysisPlugin(config), - ]), + // // internal server-only plugins are always applied after everything else + // ...(isBuild + // ? [] + // : [ + // clientInjectionsPlugin(config), + // cssAnalysisPlugin(config), + // importAnalysisPlugin(config), + // ]), ].filter(Boolean) as Plugin[] } diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 4f3154ad615547..44d2eb32840eb8 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -1,4 +1,4 @@ -import fsp from 'node:fs/promises' +// import fsp from 'node:fs/promises' import path from 'node:path' import { EventEmitter } from 'node:events' import colors from 'picocolors' @@ -9,32 +9,34 @@ import type { InvokeResponseData, InvokeSendData, } from '../../shared/invokeMethods' -import { CLIENT_DIR } from '../constants' -import { createDebugger, normalizePath } from '../utils' +// import { CLIENT_DIR } from '../constants' +import { createDebugger, + // normalizePath +} from '../utils' import type { InferCustomEventPayload, ViteDevServer } from '..' -import { getHookHandler } from '../plugins' +// import { getHookHandler } from '../plugins' import { isCSSRequest } from '../plugins/css' import { isExplicitImportRequired } from '../plugins/importAnalysis' -import { getEnvFilesForMode } from '../env' -import type { Environment } from '../environment' +// import { getEnvFilesForMode } from '../env' +// import type { Environment } from '../environment' import { withTrailingSlash, wrapId } from '../../shared/utils' import type { Plugin } from '../plugin' -import { - ignoreDeprecationWarnings, - warnFutureDeprecation, -} from '../deprecations' +// import { +// ignoreDeprecationWarnings, +// warnFutureDeprecation, +// } from '../deprecations' import type { EnvironmentModuleNode } from './moduleGraph' import type { ModuleNode } from './mixedModuleGraph' import type { DevEnvironment } from './environment' -import { prepareError } from './middlewares/error' +// import { prepareError } from './middlewares/error' import type { HttpServer } from '.' -import { restartServerWithUrls } from '.' +// import { restartServerWithUrls } from '.' export const debugHmr = createDebugger('vite:hmr') const whitespaceRE = /\s/ -const normalizedClientDir = normalizePath(CLIENT_DIR) +// const normalizedClientDir = normalizePath(CLIENT_DIR) export interface HmrOptions { protocol?: string @@ -362,268 +364,268 @@ export function getSortedPluginsByHotUpdateHook( return sortedPlugins } -const sortedHotUpdatePluginsCache = new WeakMap() -function getSortedHotUpdatePlugins(environment: Environment): Plugin[] { - let sortedPlugins = sortedHotUpdatePluginsCache.get(environment) - if (!sortedPlugins) { - sortedPlugins = getSortedPluginsByHotUpdateHook(environment.plugins) - sortedHotUpdatePluginsCache.set(environment, sortedPlugins) - } - return sortedPlugins -} - -export async function handleHMRUpdate( - type: 'create' | 'delete' | 'update', - file: string, - server: ViteDevServer, -): Promise { - const { config } = server - const mixedModuleGraph = ignoreDeprecationWarnings(() => server.moduleGraph) - - const environments = Object.values(server.environments) - const shortFile = getShortName(file, config.root) - - const isConfig = file === config.configFile - const isConfigDependency = config.configFileDependencies.some( - (name) => file === name, - ) - - const isEnv = - config.envDir !== false && - getEnvFilesForMode(config.mode, config.envDir).includes(file) - if (isConfig || isConfigDependency || isEnv) { - // auto restart server - debugHmr?.(`[config change] ${colors.dim(shortFile)}`) - config.logger.info( - colors.green( - `${normalizePath( - path.relative(process.cwd(), file), - )} changed, restarting server...`, - ), - { clear: true, timestamp: true }, - ) - try { - await restartServerWithUrls(server) - } catch (e) { - config.logger.error(colors.red(e)) - } - return - } - - debugHmr?.(`[file change] ${colors.dim(shortFile)}`) - - // (dev only) the client itself cannot be hot updated. - if (file.startsWith(withTrailingSlash(normalizedClientDir))) { - environments.forEach(({ hot }) => - hot.send({ - type: 'full-reload', - path: '*', - triggeredBy: path.resolve(config.root, file), - }), - ) - return - } - - const timestamp = Date.now() - const contextMeta = { - type, - file, - timestamp, - read: () => readModifiedFile(file), - server, - } - const hotMap = new Map< - Environment, - { options: HotUpdateOptions; error?: Error } - >() - - for (const environment of Object.values(server.environments)) { - const mods = new Set(environment.moduleGraph.getModulesByFile(file)) - if (type === 'create') { - for (const mod of environment.moduleGraph._hasResolveFailedErrorModules) { - mods.add(mod) - } - } - const options = { - ...contextMeta, - modules: [...mods], - // later on hotUpdate will be called for each runtime with a new HotUpdateOptions - environment, - } - hotMap.set(environment, { options }) - } - - const mixedMods = new Set(mixedModuleGraph.getModulesByFile(file)) - - const mixedHmrContext: HmrContext = { - ...contextMeta, - modules: [...mixedMods], - } - - const clientEnvironment = server.environments.client - const ssrEnvironment = server.environments.ssr - const clientContext = { environment: clientEnvironment } - const clientHotUpdateOptions = hotMap.get(clientEnvironment)!.options - const ssrHotUpdateOptions = hotMap.get(ssrEnvironment)?.options - try { - for (const plugin of getSortedHotUpdatePlugins( - server.environments.client, - )) { - if (plugin.hotUpdate) { - const filteredModules = await getHookHandler(plugin.hotUpdate).call( - clientContext, - clientHotUpdateOptions, - ) - if (filteredModules) { - clientHotUpdateOptions.modules = filteredModules - // Invalidate the hmrContext to force compat modules to be updated - mixedHmrContext.modules = mixedHmrContext.modules.filter( - (mixedMod) => - filteredModules.some((mod) => mixedMod.id === mod.id) || - ssrHotUpdateOptions?.modules.some( - (ssrMod) => ssrMod.id === mixedMod.id, - ), - ) - mixedHmrContext.modules.push( - ...filteredModules - .filter( - (mod) => - !mixedHmrContext.modules.some( - (mixedMod) => mixedMod.id === mod.id, - ), - ) - .map((mod) => - mixedModuleGraph.getBackwardCompatibleModuleNode(mod), - ), - ) - } - } else if (type === 'update') { - warnFutureDeprecation( - config, - 'removePluginHookHandleHotUpdate', - `Used in plugin "${plugin.name}".`, - false, - ) - // later on, we'll need: if (runtime === 'client') - // Backward compatibility with mixed client and ssr moduleGraph - const filteredModules = await getHookHandler(plugin.handleHotUpdate!)( - mixedHmrContext, - ) - if (filteredModules) { - mixedHmrContext.modules = filteredModules - clientHotUpdateOptions.modules = - clientHotUpdateOptions.modules.filter((mod) => - filteredModules.some((mixedMod) => mod.id === mixedMod.id), - ) - clientHotUpdateOptions.modules.push( - ...(filteredModules - .filter( - (mixedMod) => - !clientHotUpdateOptions.modules.some( - (mod) => mod.id === mixedMod.id, - ), - ) - .map((mixedMod) => mixedMod._clientModule) - .filter(Boolean) as EnvironmentModuleNode[]), - ) - if (ssrHotUpdateOptions) { - ssrHotUpdateOptions.modules = ssrHotUpdateOptions.modules.filter( - (mod) => - filteredModules.some((mixedMod) => mod.id === mixedMod.id), - ) - ssrHotUpdateOptions.modules.push( - ...(filteredModules - .filter( - (mixedMod) => - !ssrHotUpdateOptions.modules.some( - (mod) => mod.id === mixedMod.id, - ), - ) - .map((mixedMod) => mixedMod._ssrModule) - .filter(Boolean) as EnvironmentModuleNode[]), - ) - } - } - } - } - } catch (error) { - hotMap.get(server.environments.client)!.error = error - } - - for (const environment of Object.values(server.environments)) { - if (environment.name === 'client') continue - const hot = hotMap.get(environment)! - const environmentThis = { environment } - try { - for (const plugin of getSortedHotUpdatePlugins(environment)) { - if (plugin.hotUpdate) { - const filteredModules = await getHookHandler(plugin.hotUpdate).call( - environmentThis, - hot.options, - ) - if (filteredModules) { - hot.options.modules = filteredModules - } - } - } - } catch (error) { - hot.error = error - } - } - - async function hmr(environment: DevEnvironment) { - try { - const { options, error } = hotMap.get(environment)! - if (error) { - throw error - } - if (!options.modules.length) { - // html file cannot be hot updated - if (file.endsWith('.html') && environment.name === 'client') { - environment.logger.info( - colors.green(`page reload `) + colors.dim(shortFile), - { - clear: true, - timestamp: true, - }, - ) - environment.hot.send({ - type: 'full-reload', - path: config.server.middlewareMode - ? '*' - : '/' + normalizePath(path.relative(config.root, file)), - }) - } else { - // loaded but not in the module graph, probably not js - debugHmr?.( - `(${environment.name}) [no modules matched] ${colors.dim(shortFile)}`, - ) - } - return - } - - updateModules(environment, shortFile, options.modules, timestamp) - } catch (err) { - environment.hot.send({ - type: 'error', - err: prepareError(err), - }) - } - } - - const hotUpdateEnvironments = - server.config.server.hotUpdateEnvironments ?? - ((server, hmr) => { - // Run HMR in parallel for all environments by default - return Promise.all( - Object.values(server.environments).map((environment) => - hmr(environment), - ), - ) - }) - - await hotUpdateEnvironments(server, hmr) -} +// const sortedHotUpdatePluginsCache = new WeakMap() +// function getSortedHotUpdatePlugins(environment: Environment): Plugin[] { +// let sortedPlugins = sortedHotUpdatePluginsCache.get(environment) +// if (!sortedPlugins) { +// sortedPlugins = getSortedPluginsByHotUpdateHook(environment.plugins) +// sortedHotUpdatePluginsCache.set(environment, sortedPlugins) +// } +// return sortedPlugins +// } + +// export async function handleHMRUpdate( +// type: 'create' | 'delete' | 'update', +// file: string, +// server: ViteDevServer, +// ): Promise { +// const { config } = server +// const mixedModuleGraph = ignoreDeprecationWarnings(() => server.moduleGraph) + +// const environments = Object.values(server.environments) +// const shortFile = getShortName(file, config.root) + +// const isConfig = file === config.configFile +// const isConfigDependency = config.configFileDependencies.some( +// (name) => file === name, +// ) + +// const isEnv = +// config.envDir !== false && +// getEnvFilesForMode(config.mode, config.envDir).includes(file) +// if (isConfig || isConfigDependency || isEnv) { +// // auto restart server +// debugHmr?.(`[config change] ${colors.dim(shortFile)}`) +// config.logger.info( +// colors.green( +// `${normalizePath( +// path.relative(process.cwd(), file), +// )} changed, restarting server...`, +// ), +// { clear: true, timestamp: true }, +// ) +// try { +// await restartServerWithUrls(server) +// } catch (e) { +// config.logger.error(colors.red(e)) +// } +// return +// } + +// debugHmr?.(`[file change] ${colors.dim(shortFile)}`) + +// // (dev only) the client itself cannot be hot updated. +// if (file.startsWith(withTrailingSlash(normalizedClientDir))) { +// environments.forEach(({ hot }) => +// hot.send({ +// type: 'full-reload', +// path: '*', +// triggeredBy: path.resolve(config.root, file), +// }), +// ) +// return +// } + +// const timestamp = Date.now() +// const contextMeta = { +// type, +// file, +// timestamp, +// read: () => readModifiedFile(file), +// server, +// } +// const hotMap = new Map< +// Environment, +// { options: HotUpdateOptions; error?: Error } +// >() + +// for (const environment of Object.values(server.environments)) { +// const mods = new Set(environment.moduleGraph.getModulesByFile(file)) +// if (type === 'create') { +// for (const mod of environment.moduleGraph._hasResolveFailedErrorModules) { +// mods.add(mod) +// } +// } +// const options = { +// ...contextMeta, +// modules: [...mods], +// // later on hotUpdate will be called for each runtime with a new HotUpdateOptions +// environment, +// } +// hotMap.set(environment, { options }) +// } + +// const mixedMods = new Set(mixedModuleGraph.getModulesByFile(file)) + +// const mixedHmrContext: HmrContext = { +// ...contextMeta, +// modules: [...mixedMods], +// } + +// const clientEnvironment = server.environments.client +// const ssrEnvironment = server.environments.ssr +// const clientContext = { environment: clientEnvironment } +// const clientHotUpdateOptions = hotMap.get(clientEnvironment)!.options +// const ssrHotUpdateOptions = hotMap.get(ssrEnvironment)?.options +// try { +// for (const plugin of getSortedHotUpdatePlugins( +// server.environments.client, +// )) { +// if (plugin.hotUpdate) { +// const filteredModules = await getHookHandler(plugin.hotUpdate).call( +// clientContext, +// clientHotUpdateOptions, +// ) +// if (filteredModules) { +// clientHotUpdateOptions.modules = filteredModules +// // Invalidate the hmrContext to force compat modules to be updated +// mixedHmrContext.modules = mixedHmrContext.modules.filter( +// (mixedMod) => +// filteredModules.some((mod) => mixedMod.id === mod.id) || +// ssrHotUpdateOptions?.modules.some( +// (ssrMod) => ssrMod.id === mixedMod.id, +// ), +// ) +// mixedHmrContext.modules.push( +// ...filteredModules +// .filter( +// (mod) => +// !mixedHmrContext.modules.some( +// (mixedMod) => mixedMod.id === mod.id, +// ), +// ) +// .map((mod) => +// mixedModuleGraph.getBackwardCompatibleModuleNode(mod), +// ), +// ) +// } +// } else if (type === 'update') { +// warnFutureDeprecation( +// config, +// 'removePluginHookHandleHotUpdate', +// `Used in plugin "${plugin.name}".`, +// false, +// ) +// // later on, we'll need: if (runtime === 'client') +// // Backward compatibility with mixed client and ssr moduleGraph +// const filteredModules = await getHookHandler(plugin.handleHotUpdate!)( +// mixedHmrContext, +// ) +// if (filteredModules) { +// mixedHmrContext.modules = filteredModules +// clientHotUpdateOptions.modules = +// clientHotUpdateOptions.modules.filter((mod) => +// filteredModules.some((mixedMod) => mod.id === mixedMod.id), +// ) +// clientHotUpdateOptions.modules.push( +// ...(filteredModules +// .filter( +// (mixedMod) => +// !clientHotUpdateOptions.modules.some( +// (mod) => mod.id === mixedMod.id, +// ), +// ) +// .map((mixedMod) => mixedMod._clientModule) +// .filter(Boolean) as EnvironmentModuleNode[]), +// ) +// if (ssrHotUpdateOptions) { +// ssrHotUpdateOptions.modules = ssrHotUpdateOptions.modules.filter( +// (mod) => +// filteredModules.some((mixedMod) => mod.id === mixedMod.id), +// ) +// ssrHotUpdateOptions.modules.push( +// ...(filteredModules +// .filter( +// (mixedMod) => +// !ssrHotUpdateOptions.modules.some( +// (mod) => mod.id === mixedMod.id, +// ), +// ) +// .map((mixedMod) => mixedMod._ssrModule) +// .filter(Boolean) as EnvironmentModuleNode[]), +// ) +// } +// } +// } +// } +// } catch (error) { +// hotMap.get(server.environments.client)!.error = error +// } + +// for (const environment of Object.values(server.environments)) { +// if (environment.name === 'client') continue +// const hot = hotMap.get(environment)! +// const environmentThis = { environment } +// try { +// for (const plugin of getSortedHotUpdatePlugins(environment)) { +// if (plugin.hotUpdate) { +// const filteredModules = await getHookHandler(plugin.hotUpdate).call( +// environmentThis, +// hot.options, +// ) +// if (filteredModules) { +// hot.options.modules = filteredModules +// } +// } +// } +// } catch (error) { +// hot.error = error +// } +// } + +// async function hmr(environment: DevEnvironment) { +// try { +// const { options, error } = hotMap.get(environment)! +// if (error) { +// throw error +// } +// if (!options.modules.length) { +// // html file cannot be hot updated +// if (file.endsWith('.html') && environment.name === 'client') { +// environment.logger.info( +// colors.green(`page reload `) + colors.dim(shortFile), +// { +// clear: true, +// timestamp: true, +// }, +// ) +// environment.hot.send({ +// type: 'full-reload', +// path: config.server.middlewareMode +// ? '*' +// : '/' + normalizePath(path.relative(config.root, file)), +// }) +// } else { +// // loaded but not in the module graph, probably not js +// debugHmr?.( +// `(${environment.name}) [no modules matched] ${colors.dim(shortFile)}`, +// ) +// } +// return +// } + +// updateModules(environment, shortFile, options.modules, timestamp) +// } catch (err) { +// environment.hot.send({ +// type: 'error', +// err: prepareError(err), +// }) +// } +// } + +// const hotUpdateEnvironments = +// server.config.server.hotUpdateEnvironments ?? +// ((server, hmr) => { +// // Run HMR in parallel for all environments by default +// return Promise.all( +// Object.values(server.environments).map((environment) => +// hmr(environment), +// ), +// ) +// }) + +// await hotUpdateEnvironments(server, hmr) +// } type HasDeadEnd = string | boolean @@ -1097,24 +1099,24 @@ function error(pos: number) { // vitejs/vite#610 when hot-reloading Vue files, we read immediately on file // change event and sometimes this can be too early and get an empty buffer. // Poll until the file's modified time has changed before reading again. -async function readModifiedFile(file: string): Promise { - const content = await fsp.readFile(file, 'utf-8') - if (!content) { - const mtime = (await fsp.stat(file)).mtimeMs - - for (let n = 0; n < 10; n++) { - await new Promise((r) => setTimeout(r, 10)) - const newMtime = (await fsp.stat(file)).mtimeMs - if (newMtime !== mtime) { - break - } - } - - return await fsp.readFile(file, 'utf-8') - } else { - return content - } -} +// async function readModifiedFile(file: string): Promise { +// const content = await fsp.readFile(file, 'utf-8') +// if (!content) { +// const mtime = (await fsp.stat(file)).mtimeMs + +// for (let n = 0; n < 10; n++) { +// await new Promise((r) => setTimeout(r, 10)) +// const newMtime = (await fsp.stat(file)).mtimeMs +// if (newMtime !== mtime) { +// break +// } +// } + +// return await fsp.readFile(file, 'utf-8') +// } else { +// return content +// } +// } export type ServerHotChannelApi = { innerEmitter: EventEmitter diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 63c338e4f9039f..d8508fb1afdc10 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -13,7 +13,6 @@ import chokidar from 'chokidar' import type { FSWatcher, WatchOptions } from 'dep-types/chokidar' import type { Connect } from 'dep-types/connect' import launchEditorMiddleware from 'launch-editor-middleware' -import type { SourceMap } from 'rolldown' import type { ModuleRunner } from 'vite/module-runner' import type { CommonServerOptions } from '../http' import { @@ -38,13 +37,8 @@ import { setupSIGTERMListener, teardownSIGTERMListener, } from '../utils' -import { ssrLoadModule } from '../ssr/ssrModuleLoader' -import { ssrFixStacktrace, ssrRewriteStacktrace } from '../ssr/ssrStacktrace' -import { ssrTransform } from '../ssr/ssrTransform' -import { reloadOnTsconfigChange } from '../plugins/esbuild' import { bindCLIShortcuts } from '../shortcuts' import type { BindCLIShortcutsOptions } from '../shortcuts' -import { ERR_OUTDATED_OPTIMIZED_DEP } from '../../shared/constants' import { CLIENT_DIR, DEFAULT_DEV_PORT, @@ -52,7 +46,6 @@ import { } from '../constants' import type { Logger } from '../logger' import { printServerUrls } from '../logger' -import { warnFutureDeprecation } from '../deprecations' import { createNoopWatcher, getResolvedOutDirs, @@ -62,8 +55,7 @@ import { import { initPublicFiles } from '../publicDir' import { getEnvFilesForMode } from '../env' import type { RequiredExceptFor } from '../typeUtils' -import type { PluginContainer } from './pluginContainer' -import { ERR_CLOSED_SERVER, createPluginContainer } from './pluginContainer' +import { cleanUrl } from '../../shared/utils' import type { WebSocketServer } from './ws' import { createWebSocketServer } from './ws' import { baseMiddleware } from './middlewares/base' @@ -71,31 +63,22 @@ import { proxyMiddleware } from './middlewares/proxy' import { htmlFallbackMiddleware } from './middlewares/htmlFallback' import { cachedTransformMiddleware, - transformMiddleware, + // transformMiddleware, } from './middlewares/transform' -import { - createDevHtmlTransformFn, - indexHtmlMiddleware, -} from './middlewares/indexHtml' +// import { +// indexHtmlMiddleware, +// } from './middlewares/indexHtml' import { servePublicMiddleware, - serveRawFsMiddleware, - serveStaticMiddleware, } from './middlewares/static' import { timeMiddleware } from './middlewares/time' -import { ModuleGraph } from './mixedModuleGraph' -import type { ModuleNode } from './mixedModuleGraph' import { notFoundMiddleware } from './middlewares/notFound' -import { buildErrorMessage, errorMiddleware } from './middlewares/error' +import { errorMiddleware } from './middlewares/error' import type { HmrOptions, HotBroadcaster } from './hmr' import { createDeprecatedHotBroadcaster, - handleHMRUpdate, - updateModules, } from './hmr' import { openBrowser as _openBrowser } from './openBrowser' -import type { TransformOptions, TransformResult } from './transformRequest' -import { transformRequest } from './transformRequest' import { searchForPackageRoot, searchForWorkspaceRoot } from './searchRoot' import type { DevEnvironment } from './environment' import { hostCheckMiddleware } from './middlewares/hostCheck' @@ -285,7 +268,7 @@ export interface ViteDevServer { /** * Rollup plugin container that can run plugin hooks on a given file */ - pluginContainer: PluginContainer + // pluginContainer: PluginContainer /** * Module execution environments attached to the Vite server. */ @@ -294,7 +277,7 @@ export interface ViteDevServer { * Module graph that tracks the import relationships, url to file mapping * and hmr state. */ - moduleGraph: ModuleGraph + // moduleGraph: ModuleGraph /** * The resolved urls Vite prints on the CLI (URL-encoded). Returns `null` * in middleware mode or if the server is not listening on any port. @@ -304,53 +287,53 @@ export interface ViteDevServer { * Programmatically resolve, load and transform a URL and get the result * without going through the http request pipeline. */ - transformRequest( - url: string, - options?: TransformOptions, - ): Promise + // transformRequest( + // url: string, + // options?: TransformOptions, + // ): Promise /** * Same as `transformRequest` but only warm up the URLs so the next request * will already be cached. The function will never throw as it handles and * reports errors internally. */ - warmupRequest(url: string, options?: TransformOptions): Promise + // warmupRequest(url: string, options?: TransformOptions): Promise /** * Apply vite built-in HTML transforms and any plugin HTML transforms. */ - transformIndexHtml( - url: string, - html: string, - originalUrl?: string, - ): Promise + // transformIndexHtml( + // url: string, + // html: string, + // originalUrl?: string, + // ): Promise /** * Transform module code into SSR format. */ - ssrTransform( - code: string, - inMap: SourceMap | { mappings: '' } | null, - url: string, - originalCode?: string, - ): Promise + // ssrTransform( + // code: string, + // inMap: SourceMap | { mappings: '' } | null, + // url: string, + // originalCode?: string, + // ): Promise /** * Load a given URL as an instantiated module for SSR. */ - ssrLoadModule( - url: string, - opts?: { fixStacktrace?: boolean }, - ): Promise> + // ssrLoadModule( + // url: string, + // opts?: { fixStacktrace?: boolean }, + // ): Promise> /** * Returns a fixed version of the given stack */ - ssrRewriteStacktrace(stack: string): string + // ssrRewriteStacktrace(stack: string): string /** * Mutates the given SSR error by rewriting the stacktrace */ - ssrFixStacktrace(e: Error): void + // ssrFixStacktrace(e: Error): void /** * Triggers HMR for a module in the module graph. You can use the `server.moduleGraph` * API to retrieve the module to be reloaded. If `hmr` is false, this is a no-op. */ - reloadModule(module: ModuleNode): Promise + // reloadModule(module: ModuleNode): Promise /** * Start the server. */ @@ -411,6 +394,10 @@ export interface ViteDevServer { * @internal */ _currentServerPort?: number | undefined + /** + * @internal + */ + _currentServerHost?: string | undefined /** * @internal */ @@ -419,6 +406,10 @@ export interface ViteDevServer { * @internal */ _ssrCompatModuleRunner?: ModuleRunner + /** + * @internal + */ + memoryFiles: Record } export interface ResolvedServerUrls { @@ -520,15 +511,15 @@ export async function _createServer( // Backward compatibility - let moduleGraph = new ModuleGraph({ - client: () => environments.client.moduleGraph, - ssr: () => environments.ssr.moduleGraph, - }) - const pluginContainer = createPluginContainer(environments) + // let moduleGraph = new ModuleGraph({ + // client: () => environments.client.moduleGraph, + // ssr: () => environments.ssr.moduleGraph, + // }) + // const pluginContainer = createPluginContainer(environments) const closeHttpServer = createServerCloseFn(httpServer) - const devHtmlTransformFn = createDevHtmlTransformFn(config) + // const devHtmlTransformFn = createDevHtmlTransformFn(config) // Promise used by `server.close()` to ensure `closeServer()` is only called once let closeServerPromise: Promise | undefined @@ -553,6 +544,7 @@ export async function _createServer( } let server: ViteDevServer = { + memoryFiles: {}, config, middlewares, httpServer, @@ -561,90 +553,90 @@ export async function _createServer( hot: createDeprecatedHotBroadcaster(ws), environments, - pluginContainer, - get moduleGraph() { - warnFutureDeprecation(config, 'removeServerModuleGraph') - return moduleGraph - }, - set moduleGraph(graph) { - moduleGraph = graph - }, + // pluginContainer, + // get moduleGraph() { + // warnFutureDeprecation(config, 'removeServerModuleGraph') + // return moduleGraph + // }, + // set moduleGraph(graph) { + // moduleGraph = graph + // }, resolvedUrls: null, // will be set on listen - ssrTransform( - code: string, - inMap: SourceMap | { mappings: '' } | null, - url: string, - originalCode = code, - ) { - return ssrTransform(code, inMap, url, originalCode, { - json: { - stringify: - config.json.stringify === true && config.json.namedExports !== true, - }, - }) - }, + // ssrTransform( + // code: string, + // inMap: SourceMap | { mappings: '' } | null, + // url: string, + // originalCode = code, + // ) { + // return ssrTransform(code, inMap, url, originalCode, { + // json: { + // stringify: + // config.json.stringify === true && config.json.namedExports !== true, + // }, + // }) + // }, // environment.transformRequest and .warmupRequest don't take an options param for now, // so the logic and error handling needs to be duplicated here. // The only param in options that could be important is `html`, but we may remove it as // that is part of the internal control flow for the vite dev server to be able to bail // out and do the html fallback - transformRequest(url, options) { - warnFutureDeprecation( - config, - 'removeServerTransformRequest', - 'server.transformRequest() is deprecated. Use environment.transformRequest() instead.', - ) - const environment = server.environments[options?.ssr ? 'ssr' : 'client'] - return transformRequest(environment, url, options) - }, - async warmupRequest(url, options) { - try { - const environment = server.environments[options?.ssr ? 'ssr' : 'client'] - await transformRequest(environment, url, options) - } catch (e) { - if ( - e?.code === ERR_OUTDATED_OPTIMIZED_DEP || - e?.code === ERR_CLOSED_SERVER - ) { - // these are expected errors - return - } - // Unexpected error, log the issue but avoid an unhandled exception - server.config.logger.error( - buildErrorMessage(e, [`Pre-transform error: ${e.message}`], false), - { - error: e, - timestamp: true, - }, - ) - } - }, - transformIndexHtml(url, html, originalUrl) { - return devHtmlTransformFn(server, url, html, originalUrl) - }, - async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { - warnFutureDeprecation(config, 'removeSsrLoadModule') - return ssrLoadModule(url, server, opts?.fixStacktrace) - }, - ssrFixStacktrace(e) { - ssrFixStacktrace(e, server.environments.ssr.moduleGraph) - }, - ssrRewriteStacktrace(stack: string) { - return ssrRewriteStacktrace(stack, server.environments.ssr.moduleGraph) - }, - async reloadModule(module) { - if (serverConfig.hmr !== false && module.file) { - // TODO: Should we also update the node moduleGraph for backward compatibility? - const environmentModule = (module._clientModule ?? module._ssrModule)! - updateModules( - environments[environmentModule.environment]!, - module.file, - [environmentModule], - Date.now(), - ) - } - }, + // transformRequest(url, options) { + // warnFutureDeprecation( + // config, + // 'removeServerTransformRequest', + // 'server.transformRequest() is deprecated. Use environment.transformRequest() instead.', + // ) + // const environment = server.environments[options?.ssr ? 'ssr' : 'client'] + // return transformRequest(environment, url, options) + // }, + // async warmupRequest(url, options) { + // try { + // const environment = server.environments[options?.ssr ? 'ssr' : 'client'] + // await transformRequest(environment, url, options) + // } catch (e) { + // if ( + // e?.code === ERR_OUTDATED_OPTIMIZED_DEP || + // e?.code === ERR_CLOSED_SERVER + // ) { + // // these are expected errors + // return + // } + // // Unexpected error, log the issue but avoid an unhandled exception + // server.config.logger.error( + // buildErrorMessage(e, [`Pre-transform error: ${e.message}`], false), + // { + // error: e, + // timestamp: true, + // }, + // ) + // } + // }, + // transformIndexHtml(url, html, originalUrl) { + // return devHtmlTransformFn(server, url, html, originalUrl) + // }, + // async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { + // warnFutureDeprecation(config, 'removeSsrLoadModule') + // return ssrLoadModule(url, server, opts?.fixStacktrace) + // }, + // ssrFixStacktrace(e) { + // ssrFixStacktrace(e, server.environments.ssr.moduleGraph) + // }, + // ssrRewriteStacktrace(stack: string) { + // return ssrRewriteStacktrace(stack, server.environments.ssr.moduleGraph) + // }, + // async reloadModule(module) { + // if (serverConfig.hmr !== false && module.file) { + // // TODO: Should we also update the node moduleGraph for backward compatibility? + // const environmentModule = (module._clientModule ?? module._ssrModule)! + // updateModules( + // environments[environmentModule.environment]!, + // module.file, + // [environmentModule], + // Date.now(), + // ) + // } + // }, async listen(port?: number, isRestart?: boolean) { await startServer(server, port) if (httpServer) { @@ -726,6 +718,7 @@ export async function _createServer( bindCLIShortcuts(options) { bindCLIShortcuts(server, options) }, + // TODO(underfin): server restart need to update the rolldown runtime ws url async restart(forceOptimize?: boolean) { if (!server._restartPromise) { server._forceOptimizeOnRestart = !!forceOptimize @@ -776,67 +769,69 @@ export async function _createServer( setupSIGTERMListener(closeServerAndExit) } - const onHMRUpdate = async ( - type: 'create' | 'delete' | 'update', - file: string, - ) => { - if (serverConfig.hmr !== false) { - await handleHMRUpdate(type, file, server) - } - } - - const onFileAddUnlink = async (file: string, isUnlink: boolean) => { - file = normalizePath(file) - reloadOnTsconfigChange(server, file) - - await pluginContainer.watchChange(file, { - event: isUnlink ? 'delete' : 'create', - }) - - if (publicDir && publicFiles) { - if (file.startsWith(publicDir)) { - const path = file.slice(publicDir.length) - publicFiles[isUnlink ? 'delete' : 'add'](path) - if (!isUnlink) { - const clientModuleGraph = server.environments.client.moduleGraph - const moduleWithSamePath = - await clientModuleGraph.getModuleByUrl(path) - const etag = moduleWithSamePath?.transformResult?.etag - if (etag) { - // The public file should win on the next request over a module with the - // same path. Prevent the transform etag fast path from serving the module - clientModuleGraph.etagToModuleMap.delete(etag) - } - } - } - } - if (isUnlink) { - // invalidate module graph cache on file change - for (const environment of Object.values(server.environments)) { - environment.moduleGraph.onFileDelete(file) - } - } - await onHMRUpdate(isUnlink ? 'delete' : 'create', file) - } - - watcher.on('change', async (file) => { - file = normalizePath(file) - reloadOnTsconfigChange(server, file) - - await pluginContainer.watchChange(file, { event: 'update' }) + // const onHMRUpdate = async ( + // type: 'create' | 'delete' | 'update', + // file: string, + // ) => { + // if (serverConfig.hmr !== false) { + // await handleHMRUpdate(type, file, server) + // } + // } + + // const onFileAddUnlink = async (file: string, isUnlink: boolean) => { + // file = normalizePath(file) + // reloadOnTsconfigChange(server, file) + + // await pluginContainer.watchChange(file, { + // event: isUnlink ? 'delete' : 'create', + // }) + + // if (publicDir && publicFiles) { + // if (file.startsWith(publicDir)) { + // const path = file.slice(publicDir.length) + // publicFiles[isUnlink ? 'delete' : 'add'](path) + // if (!isUnlink) { + // const clientModuleGraph = server.environments.client.moduleGraph + // const moduleWithSamePath = + // await clientModuleGraph.getModuleByUrl(path) + // const etag = moduleWithSamePath?.transformResult?.etag + // if (etag) { + // // The public file should win on the next request over a module with the + // // same path. Prevent the transform etag fast path from serving the module + // clientModuleGraph.etagToModuleMap.delete(etag) + // } + // } + // } + // } + // if (isUnlink) { + // // invalidate module graph cache on file change + // for (const environment of Object.values(server.environments)) { + // environment.moduleGraph.onFileDelete(file) + // } + // } + // await onHMRUpdate(isUnlink ? 'delete' : 'create', file) + // } + + // watcher.on('change', async (file) => { + // file = normalizePath(file) + // TODO(underfin): handle ts config.json change + // reloadOnTsconfigChange(server, file) + + // TODO(underfin): watchChange hooks how to migrate + // await pluginContainer.watchChange(file, { event: 'update' }) // invalidate module graph cache on file change - for (const environment of Object.values(server.environments)) { - environment.moduleGraph.onFileChange(file) - } - await onHMRUpdate('update', file) - }) - - watcher.on('add', (file) => { - onFileAddUnlink(file, false) - }) - watcher.on('unlink', (file) => { - onFileAddUnlink(file, true) - }) + // for (const environment of Object.values(server.environments)) { + // environment.moduleGraph.onFileChange(file) + // } + // await onHMRUpdate('update', file) + // }) + + // watcher.on('add', (file) => { + // onFileAddUnlink(file, false) + // }) + // watcher.on('unlink', (file) => { + // onFileAddUnlink(file, true) + // }) if (!middlewareMode && httpServer) { httpServer.once('listening', () => { @@ -902,6 +897,7 @@ export async function _createServer( } }) + // TODO(underfin): public dir also should be served to dist // serve static files under /public // this applies before the transform middleware so that these files are served // as-is without transforms. @@ -910,11 +906,23 @@ export async function _createServer( } // main transform middleware - middlewares.use(transformMiddleware(server)) + // middlewares.use(transformMiddleware(server)) // serve static files - middlewares.use(serveRawFsMiddleware(server)) - middlewares.use(serveStaticMiddleware(server)) + // middlewares.use(serveRawFsMiddleware(server)) + // middlewares.use(serveStaticMiddleware(server)) + + // serve memory output dist files + middlewares.use((req, res, next) => { + const cleanedUrl = cleanUrl(req.url!) + const file = server.memoryFiles[cleanedUrl] + if ( + file + ) { + return res.end(file) + } + next() + }); // html fallback if (config.appType === 'spa' || config.appType === 'mpa') { @@ -928,7 +936,7 @@ export async function _createServer( if (config.appType === 'spa' || config.appType === 'mpa') { // transform index.html - middlewares.use(indexHtmlMiddleware(root, server)) + // middlewares.use(indexHtmlMiddleware(root, server)) // handle 404s middlewares.use(notFoundMiddleware()) @@ -950,7 +958,7 @@ export async function _createServer( // For backward compatibility, we call buildStart for the client // environment when initing the server. For other environments // buildStart will be called when the first request is transformed - await environments.client.pluginContainer.buildStart() + // await environments.client.pluginContainer.buildStart() // ensure ws server started if (onListen || options.listen) { @@ -1012,6 +1020,7 @@ async function startServer( logger: server.config.logger, }) server._currentServerPort = serverPort + server._currentServerHost = hostname.host } export function createServerCloseFn( diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 6e88f2145dc967..6199b3ca50d79e 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -1,432 +1,441 @@ import fs from 'node:fs' import fsp from 'node:fs/promises' import path from 'node:path' -import MagicString from 'magic-string' -import type { SourceMapInput } from 'rolldown' +// import MagicString from 'magic-string' +// import type { SourceMapInput } from 'rolldown' import type { Connect } from 'dep-types/connect' -import type { DefaultTreeAdapterMap, Token } from 'parse5' -import type { IndexHtmlTransformHook } from '../../plugins/html' -import { - addToHTMLProxyCache, - applyHtmlTransforms, - extractImportExpressionFromClassicScript, - findNeedTransformStyleAttribute, - getScriptInfo, - htmlEnvHook, - htmlProxyResult, - injectCspNonceMetaTagHook, - injectNonceAttributeTagHook, - nodeIsElement, - overwriteAttrValue, - postImportMapHook, - preImportMapHook, - removeViteIgnoreAttr, - resolveHtmlTransforms, - traverseHtml, -} from '../../plugins/html' -import type { PreviewServer, ResolvedConfig, ViteDevServer } from '../..' +// import type { +// DefaultTreeAdapterMap, +// Token +// } from 'parse5' +// import type { IndexHtmlTransformHook } from '../../plugins/html' +// import { +// addToHTMLProxyCache, +// applyHtmlTransforms, +// extractImportExpressionFromClassicScript, +// findNeedTransformStyleAttribute, +// getScriptInfo, +// htmlEnvHook, +// htmlProxyResult, +// injectCspNonceMetaTagHook, +// injectNonceAttributeTagHook, +// nodeIsElement, +// overwriteAttrValue, +// postImportMapHook, +// preImportMapHook, +// removeViteIgnoreAttr, +// resolveHtmlTransforms, +// traverseHtml, +// } from '../../plugins/html' +import type { PreviewServer, + // ResolvedConfig, + ViteDevServer } from '../..' import { send } from '../send' -import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants' +import { + // CLIENT_PUBLIC_PATH, + FS_PREFIX } from '../../constants' import { - ensureWatchedFile, + // ensureWatchedFile, fsPathFromId, - getHash, - injectQuery, + // getHash, + // injectQuery, isDevServer, - isJSRequest, - joinUrlSegments, - normalizePath, - processSrcSetSync, - stripBase, + // isJSRequest, + // joinUrlSegments, + // normalizePath, + // processSrcSetSync, + // stripBase, } from '../../utils' -import { checkPublicFile } from '../../publicDir' -import { isCSSRequest } from '../../plugins/css' -import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap' -import { cleanUrl, unwrapId, wrapId } from '../../../shared/utils' -import { getNodeAssetAttributes } from '../../assetSource' - -interface AssetNode { - start: number - end: number - code: string -} - -interface InlineStyleAttribute { - index: number - location: Token.Location - code: string -} - -export function createDevHtmlTransformFn( - config: ResolvedConfig, -): ( - server: ViteDevServer, - url: string, - html: string, - originalUrl?: string, -) => Promise { - const [preHooks, normalHooks, postHooks] = resolveHtmlTransforms( - config.plugins, - config.logger, - ) - const transformHooks = [ - preImportMapHook(config), - injectCspNonceMetaTagHook(config), - ...preHooks, - htmlEnvHook(config), - devHtmlHook, - ...normalHooks, - ...postHooks, - injectNonceAttributeTagHook(config), - postImportMapHook(), - ] - return ( - server: ViteDevServer, - url: string, - html: string, - originalUrl?: string, - ): Promise => { - return applyHtmlTransforms(html, transformHooks, { - path: url, - filename: getHtmlFilename(url, server), - server, - originalUrl, - }) - } -} - -function getHtmlFilename(url: string, server: ViteDevServer) { - if (url.startsWith(FS_PREFIX)) { - return decodeURIComponent(fsPathFromId(url)) - } else { - return decodeURIComponent( - normalizePath(path.join(server.config.root, url.slice(1))), - ) - } -} - -function shouldPreTransform(url: string, config: ResolvedConfig) { - return ( - !checkPublicFile(url, config) && (isJSRequest(url) || isCSSRequest(url)) - ) -} - -const wordCharRE = /\w/ - -function isBareRelative(url: string) { - return wordCharRE.test(url[0]) && !url.includes(':') -} - -const processNodeUrl = ( - url: string, - useSrcSetReplacer: boolean, - config: ResolvedConfig, - htmlPath: string, - originalUrl?: string, - server?: ViteDevServer, - isClassicScriptLink?: boolean, -): string => { - // prefix with base (dev only, base is never relative) - const replacer = (url: string) => { - if ( - (url[0] === '/' && url[1] !== '/') || - // #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets - // path will add `/a/` prefix, it will caused 404. - // - // skip if url contains `:` as it implies a url protocol or Windows path that we don't want to replace. - // - // rewrite `./index.js` -> `localhost:5173/a/index.js`. - // rewrite `../index.js` -> `localhost:5173/index.js`. - // rewrite `relative/index.js` -> `localhost:5173/a/relative/index.js`. - ((url[0] === '.' || isBareRelative(url)) && - originalUrl && - originalUrl !== '/' && - htmlPath === '/index.html') - ) { - url = path.posix.join(config.base, url) - } - - let preTransformUrl: string | undefined - - if (!isClassicScriptLink && shouldPreTransform(url, config)) { - if (url[0] === '/' && url[1] !== '/') { - preTransformUrl = url - } else if (url[0] === '.' || isBareRelative(url)) { - preTransformUrl = path.posix.join( - config.base, - path.posix.dirname(htmlPath), - url, - ) - } - } - - if (server) { - const mod = server.environments.client.moduleGraph.urlToModuleMap.get( - preTransformUrl || url, - ) - if (mod && mod.lastHMRTimestamp > 0) { - url = injectQuery(url, `t=${mod.lastHMRTimestamp}`) - } - } - - if (server && preTransformUrl) { - try { - preTransformUrl = decodeURI(preTransformUrl) - } catch { - // Malformed uri. Skip pre-transform. - return url - } - preTransformRequest(server, preTransformUrl, config.decodedBase) - } - - return url - } - - const processedUrl = useSrcSetReplacer - ? processSrcSetSync(url, ({ url }) => replacer(url)) - : replacer(url) - return processedUrl -} -const devHtmlHook: IndexHtmlTransformHook = async ( - html, - { path: htmlPath, filename, server, originalUrl }, -) => { - const { config, watcher } = server! - const base = config.base || '/' - const decodedBase = config.decodedBase || '/' - - let proxyModulePath: string - let proxyModuleUrl: string - - const trailingSlash = htmlPath.endsWith('/') - if (!trailingSlash && fs.existsSync(filename)) { - proxyModulePath = htmlPath - proxyModuleUrl = proxyModulePath - } else { - // There are users of vite.transformIndexHtml calling it with url '/' - // for SSR integrations #7993, filename is root for this case - // A user may also use a valid name for a virtual html file - // Mark the path as virtual in both cases so sourcemaps aren't processed - // and ids are properly handled - const validPath = `${htmlPath}${trailingSlash ? 'index.html' : ''}` - proxyModulePath = `\0${validPath}` - proxyModuleUrl = wrapId(proxyModulePath) - } - proxyModuleUrl = joinUrlSegments(decodedBase, proxyModuleUrl) - - const s = new MagicString(html) - let inlineModuleIndex = -1 - // The key to the proxyHtml cache is decoded, as it will be compared - // against decoded URLs by the HTML plugins. - const proxyCacheUrl = decodeURI( - cleanUrl(proxyModulePath).replace(normalizePath(config.root), ''), - ) - const styleUrl: AssetNode[] = [] - const inlineStyles: InlineStyleAttribute[] = [] - const inlineModulePaths: string[] = [] - - const addInlineModule = ( - node: DefaultTreeAdapterMap['element'], - ext: 'js', - ) => { - inlineModuleIndex++ - - const contentNode = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] - - const code = contentNode.value - - let map: SourceMapInput | undefined - if (proxyModulePath[0] !== '\0') { - map = new MagicString(html) - .snip( - contentNode.sourceCodeLocation!.startOffset, - contentNode.sourceCodeLocation!.endOffset, - ) - .generateMap({ hires: 'boundary' }) - map.sources = [filename] - map.file = filename - } - - // add HTML Proxy to Map - addToHTMLProxyCache(config, proxyCacheUrl, inlineModuleIndex, { code, map }) - - // inline js module. convert to src="proxy" (dev only, base is never relative) - const modulePath = `${proxyModuleUrl}?html-proxy&index=${inlineModuleIndex}.${ext}` - inlineModulePaths.push(modulePath) - - s.update( - node.sourceCodeLocation!.startOffset, - node.sourceCodeLocation!.endOffset, - ``, - ) - preTransformRequest(server!, modulePath, decodedBase) - } - - await traverseHtml(html, filename, (node) => { - if (!nodeIsElement(node)) { - return - } - - // script tags - if (node.nodeName === 'script') { - const { src, srcSourceCodeLocation, isModule, isIgnored } = - getScriptInfo(node) - - if (isIgnored) { - removeViteIgnoreAttr(s, node.sourceCodeLocation!) - } else if (src) { - const processedUrl = processNodeUrl( - src.value, - /* useSrcSetReplacer */ false, - config, - htmlPath, - originalUrl, - server, - !isModule, - ) - if (processedUrl !== src.value) { - overwriteAttrValue(s, srcSourceCodeLocation!, processedUrl) - } - } else if (isModule && node.childNodes.length) { - addInlineModule(node, 'js') - } else if (node.childNodes.length) { - const scriptNode = node.childNodes[ - node.childNodes.length - 1 - ] as DefaultTreeAdapterMap['textNode'] - for (const { - url, - start, - end, - } of extractImportExpressionFromClassicScript(scriptNode)) { - const processedUrl = processNodeUrl( - url, - false, - config, - htmlPath, - originalUrl, - ) - if (processedUrl !== url) { - s.update(start, end, processedUrl) - } - } - } - } - - const inlineStyle = findNeedTransformStyleAttribute(node) - if (inlineStyle) { - inlineModuleIndex++ - inlineStyles.push({ - index: inlineModuleIndex, - location: inlineStyle.location!, - code: inlineStyle.attr.value, - }) - } - - if (node.nodeName === 'style' && node.childNodes.length) { - const children = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] - styleUrl.push({ - start: children.sourceCodeLocation!.startOffset, - end: children.sourceCodeLocation!.endOffset, - code: children.value, - }) - } - - // elements with [href/src] attrs - const assetAttributes = getNodeAssetAttributes(node) - for (const attr of assetAttributes) { - if (attr.type === 'remove') { - s.remove(attr.location.startOffset, attr.location.endOffset) - } else { - const processedUrl = processNodeUrl( - attr.value, - attr.type === 'srcset', - config, - htmlPath, - originalUrl, - ) - if (processedUrl !== attr.value) { - overwriteAttrValue(s, attr.location, processedUrl) - } - } - } - }) - - // invalidate the module so the newly cached contents will be served - const clientModuelGraph = server?.environments.client.moduleGraph - if (clientModuelGraph) { - await Promise.all( - inlineModulePaths.map(async (url) => { - const module = await clientModuelGraph.getModuleByUrl(url) - if (module) { - clientModuelGraph.invalidateModule(module) - } - }), - ) - } - - await Promise.all([ - ...styleUrl.map(async ({ start, end, code }, index) => { - const url = `${proxyModulePath}?html-proxy&direct&index=${index}.css` - - // ensure module in graph after successful load - const mod = - await server!.environments.client.moduleGraph.ensureEntryFromUrl( - url, - false, - ) - ensureWatchedFile(watcher, mod.file, config.root) - - const result = await server!.pluginContainer.transform(code, mod.id!, { - environment: server!.environments.client, - }) - let content = '' - if (result.map && 'version' in result.map) { - if (result.map.mappings) { - await injectSourcesContent(result.map, proxyModulePath, config.logger) - } - content = getCodeWithSourcemap('css', result.code, result.map) - } else { - content = result.code - } - s.overwrite(start, end, content) - }), - ...inlineStyles.map(async ({ index, location, code }) => { - // will transform with css plugin and cache result with css-post plugin - const url = `${proxyModulePath}?html-proxy&inline-css&style-attr&index=${index}.css` - - const mod = - await server!.environments.client.moduleGraph.ensureEntryFromUrl( - url, - false, - ) - ensureWatchedFile(watcher, mod.file, config.root) - - await server?.pluginContainer.transform(code, mod.id!, { - environment: server!.environments.client, - }) - - const hash = getHash(cleanUrl(mod.id!)) - const result = htmlProxyResult.get(`${hash}_${index}`) - overwriteAttrValue(s, location, result ?? '') - }), - ]) - - html = s.toString() - - return { - html, - tags: [ - { - tag: 'script', - attrs: { - type: 'module', - src: path.posix.join(base, CLIENT_PUBLIC_PATH), - }, - injectTo: 'head-prepend', - }, - ], - } -} +// import { checkPublicFile } from '../../publicDir' +// import { isCSSRequest } from '../../plugins/css' +// import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap' +import { cleanUrl, + // unwrapId, wrapId +} from '../../../shared/utils' +// import { getNodeAssetAttributes } from '../../assetSource' + +// interface AssetNode { +// start: number +// end: number +// code: string +// } + +// interface InlineStyleAttribute { +// index: number +// location: Token.Location +// code: string +// } + +// export function createDevHtmlTransformFn( +// config: ResolvedConfig, +// ): ( +// server: ViteDevServer, +// url: string, +// html: string, +// originalUrl?: string, +// ) => Promise { +// const [preHooks, normalHooks, postHooks] = resolveHtmlTransforms( +// config.plugins, +// config.logger, +// ) +// const transformHooks = [ +// preImportMapHook(config), +// injectCspNonceMetaTagHook(config), +// ...preHooks, +// htmlEnvHook(config), +// // devHtmlHook, +// ...normalHooks, +// ...postHooks, +// injectNonceAttributeTagHook(config), +// postImportMapHook(), +// ] +// return ( +// server: ViteDevServer, +// url: string, +// html: string, +// originalUrl?: string, +// ): Promise => { +// return applyHtmlTransforms(html, transformHooks, { +// path: url, +// filename: getHtmlFilename(url, server), +// server, +// originalUrl, +// }) +// } +// } + +// function getHtmlFilename(url: string, server: ViteDevServer) { +// if (url.startsWith(FS_PREFIX)) { +// return decodeURIComponent(fsPathFromId(url)) +// } else { +// return decodeURIComponent( +// normalizePath(path.join(server.config.root, url.slice(1))), +// ) +// } +// } + +// function shouldPreTransform(url: string, config: ResolvedConfig) { +// return ( +// !checkPublicFile(url, config) && (isJSRequest(url) || isCSSRequest(url)) +// ) +// } + +// const wordCharRE = /\w/ + +// function isBareRelative(url: string) { +// return wordCharRE.test(url[0]) && !url.includes(':') +// } + +// const processNodeUrl = ( +// url: string, +// useSrcSetReplacer: boolean, +// config: ResolvedConfig, +// htmlPath: string, +// originalUrl?: string, +// server?: ViteDevServer, +// isClassicScriptLink?: boolean, +// ): string => { +// // prefix with base (dev only, base is never relative) +// const replacer = (url: string) => { +// if ( +// (url[0] === '/' && url[1] !== '/') || +// // #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets +// // path will add `/a/` prefix, it will caused 404. +// // +// // skip if url contains `:` as it implies a url protocol or Windows path that we don't want to replace. +// // +// // rewrite `./index.js` -> `localhost:5173/a/index.js`. +// // rewrite `../index.js` -> `localhost:5173/index.js`. +// // rewrite `relative/index.js` -> `localhost:5173/a/relative/index.js`. +// ((url[0] === '.' || isBareRelative(url)) && +// originalUrl && +// originalUrl !== '/' && +// htmlPath === '/index.html') +// ) { +// url = path.posix.join(config.base, url) +// } + +// let preTransformUrl: string | undefined + +// if (!isClassicScriptLink && shouldPreTransform(url, config)) { +// if (url[0] === '/' && url[1] !== '/') { +// preTransformUrl = url +// } else if (url[0] === '.' || isBareRelative(url)) { +// preTransformUrl = path.posix.join( +// config.base, +// path.posix.dirname(htmlPath), +// url, +// ) +// } +// } + +// if (server) { +// const mod = server.environments.client.moduleGraph.urlToModuleMap.get( +// preTransformUrl || url, +// ) +// if (mod && mod.lastHMRTimestamp > 0) { +// url = injectQuery(url, `t=${mod.lastHMRTimestamp}`) +// } +// } + +// if (server && preTransformUrl) { +// try { +// preTransformUrl = decodeURI(preTransformUrl) +// } catch { +// // Malformed uri. Skip pre-transform. +// return url +// } +// preTransformRequest(server, preTransformUrl, config.decodedBase) +// } + +// return url +// } + +// const processedUrl = useSrcSetReplacer +// ? processSrcSetSync(url, ({ url }) => replacer(url)) +// : replacer(url) +// return processedUrl +// } +// const devHtmlHook: IndexHtmlTransformHook = async ( +// html, +// { path: htmlPath, filename, server, originalUrl }, +// ) => { +// const { config, watcher } = server! +// const base = config.base || '/' +// const decodedBase = config.decodedBase || '/' + +// let proxyModulePath: string +// let proxyModuleUrl: string + +// const trailingSlash = htmlPath.endsWith('/') +// if (!trailingSlash && fs.existsSync(filename)) { +// proxyModulePath = htmlPath +// proxyModuleUrl = proxyModulePath +// } else { +// // There are users of vite.transformIndexHtml calling it with url '/' +// // for SSR integrations #7993, filename is root for this case +// // A user may also use a valid name for a virtual html file +// // Mark the path as virtual in both cases so sourcemaps aren't processed +// // and ids are properly handled +// const validPath = `${htmlPath}${trailingSlash ? 'index.html' : ''}` +// proxyModulePath = `\0${validPath}` +// proxyModuleUrl = wrapId(proxyModulePath) +// } +// proxyModuleUrl = joinUrlSegments(decodedBase, proxyModuleUrl) + +// const s = new MagicString(html) +// let inlineModuleIndex = -1 +// // The key to the proxyHtml cache is decoded, as it will be compared +// // against decoded URLs by the HTML plugins. +// const proxyCacheUrl = decodeURI( +// cleanUrl(proxyModulePath).replace(normalizePath(config.root), ''), +// ) +// const styleUrl: AssetNode[] = [] +// const inlineStyles: InlineStyleAttribute[] = [] +// const inlineModulePaths: string[] = [] + +// const addInlineModule = ( +// node: DefaultTreeAdapterMap['element'], +// ext: 'js', +// ) => { +// inlineModuleIndex++ + +// const contentNode = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] + +// const code = contentNode.value + +// let map: SourceMapInput | undefined +// if (proxyModulePath[0] !== '\0') { +// map = new MagicString(html) +// .snip( +// contentNode.sourceCodeLocation!.startOffset, +// contentNode.sourceCodeLocation!.endOffset, +// ) +// .generateMap({ hires: 'boundary' }) +// map.sources = [filename] +// map.file = filename +// } + +// // add HTML Proxy to Map +// addToHTMLProxyCache(config, proxyCacheUrl, inlineModuleIndex, { code, map }) + +// // inline js module. convert to src="proxy" (dev only, base is never relative) +// const modulePath = `${proxyModuleUrl}?html-proxy&index=${inlineModuleIndex}.${ext}` +// inlineModulePaths.push(modulePath) + +// s.update( +// node.sourceCodeLocation!.startOffset, +// node.sourceCodeLocation!.endOffset, +// ``, +// ) +// preTransformRequest(server!, modulePath, decodedBase) +// } + +// await traverseHtml(html, filename, (node) => { +// if (!nodeIsElement(node)) { +// return +// } + +// // script tags +// if (node.nodeName === 'script') { +// const { src, srcSourceCodeLocation, isModule, isIgnored } = +// getScriptInfo(node) + +// if (isIgnored) { +// removeViteIgnoreAttr(s, node.sourceCodeLocation!) +// } else if (src) { +// const processedUrl = processNodeUrl( +// src.value, +// /* useSrcSetReplacer */ false, +// config, +// htmlPath, +// originalUrl, +// server, +// !isModule, +// ) +// if (processedUrl !== src.value) { +// overwriteAttrValue(s, srcSourceCodeLocation!, processedUrl) +// } +// } else if (isModule && node.childNodes.length) { +// addInlineModule(node, 'js') +// } else if (node.childNodes.length) { +// const scriptNode = node.childNodes[ +// node.childNodes.length - 1 +// ] as DefaultTreeAdapterMap['textNode'] +// for (const { +// url, +// start, +// end, +// } of extractImportExpressionFromClassicScript(scriptNode)) { +// const processedUrl = processNodeUrl( +// url, +// false, +// config, +// htmlPath, +// originalUrl, +// ) +// if (processedUrl !== url) { +// s.update(start, end, processedUrl) +// } +// } +// } +// } + +// const inlineStyle = findNeedTransformStyleAttribute(node) +// if (inlineStyle) { +// inlineModuleIndex++ +// inlineStyles.push({ +// index: inlineModuleIndex, +// location: inlineStyle.location!, +// code: inlineStyle.attr.value, +// }) +// } + +// if (node.nodeName === 'style' && node.childNodes.length) { +// const children = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] +// styleUrl.push({ +// start: children.sourceCodeLocation!.startOffset, +// end: children.sourceCodeLocation!.endOffset, +// code: children.value, +// }) +// } + +// // elements with [href/src] attrs +// const assetAttributes = getNodeAssetAttributes(node) +// for (const attr of assetAttributes) { +// if (attr.type === 'remove') { +// s.remove(attr.location.startOffset, attr.location.endOffset) +// } else { +// const processedUrl = processNodeUrl( +// attr.value, +// attr.type === 'srcset', +// config, +// htmlPath, +// originalUrl, +// ) +// if (processedUrl !== attr.value) { +// overwriteAttrValue(s, attr.location, processedUrl) +// } +// } +// } +// }) + +// // invalidate the module so the newly cached contents will be served +// const clientModuelGraph = server?.environments.client.moduleGraph +// if (clientModuelGraph) { +// await Promise.all( +// inlineModulePaths.map(async (url) => { +// const module = await clientModuelGraph.getModuleByUrl(url) +// if (module) { +// clientModuelGraph.invalidateModule(module) +// } +// }), +// ) +// } + +// await Promise.all([ +// ...styleUrl.map(async ({ start, end, code }, index) => { +// const url = `${proxyModulePath}?html-proxy&direct&index=${index}.css` + +// // ensure module in graph after successful load +// const mod = +// await server!.environments.client.moduleGraph.ensureEntryFromUrl( +// url, +// false, +// ) +// ensureWatchedFile(watcher, mod.file, config.root) + +// const result = await server!.pluginContainer.transform(code, mod.id!, { +// environment: server!.environments.client, +// }) +// let content = '' +// if (result.map && 'version' in result.map) { +// if (result.map.mappings) { +// await injectSourcesContent(result.map, proxyModulePath, config.logger) +// } +// content = getCodeWithSourcemap('css', result.code, result.map) +// } else { +// content = result.code +// } +// s.overwrite(start, end, content) +// }), +// ...inlineStyles.map(async ({ index, location, code }) => { +// // will transform with css plugin and cache result with css-post plugin +// const url = `${proxyModulePath}?html-proxy&inline-css&style-attr&index=${index}.css` + +// const mod = +// await server!.environments.client.moduleGraph.ensureEntryFromUrl( +// url, +// false, +// ) +// ensureWatchedFile(watcher, mod.file, config.root) + +// await server?.pluginContainer.transform(code, mod.id!, { +// environment: server!.environments.client, +// }) + +// const hash = getHash(cleanUrl(mod.id!)) +// const result = htmlProxyResult.get(`${hash}_${index}`) +// overwriteAttrValue(s, location, result ?? '') +// }), +// ]) + +// html = s.toString() + +// return { +// html, +// tags: [ +// { +// tag: 'script', +// attrs: { +// type: 'module', +// src: path.posix.join(base, CLIENT_PUBLIC_PATH), +// }, +// injectTo: 'head-prepend', +// }, +// ], +// } +// } export function indexHtmlMiddleware( root: string, @@ -456,10 +465,10 @@ export function indexHtmlMiddleware( : server.config.preview.headers try { - let html = await fsp.readFile(filePath, 'utf-8') - if (isDev) { - html = await server.transformIndexHtml(url, html, req.originalUrl) - } + const html = await fsp.readFile(filePath, 'utf-8') + // if (isDev) { + // html = await server.transformIndexHtml(url, html, req.originalUrl) + // } return send(req, res, html, 'html', { headers }) } catch (e) { return next(e) @@ -472,14 +481,14 @@ export function indexHtmlMiddleware( // NOTE: We usually don't prefix `url` and `base` with `decoded`, but in this file particularly // we're dealing with mixed encoded/decoded paths often, so we make this explicit for now. -function preTransformRequest( - server: ViteDevServer, - decodedUrl: string, - decodedBase: string, -) { - if (!server.config.server.preTransformRequests) return - - // transform all url as non-ssr as html includes client-side assets only - decodedUrl = unwrapId(stripBase(decodedUrl, decodedBase)) - server.warmupRequest(decodedUrl) -} +// function preTransformRequest( +// server: ViteDevServer, +// decodedUrl: string, +// decodedBase: string, +// ) { +// if (!server.config.server.preTransformRequests) return + +// // transform all url as non-ssr as html includes client-side assets only +// decodedUrl = unwrapId(stripBase(decodedUrl, decodedBase)) +// server.warmupRequest(decodedUrl) +// } diff --git a/packages/vite/src/node/server/warmup.ts b/packages/vite/src/node/server/warmup.ts index 7911e0b647df07..33109534ba976d 100644 --- a/packages/vite/src/node/server/warmup.ts +++ b/packages/vite/src/node/server/warmup.ts @@ -1,6 +1,6 @@ -import fs from 'node:fs/promises' +// import fs from 'node:fs/promises' import path from 'node:path' -import colors from 'picocolors' +// import colors from 'picocolors' import { glob, isDynamicPattern } from 'tinyglobby' import { FS_PREFIX } from '../constants' import { normalizePath } from '../utils' @@ -30,19 +30,19 @@ async function warmupFile( if (file.endsWith('.html')) { const url = htmlFileToUrl(file, server.config.root) if (url) { - try { - const html = await fs.readFile(file, 'utf-8') - await server.transformIndexHtml(url, html) - } catch (e) { - // Unexpected error, log the issue but avoid an unhandled exception - environment.logger.error( - `Pre-transform error (${colors.cyan(file)}): ${e.message}`, - { - error: e, - timestamp: true, - }, - ) - } + // try { + // const html = await fs.readFile(file, 'utf-8') + // await server.transformIndexHtml(url, html) + // } catch (e) { + // // Unexpected error, log the issue but avoid an unhandled exception + // environment.logger.error( + // `Pre-transform error (${colors.cyan(file)}): ${e.message}`, + // { + // error: e, + // timestamp: true, + // }, + // ) + // } } } // for other files, pass it through `transformRequest` with warmup From 25f3e0ef5b4d4a9171683ecf45a856220bf80cc7 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 8 Apr 2025 12:02:34 +0800 Subject: [PATCH 02/76] feat: make memory files middleware work --- packages/vite/src/node/build.ts | 13 +++++---- packages/vite/src/node/server/index.ts | 16 +++------- .../src/node/server/middlewares/indexHtml.ts | 1 + .../node/server/middlewares/memoryFiles.ts | 29 +++++++++++++++++++ 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 packages/vite/src/node/server/middlewares/memoryFiles.ts diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index d36cc667820742..bb544def74ac98 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -571,8 +571,8 @@ function resolveConfigToBuild( return resolveConfig( inlineConfig, command, - 'production', - 'production', + command === 'serve' ? 'development' : 'production', + command === 'serve' ? 'development' : 'production', false, patchConfig, patchPlugins, @@ -671,10 +671,11 @@ async function buildEnvironment( '.css': 'js', }, experimental: { - hmr: server ? { - host: server._currentServerHost!, - port: server._currentServerPort!, - } : false, + hmr: true + // hmr: server ? { + // host: server._currentServerHost!, + // port: server._currentServerPort!, + // } : false, } } diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index d8508fb1afdc10..c3630ef378a39d 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -55,7 +55,6 @@ import { import { initPublicFiles } from '../publicDir' import { getEnvFilesForMode } from '../env' import type { RequiredExceptFor } from '../typeUtils' -import { cleanUrl } from '../../shared/utils' import type { WebSocketServer } from './ws' import { createWebSocketServer } from './ws' import { baseMiddleware } from './middlewares/base' @@ -82,6 +81,7 @@ import { openBrowser as _openBrowser } from './openBrowser' import { searchForPackageRoot, searchForWorkspaceRoot } from './searchRoot' import type { DevEnvironment } from './environment' import { hostCheckMiddleware } from './middlewares/hostCheck' +import { memoryFilesMiddleware } from './middlewares/memoryFiles' import { rejectInvalidRequestMiddleware } from './middlewares/rejectInvalidRequest' export interface ServerOptions extends CommonServerOptions { @@ -910,19 +910,10 @@ export async function _createServer( // serve static files // middlewares.use(serveRawFsMiddleware(server)) - // middlewares.use(serveStaticMiddleware(server)) + // middlewares.use(c(server)) // serve memory output dist files - middlewares.use((req, res, next) => { - const cleanedUrl = cleanUrl(req.url!) - const file = server.memoryFiles[cleanedUrl] - if ( - file - ) { - return res.end(file) - } - next() - }); + middlewares.use(memoryFilesMiddleware(server, false)); // html fallback if (config.appType === 'spa' || config.appType === 'mpa') { @@ -937,6 +928,7 @@ export async function _createServer( if (config.appType === 'spa' || config.appType === 'mpa') { // transform index.html // middlewares.use(indexHtmlMiddleware(root, server)) + middlewares.use(memoryFilesMiddleware(server, true)); // handle 404s middlewares.use(notFoundMiddleware()) diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 6199b3ca50d79e..77f2dcbbd4f4d8 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -437,6 +437,7 @@ import { cleanUrl, // } // } +// TODO(underfin): it only used for preview export function indexHtmlMiddleware( root: string, server: ViteDevServer | PreviewServer, diff --git a/packages/vite/src/node/server/middlewares/memoryFiles.ts b/packages/vite/src/node/server/middlewares/memoryFiles.ts new file mode 100644 index 00000000000000..6f88d1cc383b69 --- /dev/null +++ b/packages/vite/src/node/server/middlewares/memoryFiles.ts @@ -0,0 +1,29 @@ +import type { Connect } from 'dep-types/connect' +import { cleanUrl } from '../../../shared/utils' +import type { ViteDevServer } from '..' + +export function memoryFilesMiddleware(server: ViteDevServer, handleHtml: boolean): Connect.NextHandleFunction { + return function viteMemoryFilesMiddleware(req, res, next) { + const cleanedUrl = cleanUrl(req.url!).slice(1) // remove first / + if (cleanedUrl.endsWith('.html') && !handleHtml) { + return next() + } + const file = server.memoryFiles[cleanedUrl] + if ( + file + ) { + if (cleanedUrl.endsWith('.js')) { + res.setHeader('Content-Type', 'text/javascript') + } + const headers = server.config.server.headers + if (headers) { + for (const name in headers) { + res.setHeader(name, headers[name]!) + } + } + + return res.end(file) + } + next() + } +} From f18347281905fdf40bccfecf5495f952862cf318 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 8 Apr 2025 16:40:19 +0800 Subject: [PATCH 03/76] fix: make js define plugin works at dev build --- packages/vite/src/node/plugins/define.ts | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index a3ea472eab094f..ab0abb67d253e8 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,8 +12,8 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - const isBuild = config.command === 'build' - const isBuildLib = isBuild && config.build.lib + // const isBuild = config.command === 'build' + const isBuildLib = config.build.lib // ignore replace process.env in lib build const processEnv: Record = {} @@ -33,17 +33,17 @@ export function definePlugin(config: ResolvedConfig): Plugin { const importMetaKeys: Record = {} const importMetaEnvKeys: Record = {} const importMetaFallbackKeys: Record = {} - if (isBuild) { - importMetaKeys['import.meta.hot'] = `undefined` - for (const key in config.env) { - const val = JSON.stringify(config.env[key]) - importMetaKeys[`import.meta.env.${key}`] = val - importMetaEnvKeys[key] = val - } - // these will be set to a proper value in `generatePattern` - importMetaKeys['import.meta.env.SSR'] = `undefined` - importMetaFallbackKeys['import.meta.env'] = `undefined` + // if (isBuild) { + importMetaKeys['import.meta.hot'] = `undefined` + for (const key in config.env) { + const val = JSON.stringify(config.env[key]) + importMetaKeys[`import.meta.env.${key}`] = val + importMetaEnvKeys[key] = val } + // these will be set to a proper value in `generatePattern` + importMetaKeys['import.meta.env.SSR'] = `undefined` + importMetaFallbackKeys['import.meta.env'] = `undefined` + // } function generatePattern(environment: Environment) { const keepProcessEnv = environment.config.keepProcessEnv @@ -54,7 +54,7 @@ export function definePlugin(config: ResolvedConfig): Plugin { userDefine[key] = handleDefineValue(environment.config.define[key]) // make sure `import.meta.env` object has user define properties - if (isBuild && key.startsWith('import.meta.env.')) { + if (key.startsWith('import.meta.env.')) { userDefineEnv[key.slice(16)] = environment.config.define[key] } } @@ -128,12 +128,12 @@ export function definePlugin(config: ResolvedConfig): Plugin { transform: { async handler(code, id) { - if (this.environment.config.consumer === 'client' && !isBuild) { - // for dev we inject actual global defines in the vite client to - // avoid the transform cost. see the `clientInjection` and - // `importAnalysis` plugin. - return - } + // if (this.environment.config.consumer === 'client' && !isBuild) { + // for dev we inject actual global defines in the vite client to + // avoid the transform cost. see the `clientInjection` and + // `importAnalysis` plugin. + // return + // } if ( // exclude html, css and static assets for performance From c57d1821f371e721ecd33740f22ab93b72d2324d Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 8 Apr 2025 17:16:38 +0800 Subject: [PATCH 04/76] fix: disable rolldown minify at devlopment build --- packages/vite/src/node/build.ts | 5 +++-- packages/vite/src/node/plugins/index.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index bb544def74ac98..0207e2e9dd6b02 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -586,7 +586,7 @@ async function buildEnvironment( environment: BuildEnvironment, server?: ViteDevServer ): Promise { - const { root, packageCache } = environment.config + const { root, packageCache, mode } = environment.config const options = environment.config.build const libOptions = options.lib const { logger } = environment @@ -819,11 +819,12 @@ async function buildEnvironment( output.format === 'iife' || (isSsrTargetWebworkerEnvironment && (typeof input === 'string' || Object.keys(input).length === 1)), - minify: + minify: mode === 'production' ? options.minify === 'oxc' ? true : options.minify === false ? 'dce-only' + : false : false, ...output, } diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index fdb496104867b0..574b4e5224738e 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -55,7 +55,7 @@ export async function resolvePlugins( normalPlugins: Plugin[], postPlugins: Plugin[], ): Promise { - const isBuild = true + const isBuild = config.command === 'build' const isWorker = config.isWorker const buildPlugins = isBuild ? await (await import('../build')).resolveBuildPlugins(config) From 2bbb201bc5150af273b9b524d3d863aa2cbfa4d7 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 9 Apr 2025 16:13:43 +0800 Subject: [PATCH 05/76] fix: make watcher + rolldown hmr build work --- packages/vite/src/node/build.ts | 47 +++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 0207e2e9dd6b02..598347754e263e 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -906,21 +906,41 @@ async function buildEnvironment( prepareOutDir(resolvedOutDirs, emptyOutDir, environment) } - const res: RolldownOutput[] = [] - for (const output of normalizedOutputs) { - res.push(await bundle[options.write ? 'write' : 'generate'](output)) - } + const res = await build() + logger.info( `${colors.green(`✓ built in ${displayTime(Date.now() - startTime)}`)}`, ) - if (server) { - for(const output of res) { - for (const outputFile of output.output) { - server.memoryFiles[outputFile.fileName] = outputFile.type === 'chunk' ? outputFile.code : outputFile.source; + async function build() { + const res: RolldownOutput[] = [] + for (const output of normalizedOutputs) { + // TODO(underfin): using the generate at development build could be improve performance. + res.push(await bundle![options.write ? 'write' : 'generate'](output)) + } + + if (server) { + // watching the files + for (const file of bundle!.watchFiles) { + if (path.isAbsolute(file) && fs.existsSync(file)) { + server.watcher.add(file) + } + } + + // Write the output files to memory + for(const output of res) { + for (const outputFile of output.output) { + server.memoryFiles[outputFile.fileName] = outputFile.type === 'chunk' ? outputFile.code : outputFile.source; + } } } + return res + } + + + if (server) { server.watcher.on('change', async (file) => { + const startTime = Date.now() const patch = await bundle!.generateHmrPatch([file]); if (patch) { const url = `${Date.now()}.js`; @@ -930,10 +950,14 @@ async function buildEnvironment( server.ws.send({ type: 'update', url - }) + }); + logger.info( + `${colors.green(`✓ Found ${path.relative(root, file)} changed, rebuilt in ${displayTime(Date.now() - startTime)}`)}`, + ) + + await build(); } }) - // server.watcher = watcher } return Array.isArray(outputs) ? res : res[0] @@ -948,7 +972,8 @@ async function buildEnvironment( } throw e } finally { - if (bundle) await bundle.close() + // close the bundle will make the rolldown hmr invalid, so dev build need to disable it. + if (bundle && !server) await bundle.close() } } From cf9e3cca76770804a1afa86420c7c789ccbf46b3 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 9 Apr 2025 17:50:27 +0800 Subject: [PATCH 06/76] chore: disable ws validate connnection --- packages/vite/src/node/server/ws.ts | 58 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index 583a68457c8a2b..ed4f5486069cde 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -1,11 +1,11 @@ -import path from 'node:path' +// import path from 'node:path' import type { IncomingMessage, Server } from 'node:http' import { STATUS_CODES, createServer as createHttpServer } from 'node:http' import type { ServerOptions as HttpsServerOptions } from 'node:https' import { createServer as createHttpsServer } from 'node:https' import type { Socket } from 'node:net' import type { Duplex } from 'node:stream' -import crypto from 'node:crypto' +// import crypto from 'node:crypto' import colors from 'picocolors' import type { WebSocket as WebSocketRaw } from 'ws' import { WebSocketServer as WebSocketServerRaw_ } from 'ws' @@ -102,19 +102,19 @@ function noop() { // // using the query params means the token might be logged out in server or middleware logs // but we assume that is not an issue since the token is regenerated for each process -function hasValidToken(config: ResolvedConfig, url: URL) { - const token = url.searchParams.get('token') - if (!token) return false +// function hasValidToken(config: ResolvedConfig, url: URL) { +// const token = url.searchParams.get('token') +// if (!token) return false - try { - const isValidToken = crypto.timingSafeEqual( - Buffer.from(token), - Buffer.from(config.webSocketToken), - ) - return isValidToken - } catch {} // an error is thrown when the length is incorrect - return false -} +// try { +// const isValidToken = crypto.timingSafeEqual( +// Buffer.from(token), +// Buffer.from(config.webSocketToken), +// ) +// return isValidToken +// } catch {} // an error is thrown when the length is incorrect +// return false +// } export function createWebSocketServer( server: HttpServer | null, @@ -181,10 +181,10 @@ export function createWebSocketServer( // If the Origin header is set, this request might be coming from a browser. // Browsers always sets the Origin header for WebSocket connections. - if (req.headers.origin) { - const parsedUrl = new URL(`http://example.com${req.url!}`) - return hasValidToken(config, parsedUrl) - } + // if (req.headers.origin) { + // const parsedUrl = new URL(`http://example.com${req.url!}`) + // return hasValidToken(config, parsedUrl) + // } // We allow non-browser requests to connect without a token // for backward compat and convenience @@ -213,20 +213,20 @@ export function createWebSocketServer( wss.shouldHandle = shouldHandle if (wsServer) { - let hmrBase = config.base - const hmrPath = hmr ? hmr.path : undefined - if (hmrPath) { - hmrBase = path.posix.join(hmrBase, hmrPath) - } + // let hmrBase = config.base + // const hmrPath = hmr ? hmr.path : undefined + // if (hmrPath) { + // hmrBase = path.posix.join(hmrBase, hmrPath) + // } hmrServerWsListener = (req, socket, head) => { const protocol = req.headers['sec-websocket-protocol']! - const parsedUrl = new URL(`http://example.com${req.url!}`) - if ( - [HMR_HEADER, 'vite-ping'].includes(protocol) && - parsedUrl.pathname === hmrBase - ) { + // const parsedUrl = new URL(`http://example.com${req.url!}`) + // if ( + // [HMR_HEADER, 'vite-ping'].includes(protocol) && + // parsedUrl.pathname === hmrBase + // ) { handleUpgrade(req, socket as Socket, head, protocol === 'vite-ping') - } + // } } wsServer.on('upgrade', hmrServerWsListener) } else { From 6390882827520b018eb048955d006c6aeaa43a4b Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 10 Apr 2025 10:50:25 +0800 Subject: [PATCH 07/76] chore: disable dev plugin relate logic --- packages/vite/src/node/plugins/asset.ts | 25 ++-- .../src/node/plugins/assetImportMetaUrl.ts | 2 +- packages/vite/src/node/plugins/css.ts | 120 +++++++++--------- packages/vite/src/node/plugins/define.ts | 6 +- .../src/node/plugins/modulePreloadPolyfill.ts | 2 +- packages/vite/src/node/plugins/worker.ts | 2 +- .../src/node/plugins/workerImportMetaUrl.ts | 4 +- 7 files changed, 82 insertions(+), 79 deletions(-) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index c94c8c8272e8c2..4501dca145463d 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -229,10 +229,11 @@ export function assetPlugin(config: ResolvedConfig): Plugin { // Force rollup to keep this module from being shared between other entry points if it's an entrypoint. // If the resulting chunk is empty, it will be removed in generateBundle. moduleSideEffects: - config.command === 'build' && this.getModuleInfo(id)?.isEntry + // config.command === 'build' && + this.getModuleInfo(id)?.isEntry ? 'no-treeshake' : false, - meta: config.command === 'build' ? { 'vite:asset': true } : undefined, + meta: /* config.command === 'build' ? */ { 'vite:asset': true } /* : undefined */, moduleType: 'js', // NOTE: needs to be set to avoid double `export default` in `.txt`s } }, @@ -287,7 +288,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin { // do not emit assets for SSR build if ( - config.command === 'build' && + // config.command === 'build' && !this.environment.config.build.emitAssets ) { for (const file in bundle) { @@ -308,12 +309,12 @@ export async function fileToUrl( pluginContext: PluginContext, id: string, ): Promise { - const { environment } = pluginContext - if (environment.config.command === 'serve') { - return fileToDevUrl(environment, id) - } else { + // const { environment } = pluginContext + // if (environment.config.command === 'serve') { + // return fileToDevUrl(environment, id) + // } else { return fileToBuiltUrl(pluginContext, id) - } + // } } export async function fileToDevUrl( @@ -380,10 +381,10 @@ export function publicFileToBuiltUrl( url: string, config: ResolvedConfig, ): string { - if (config.command !== 'build') { - // We don't need relative base or renderBuiltUrl support during dev - return joinUrlSegments(config.decodedBase, url) - } + // if (config.command !== 'build') { + // // We don't need relative base or renderBuiltUrl support during dev + // return joinUrlSegments(config.decodedBase, url) + // } const hash = getHash(url) let cache = publicAssetUrlCache.get(config) if (!cache) { diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index b488e4c1c9415b..b8ac715dcf337f 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -38,7 +38,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { ...config.resolve, root: config.root, isProduction: config.isProduction, - isBuild: config.command === 'build', + isBuild: true , // config.command === 'build' packageCache: config.packageCache, asSrc: true, } diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index a1bc24e90bd0a5..5bbf7963e06170 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -38,7 +38,7 @@ import type { } from 'lightningcss' import type { CustomPluginOptionsVite } from 'types/metadata' import type { EsbuildTransformOptions } from 'types/internal/esbuildOptions' -import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap' +// import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap' import type { EnvironmentModuleNode } from '../server/moduleGraph' import { createToImportMetaURLBasedRelativeRuntime, @@ -48,7 +48,7 @@ import { } from '../build' import type { LibraryOptions } from '../build' import { - CLIENT_PUBLIC_PATH, + // CLIENT_PUBLIC_PATH, CSS_LANGS_RE, DEV_PROD_CONDITION, ESBUILD_MODULES_TARGET, @@ -73,7 +73,7 @@ import { isDataUrl, isExternalUrl, isObject, - joinUrlSegments, + // joinUrlSegments, mergeWithDefaults, normalizePath, processSrcSet, @@ -285,9 +285,9 @@ const postcssConfigCache = new WeakMap< PostCSSConfigResult | null | Promise >() -function encodePublicUrlsInCSS(config: ResolvedConfig) { - return config.command === 'build' -} +// function encodePublicUrlsInCSS(config: ResolvedConfig) { +// return config.command === 'build' +// } const cssUrlAssetRE = /__VITE_CSS_URL__([\da-f]+)__/g @@ -295,7 +295,7 @@ const cssUrlAssetRE = /__VITE_CSS_URL__([\da-f]+)__/g * Plugin applied before user plugins */ export function cssPlugin(config: ResolvedConfig): Plugin { - const isBuild = config.command === 'build' + // const isBuild = config.command === 'build' let moduleCache: Map> const idResolver = createBackCompatIdResolver(config, { @@ -352,7 +352,7 @@ export function cssPlugin(config: ResolvedConfig): Plugin { // *.css?url // in dev, it's handled by assets plugin. - if (isBuild) { + // if (isBuild) { id = injectQuery(removeUrlQuery(id), 'transform-only') return ( `import ${JSON.stringify(id)};` + @@ -360,7 +360,7 @@ export function cssPlugin(config: ResolvedConfig): Plugin { 'hex', )}__"` ) - } + // } } }, }, @@ -390,11 +390,11 @@ export function cssPlugin(config: ResolvedConfig): Plugin { const urlResolver: CssUrlResolver = async (url, importer) => { const decodedUrl = decodeURI(url) if (checkPublicFile(decodedUrl, config)) { - if (encodePublicUrlsInCSS(config)) { + // if (encodePublicUrlsInCSS(config)) { return [publicFileToBuiltUrl(decodedUrl, config), undefined] - } else { - return [joinUrlSegments(config.base, decodedUrl), undefined] - } + // } else { + // return [joinUrlSegments(config.base, decodedUrl), undefined] + // } } const [id, fragment] = decodedUrl.split('#') let resolved = await resolveUrl(id, importer) @@ -402,7 +402,7 @@ export function cssPlugin(config: ResolvedConfig): Plugin { if (fragment) resolved += '#' + fragment return [await fileToUrl(this, resolved), resolved] } - if (config.command === 'build') { + // if (config.command === 'build') { const isExternal = config.build.rollupOptions.external ? resolveUserExternal( config.build.rollupOptions.external, @@ -418,7 +418,7 @@ export function cssPlugin(config: ResolvedConfig): Plugin { `\n${decodedUrl} referenced in ${id} didn't resolve at build time, it will remain unchanged to be resolved at runtime`, ) } - } + // } return [url, undefined] } @@ -577,46 +577,47 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { !inlined && dataToEsm(modules, { namedExports: true, preferConst: true }) - if (config.command === 'serve') { - const getContentWithSourcemap = async (content: string) => { - if (config.css.devSourcemap) { - const sourcemap = this.getCombinedSourcemap() - if (sourcemap.mappings) { - await injectSourcesContent( - sourcemap, - cleanUrl(id), - config.logger, - ) - } - return getCodeWithSourcemap('css', content, sourcemap) - } - return content - } - - if (isDirectCSSRequest(id)) { - return null - } - if (inlined) { - return `export default ${JSON.stringify(css)}` - } - if (this.environment.config.consumer === 'server') { - return modulesCode || 'export {}' - } - - const cssContent = await getContentWithSourcemap(css) - const code = [ - `import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify( - path.posix.join(config.base, CLIENT_PUBLIC_PATH), - )}`, - `const __vite__id = ${JSON.stringify(id)}`, - `const __vite__css = ${JSON.stringify(cssContent)}`, - `__vite__updateStyle(__vite__id, __vite__css)`, - // css modules exports change on edit so it can't self accept - `${modulesCode || 'import.meta.hot.accept()'}`, - `import.meta.hot.prune(() => __vite__removeStyle(__vite__id))`, - ].join('\n') - return { code, map: { mappings: '' } } - } + // TODO(underfin): css hmr + // if (config.command === 'serve') { + // const getContentWithSourcemap = async (content: string) => { + // if (config.css.devSourcemap) { + // const sourcemap = this.getCombinedSourcemap() + // if (sourcemap.mappings) { + // await injectSourcesContent( + // sourcemap, + // cleanUrl(id), + // config.logger, + // ) + // } + // return getCodeWithSourcemap('css', content, sourcemap) + // } + // return content + // } + + // if (isDirectCSSRequest(id)) { + // return null + // } + // if (inlined) { + // return `export default ${JSON.stringify(css)}` + // } + // if (this.environment.config.consumer === 'server') { + // return modulesCode || 'export {}' + // } + + // const cssContent = await getContentWithSourcemap(css) + // const code = [ + // `import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify( + // path.posix.join(config.base, CLIENT_PUBLIC_PATH), + // )}`, + // `const __vite__id = ${JSON.stringify(id)}`, + // `const __vite__css = ${JSON.stringify(cssContent)}`, + // `__vite__updateStyle(__vite__id, __vite__css)`, + // // css modules exports change on edit so it can't self accept + // `${modulesCode || 'import.meta.hot.accept()'}`, + // `import.meta.hot.prune(() => __vite__removeStyle(__vite__id))`, + // ].join('\n') + // return { code, map: { mappings: '' } } + // } // build CSS handling ---------------------------------------------------- @@ -707,7 +708,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { chunkCSS: string, cssAssetName: string, ) => { - const encodedPublicUrls = encodePublicUrlsInCSS(config) + const encodedPublicUrls = true const relative = config.base === './' || config.base === '' const cssAssetDirname = @@ -3514,9 +3515,10 @@ async function compileLightningCSS( }, minify: config.isProduction && !!config.build.cssMinify, sourceMap: - config.command === 'build' - ? !!config.build.sourcemap - : config.css.devSourcemap, + // config.command === 'build' ? + !!config.build.sourcemap, + // TODO(underfin): deprecate css.devSourcemap + // : config.css.devSourcemap, analyzeDependencies: true, cssModules: cssModuleRE.test(id) ? (config.css.lightningcss?.cssModules ?? true) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index ab0abb67d253e8..8e1b899ce461e7 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -205,9 +205,9 @@ export async function replaceDefine( sourceType: 'module', define, sourcemap: - environment.config.command === 'build' - ? !!environment.config.build.sourcemap - : true, + // environment.config.command === 'build' ? + !!environment.config.build.sourcemap + // : true, }) if (result.errors.length > 0) { diff --git a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts index d0558fd44f6ff4..d3e87ec766c630 100644 --- a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts +++ b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts @@ -22,7 +22,7 @@ export function modulePreloadPolyfillPlugin(config: ResolvedConfig): Plugin { handler(_id) { // `isModernFlag` is only available during build since it is resolved by `vite:build-import-analysis` if ( - config.command !== 'build' || + // config.command !== 'build' || this.environment.config.consumer !== 'client' ) { return '' diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 3c3a5c54a810e2..240d61e6abb285 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -285,7 +285,7 @@ export function webWorkerPostPlugin(): Plugin { } export function webWorkerPlugin(config: ResolvedConfig): Plugin { - const isBuild = config.command === 'build' + const isBuild = true const isWorker = config.isWorker return { diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 742fb2e27b11c8..80036f86d51981 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -184,14 +184,14 @@ const workerImportMetaUrlRE = /new\s+(?:Worker|SharedWorker).+new\s+URL.+import\.meta\.url/s export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { - const isBuild = config.command === 'build' + const isBuild = true let workerResolver: ResolveIdFn const fsResolveOptions: InternalResolveOptions = { ...config.resolve, root: config.root, isProduction: config.isProduction, - isBuild: config.command === 'build', + isBuild, packageCache: config.packageCache, asSrc: true, } From 804a636b96f49aaba3060ccdd8ff0d715e6b8204 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 10 Apr 2025 15:11:29 +0800 Subject: [PATCH 08/76] chore: enable loadfallback plugin at dev build --- packages/vite/src/node/plugins/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 574b4e5224738e..fdb496104867b0 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -55,7 +55,7 @@ export async function resolvePlugins( normalPlugins: Plugin[], postPlugins: Plugin[], ): Promise { - const isBuild = config.command === 'build' + const isBuild = true const isWorker = config.isWorker const buildPlugins = isBuild ? await (await import('../build')).resolveBuildPlugins(config) From 809c55357548ad48da03c7920fcc9bbfae317b96 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 10 Apr 2025 15:22:31 +0800 Subject: [PATCH 09/76] chore: disable depOptimizer --- packages/vite/src/node/server/environment.ts | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/server/environment.ts b/packages/vite/src/node/server/environment.ts index 3ce3311e60a501..3f38e45c62c585 100644 --- a/packages/vite/src/node/server/environment.ts +++ b/packages/vite/src/node/server/environment.ts @@ -145,18 +145,18 @@ export class DevEnvironment extends BaseEnvironment { }, ) - const { optimizeDeps } = this.config - if (context.depsOptimizer) { - this.depsOptimizer = context.depsOptimizer - } else if (isDepOptimizationDisabled(optimizeDeps)) { - this.depsOptimizer = undefined - } else { - this.depsOptimizer = ( - optimizeDeps.noDiscovery - ? createExplicitDepsOptimizer - : createDepsOptimizer - )(this) - } + // const { optimizeDeps } = this.config + // if (context.depsOptimizer) { + // this.depsOptimizer = context.depsOptimizer + // } else if (isDepOptimizationDisabled(optimizeDeps)) { + // this.depsOptimizer = undefined + // } else { + // this.depsOptimizer = ( + // optimizeDeps.noDiscovery + // ? createExplicitDepsOptimizer + // : createDepsOptimizer + // )(this) + // } } async init(options?: { From 271586104b29feadcc13e1fe23fa9e64e0eacace Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 10 Apr 2025 15:36:48 +0800 Subject: [PATCH 10/76] fix: set correct header for memory files --- packages/vite/src/node/server/environment.ts | 10 +++++----- .../vite/src/node/server/middlewares/memoryFiles.ts | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/server/environment.ts b/packages/vite/src/node/server/environment.ts index 3f38e45c62c585..7199c6c28a0759 100644 --- a/packages/vite/src/node/server/environment.ts +++ b/packages/vite/src/node/server/environment.ts @@ -13,11 +13,11 @@ import type { import { mergeConfig } from '../utils' import { fetchModule } from '../ssr/fetchModule' import type { DepsOptimizer } from '../optimizer' -import { isDepOptimizationDisabled } from '../optimizer' -import { - createDepsOptimizer, - createExplicitDepsOptimizer, -} from '../optimizer/optimizer' +// import { isDepOptimizationDisabled } from '../optimizer' +// import { +// createDepsOptimizer, +// createExplicitDepsOptimizer, +// } from '../optimizer/optimizer' import { resolveEnvironmentPlugins } from '../plugin' import { ERR_OUTDATED_OPTIMIZED_DEP } from '../../shared/constants' import { promiseWithResolvers } from '../../shared/utils' diff --git a/packages/vite/src/node/server/middlewares/memoryFiles.ts b/packages/vite/src/node/server/middlewares/memoryFiles.ts index 6f88d1cc383b69..7ead17eaec4cfe 100644 --- a/packages/vite/src/node/server/middlewares/memoryFiles.ts +++ b/packages/vite/src/node/server/middlewares/memoryFiles.ts @@ -1,4 +1,5 @@ import type { Connect } from 'dep-types/connect' +import * as mrmime from 'mrmime' import { cleanUrl } from '../../../shared/utils' import type { ViteDevServer } from '..' @@ -14,7 +15,13 @@ export function memoryFilesMiddleware(server: ViteDevServer, handleHtml: boolean ) { if (cleanedUrl.endsWith('.js')) { res.setHeader('Content-Type', 'text/javascript') + } else { + const mime = mrmime.lookup(cleanedUrl) + if (mime) { + res.setHeader('Content-Type', mime) + } } + const headers = server.config.server.headers if (headers) { for (const name in headers) { From bba3e73b324eaba1c38e538fc82f0de1964b8d29 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 10 Apr 2025 17:11:46 +0800 Subject: [PATCH 11/76] chore: make test work, avoid overrides vitest vite --- package.json | 3 +- playground/hmr/file-delete-restore/index.js | 7 ++- playground/vitestSetup.ts | 2 +- pnpm-lock.yaml | 62 ++++++++++++++++----- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 13cf92b7201e4c..0022bf9a6bbdb0 100644 --- a/package.json +++ b/package.json @@ -98,8 +98,9 @@ "packageManager": "pnpm@10.10.0", "pnpm": { "overrides": { + "vitest>vite": "npm:vite@^6.2.6", "vite": "workspace:rolldown-vite@*" - }, + }, "patchedDependencies": { "http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch", "sirv@3.0.1": "patches/sirv@3.0.1.patch", diff --git a/playground/hmr/file-delete-restore/index.js b/playground/hmr/file-delete-restore/index.js index fa4908a32662ac..4e4dd963730774 100644 --- a/playground/hmr/file-delete-restore/index.js +++ b/playground/hmr/file-delete-restore/index.js @@ -1,4 +1,5 @@ -import { render } from './runtime' -import { childValue, parentValue } from './parent' +// TODO(underfin): https://github.com/rolldown/rolldown/issues/4061 +// import { render } from './runtime' +// import { childValue, parentValue } from './parent' -render({ parent: parentValue, child: childValue }) +// render({ parent: parentValue, child: childValue }) diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index bee9ced78f9bf3..ada72439094c79 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -261,7 +261,7 @@ export async function startDefaultServe(): Promise { }, ) if (buildConfig.builder) { - const builder = await createBuilder(buildConfig) + const builder = await createBuilder(buildConfig, null, 'build') await builder.buildApp() } else { const rollupOutput = await build(buildConfig) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78b70c6568bbfc..f3217ca4590619 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: + vitest>vite: npm:vite@^6.2.6 vite: workspace:rolldown-vite@* packageExtensionsChecksum: sha256-BLDZCgUIohvBXMHo3XFOlGLzGXRyK3sDU0nMBRk9APY= @@ -7533,11 +7534,12 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true +<<<<<<< HEAD vitepress-plugin-group-icons@1.5.2: resolution: {integrity: sha512-zen07KxZ83y3eecou4EraaEgwIriwHaB5Q0cHAmS4yO1UZEQvbljTylHPqiJ7LNkV39U8VehfcyquAJXg/26LA==} - vitepress-plugin-llms@1.1.4: - resolution: {integrity: sha512-/+xrVWpd03B5bGsWN+R+TN0P3kqM/jaDWiddmpFxPuw7vJdoE9mAqAZx1ywzhHEeBCepDMt069l1VRyitmEU6A==} + vitepress-plugin-llms@1.1.0: + resolution: {integrity: sha512-nb7bG/lBDihlcFTzqxRxQIyzeBWQW9F6OwuUWQ7PFUNK5kVbybxXGISU4wvAV8osQmfrD9xNIGJQfuOLj5CzHg==} vitepress@1.6.3: resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} @@ -9926,13 +9928,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.3(vite@packages+vite)': + '@vitest/mocker@3.1.1(vite@packages+vite)': dependencies: '@vitest/spy': 3.1.3 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: link:packages/vite + vite: 6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.29.3)(sass-embedded@1.85.1(source-map-js@1.2.1))(sass@1.85.1)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) '@vitest/pretty-format@3.1.3': dependencies: @@ -13844,7 +13846,30 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD vitepress-plugin-group-icons@1.5.2: +======= + vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.29.3)(sass-embedded@1.85.1(source-map-js@1.2.1))(sass@1.85.1)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + dependencies: + esbuild: 0.25.0 + postcss: 8.5.3 + rollup: 4.34.9 + optionalDependencies: + '@types/node': 22.13.6 + fsevents: 2.3.3 + jiti: 2.4.2 + less: 4.2.2 + lightningcss: 1.29.3 + sass: 1.85.1 + sass-embedded: 1.85.1(source-map-js@1.2.1) + stylus: 0.64.0 + sugarss: 5.0.0(postcss@8.5.3) + terser: 5.39.0 + tsx: 4.19.3 + yaml: 2.7.0 + + vitepress-plugin-group-icons@1.3.7: +>>>>>>> a2d094f19 (chore: make test work, avoid overrides vitest vite) dependencies: '@iconify-json/logos': 1.2.4 '@iconify-json/vscode-icons': 1.2.19 @@ -13909,15 +13934,15 @@ snapshots: - typescript - universal-cookie - vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18): + vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.13.6): dependencies: - '@vitest/expect': 3.1.3 - '@vitest/mocker': 3.1.3(vite@packages+vite) - '@vitest/pretty-format': 3.1.3 - '@vitest/runner': 3.1.3 - '@vitest/snapshot': 3.1.3 - '@vitest/spy': 3.1.3 - '@vitest/utils': 3.1.3 + '@vitest/expect': 3.1.1 + '@vitest/mocker': 3.1.1(vite@packages+vite) + '@vitest/pretty-format': 3.1.1 + '@vitest/runner': 3.1.1 + '@vitest/snapshot': 3.1.1 + '@vitest/spy': 3.1.1 + '@vitest/utils': 3.1.1 chai: 5.2.0 debug: 4.4.0 expect-type: 1.2.1 @@ -13929,15 +13954,26 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 +<<<<<<< HEAD vite: link:packages/vite - vite-node: 3.1.3 + vite-node: 3.1.1 why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 '@types/node': 22.15.18 transitivePeerDependencies: + - jiti + - less + - lightningcss - msw + - sass + - sass-embedded + - stylus + - sugarss - supports-color + - terser + - tsx + - yaml void-elements@3.1.0: {} From 0a5a51a0b9cdb3229dbea6794b8b5049a146cdf0 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 10 Apr 2025 17:26:08 +0800 Subject: [PATCH 12/76] chore: avoid watcher outdir --- packages/vite/src/node/watch.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/watch.ts b/packages/vite/src/node/watch.ts index 63f61db51da254..01fdbd7900eccf 100644 --- a/packages/vite/src/node/watch.ts +++ b/packages/vite/src/node/watch.ts @@ -51,7 +51,7 @@ export function resolveEmptyOutDir( export function resolveChokidarOptions( options: WatchOptions | undefined, resolvedOutDirs: Set, - emptyOutDir: boolean, + _emptyOutDir: boolean, cacheDir: string, ): WatchOptions { const { ignored: ignoredList, ...otherOptions } = options ?? {} @@ -62,11 +62,12 @@ export function resolveChokidarOptions( escapePath(cacheDir) + '/**', ...arraify(ignoredList || []), ] - if (emptyOutDir) { + // TODO(underfin): revert it if the dev build only write output to memory. + // if (emptyOutDir) { ignored.push( ...[...resolvedOutDirs].map((outDir) => escapePath(outDir) + '/**'), ) - } + // } const resolvedWatchOptions: WatchOptions = { ignored, From 8a6293d368eb0166f356441e23d42a9a25a77480 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 14 Apr 2025 16:07:21 +0800 Subject: [PATCH 13/76] chore: fix lint --- package.json | 2 +- packages/vite/src/node/build.ts | 46 ++--- packages/vite/src/node/cli.ts | 178 ++++++++++-------- packages/vite/src/node/plugins/asset.ts | 12 +- .../src/node/plugins/assetImportMetaUrl.ts | 2 +- packages/vite/src/node/plugins/css.ts | 50 ++--- packages/vite/src/node/plugins/define.ts | 4 +- packages/vite/src/node/plugins/index.ts | 8 +- .../src/node/plugins/modulePreloadPolyfill.ts | 2 +- packages/vite/src/node/server/hmr.ts | 5 +- packages/vite/src/node/server/index.ts | 100 +++++----- .../src/node/server/middlewares/indexHtml.ts | 22 ++- .../node/server/middlewares/memoryFiles.ts | 53 +++--- packages/vite/src/node/server/ws.ts | 2 +- packages/vite/src/node/watch.ts | 6 +- 15 files changed, 256 insertions(+), 236 deletions(-) diff --git a/package.json b/package.json index 0022bf9a6bbdb0..f5d544ab609552 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "overrides": { "vitest>vite": "npm:vite@^6.2.6", "vite": "workspace:rolldown-vite@*" - }, + }, "patchedDependencies": { "http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch", "sirv@3.0.1": "patches/sirv@3.0.1.patch", diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 598347754e263e..07a3d307e8d84d 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -671,12 +671,12 @@ async function buildEnvironment( '.css': 'js', }, experimental: { - hmr: true + hmr: true, // hmr: server ? { // host: server._currentServerHost!, // port: server._currentServerPort!, // } : false, - } + }, } /** @@ -819,13 +819,14 @@ async function buildEnvironment( output.format === 'iife' || (isSsrTargetWebworkerEnvironment && (typeof input === 'string' || Object.keys(input).length === 1)), - minify: mode === 'production' ? - options.minify === 'oxc' - ? true - : options.minify === false - ? 'dce-only' - : false - : false, + minify: + mode === 'production' + ? options.minify === 'oxc' + ? true + : options.minify === false + ? 'dce-only' + : false + : false, ...output, } } @@ -918,7 +919,7 @@ async function buildEnvironment( // TODO(underfin): using the generate at development build could be improve performance. res.push(await bundle![options.write ? 'write' : 'generate'](output)) } - + if (server) { // watching the files for (const file of bundle!.watchFiles) { @@ -928,34 +929,34 @@ async function buildEnvironment( } // Write the output files to memory - for(const output of res) { + for (const output of res) { for (const outputFile of output.output) { - server.memoryFiles[outputFile.fileName] = outputFile.type === 'chunk' ? outputFile.code : outputFile.source; + server.memoryFiles[outputFile.fileName] = + outputFile.type === 'chunk' ? outputFile.code : outputFile.source } } } return res } - if (server) { server.watcher.on('change', async (file) => { const startTime = Date.now() - const patch = await bundle!.generateHmrPatch([file]); + const patch = await bundle!.generateHmrPatch([file]) if (patch) { - const url = `${Date.now()}.js`; - server.memoryFiles[url] = patch; + const url = `${Date.now()}.js` + server.memoryFiles[url] = patch // TODO(underfin): fix ws msg typing - // @ts-expect-error + // @ts-expect-error fix ws msg typing server.ws.send({ type: 'update', - url - }); + url, + }) logger.info( `${colors.green(`✓ Found ${path.relative(root, file)} changed, rebuilt in ${displayTime(Date.now() - startTime)}`)}`, ) - await build(); + await build() } }) } @@ -1712,7 +1713,10 @@ export interface BuilderOptions { buildApp?: (builder: ViteBuilder, server?: ViteDevServer) => Promise } -async function defaultBuildApp(builder: ViteBuilder, server?: ViteDevServer): Promise { +async function defaultBuildApp( + builder: ViteBuilder, + server?: ViteDevServer, +): Promise { for (const environment of Object.values(builder.environments)) { await builder.build(environment, server) } diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 8f00bb6f018a44..b8e7712674e2df 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -177,64 +177,72 @@ cli `[boolean] force the optimizer to ignore the cache and re-bundle`, ) // TODO(underfin): Consider how to merge the build option into dev command. - .action(async (root: string, options: BuildEnvironmentOptions & BuilderCLIOptions & ServerOptions & GlobalCLIOptions) => { - filterDuplicateOptions(options) - // output structure is preserved even after bundling so require() - // is ok here - const { createServer } = await import('./server') - try { - const server = await createServer({ - root, - base: options.base, - mode: options.mode, - configFile: options.config, - configLoader: options.configLoader, - logLevel: options.logLevel, - clearScreen: options.clearScreen, - server: cleanGlobalCLIOptions(options), - forceOptimizeDeps: options.force, - }) + .action( + async ( + root: string, + options: BuildEnvironmentOptions & + BuilderCLIOptions & + ServerOptions & + GlobalCLIOptions, + ) => { + filterDuplicateOptions(options) + // output structure is preserved even after bundling so require() + // is ok here + const { createServer } = await import('./server') + try { + const server = await createServer({ + root, + base: options.base, + mode: options.mode, + configFile: options.config, + configLoader: options.configLoader, + logLevel: options.logLevel, + clearScreen: options.clearScreen, + server: cleanGlobalCLIOptions(options), + forceOptimizeDeps: options.force, + }) - if (!server.httpServer) { - throw new Error('HTTP server not available') - } + if (!server.httpServer) { + throw new Error('HTTP server not available') + } - const { createBuilder } = await import('./build') + const { createBuilder } = await import('./build') - const buildOptions: BuildEnvironmentOptions = cleanBuilderCLIOptions(options) + const buildOptions: BuildEnvironmentOptions = + cleanBuilderCLIOptions(options) - const inlineConfig: InlineConfig = { - root, - base: options.base, - mode: options.mode, - configFile: options.config, - configLoader: options.configLoader, - logLevel: options.logLevel, - clearScreen: options.clearScreen, - build: buildOptions, - ...(options.app ? { builder: {} } : {}), - } - const builder = await createBuilder(inlineConfig, null, 'serve') - await builder.buildApp(server) + const inlineConfig: InlineConfig = { + root, + base: options.base, + mode: options.mode, + configFile: options.config, + configLoader: options.configLoader, + logLevel: options.logLevel, + clearScreen: options.clearScreen, + build: buildOptions, + ...(options.app ? { builder: {} } : {}), + } + const builder = await createBuilder(inlineConfig, null, 'serve') + await builder.buildApp(server) - await server.listen() + await server.listen() - const info = server.config.logger.info + const info = server.config.logger.info - const modeString = - options.mode && options.mode !== 'development' - ? ` ${colors.bgGreen(` ${colors.bold(options.mode)} `)}` + const modeString = + options.mode && options.mode !== 'development' + ? ` ${colors.bgGreen(` ${colors.bold(options.mode)} `)}` + : '' + const viteStartTime = global.__vite_start_time ?? false + const startupDurationString = viteStartTime + ? colors.dim( + `ready in ${colors.reset( + colors.bold(Math.ceil(performance.now() - viteStartTime)), + )} ms`, + ) : '' - const viteStartTime = global.__vite_start_time ?? false - const startupDurationString = viteStartTime - ? colors.dim( - `ready in ${colors.reset( - colors.bold(Math.ceil(performance.now() - viteStartTime)), - )} ms`, - ) - : '' - const hasExistingLogs = - process.stdout.bytesWritten > 0 || process.stderr.bytesWritten > 0 + const hasExistingLogs = + process.stdout.bytesWritten > 0 || process.stderr.bytesWritten > 0 info( `\n ${colors.green( @@ -245,43 +253,47 @@ cli }, ) - server.printUrls() - const customShortcuts: CLIShortcut[] = [] - if (profileSession) { - customShortcuts.push({ - key: 'p', - description: 'start/stop the profiler', - async action(server) { - if (profileSession) { - await stopProfiler(server.config.logger.info) - } else { - const inspector = await import('node:inspector').then( - (r) => r.default, - ) - await new Promise((res) => { - profileSession = new inspector.Session() - profileSession.connect() - profileSession.post('Profiler.enable', () => { - profileSession!.post('Profiler.start', () => { - server.config.logger.info('Profiler started') - res() + server.printUrls() + const customShortcuts: CLIShortcut[] = [] + if (profileSession) { + customShortcuts.push({ + key: 'p', + description: 'start/stop the profiler', + async action(server) { + if (profileSession) { + await stopProfiler(server.config.logger.info) + } else { + const inspector = await import('node:inspector').then( + (r) => r.default, + ) + await new Promise((res) => { + profileSession = new inspector.Session() + profileSession.connect() + profileSession.post('Profiler.enable', () => { + profileSession!.post('Profiler.start', () => { + server.config.logger.info('Profiler started') + res() + }) }) }) - }) - } + } + }, + }) + } + server.bindCLIShortcuts({ print: true, customShortcuts }) + } catch (e) { + const logger = createLogger(options.logLevel) + logger.error( + colors.red(`error when starting dev server:\n${e.stack}`), + { + error: e, }, - }) + ) + stopProfiler(logger.info) + process.exit(1) } - server.bindCLIShortcuts({ print: true, customShortcuts }) - } catch (e) { - const logger = createLogger(options.logLevel) - logger.error(colors.red(`error when starting dev server:\n${e.stack}`), { - error: e, - }) - stopProfiler(logger.info) - process.exit(1) - } - }) + }, + ) // build cli diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 4501dca145463d..f3386242e7fb0d 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -229,11 +229,11 @@ export function assetPlugin(config: ResolvedConfig): Plugin { // Force rollup to keep this module from being shared between other entry points if it's an entrypoint. // If the resulting chunk is empty, it will be removed in generateBundle. moduleSideEffects: - // config.command === 'build' && - this.getModuleInfo(id)?.isEntry - ? 'no-treeshake' - : false, - meta: /* config.command === 'build' ? */ { 'vite:asset': true } /* : undefined */, + // config.command === 'build' && + this.getModuleInfo(id)?.isEntry ? 'no-treeshake' : false, + meta: /* config.command === 'build' ? */ { + 'vite:asset': true, + } /* : undefined */, moduleType: 'js', // NOTE: needs to be set to avoid double `export default` in `.txt`s } }, @@ -313,7 +313,7 @@ export async function fileToUrl( // if (environment.config.command === 'serve') { // return fileToDevUrl(environment, id) // } else { - return fileToBuiltUrl(pluginContext, id) + return fileToBuiltUrl(pluginContext, id) // } } diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index b8ac715dcf337f..ab80ecea190cba 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -38,7 +38,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { ...config.resolve, root: config.root, isProduction: config.isProduction, - isBuild: true , // config.command === 'build' + isBuild: true, // config.command === 'build' packageCache: config.packageCache, asSrc: true, } diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 5bbf7963e06170..0281d2458fb5dd 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -353,13 +353,13 @@ export function cssPlugin(config: ResolvedConfig): Plugin { // *.css?url // in dev, it's handled by assets plugin. // if (isBuild) { - id = injectQuery(removeUrlQuery(id), 'transform-only') - return ( - `import ${JSON.stringify(id)};` + - `export default "__VITE_CSS_URL__${Buffer.from(id).toString( - 'hex', - )}__"` - ) + id = injectQuery(removeUrlQuery(id), 'transform-only') + return ( + `import ${JSON.stringify(id)};` + + `export default "__VITE_CSS_URL__${Buffer.from(id).toString( + 'hex', + )}__"` + ) // } } }, @@ -391,7 +391,7 @@ export function cssPlugin(config: ResolvedConfig): Plugin { const decodedUrl = decodeURI(url) if (checkPublicFile(decodedUrl, config)) { // if (encodePublicUrlsInCSS(config)) { - return [publicFileToBuiltUrl(decodedUrl, config), undefined] + return [publicFileToBuiltUrl(decodedUrl, config), undefined] // } else { // return [joinUrlSegments(config.base, decodedUrl), undefined] // } @@ -403,21 +403,21 @@ export function cssPlugin(config: ResolvedConfig): Plugin { return [await fileToUrl(this, resolved), resolved] } // if (config.command === 'build') { - const isExternal = config.build.rollupOptions.external - ? resolveUserExternal( - config.build.rollupOptions.external, - decodedUrl, // use URL as id since id could not be resolved - id, - false, - ) - : false - - if (!isExternal) { - // #9800 If we cannot resolve the css url, leave a warning. - config.logger.warnOnce( - `\n${decodedUrl} referenced in ${id} didn't resolve at build time, it will remain unchanged to be resolved at runtime`, + const isExternal = config.build.rollupOptions.external + ? resolveUserExternal( + config.build.rollupOptions.external, + decodedUrl, // use URL as id since id could not be resolved + id, + false, ) - } + : false + + if (!isExternal) { + // #9800 If we cannot resolve the css url, leave a warning. + config.logger.warnOnce( + `\n${decodedUrl} referenced in ${id} didn't resolve at build time, it will remain unchanged to be resolved at runtime`, + ) + } // } return [url, undefined] } @@ -3516,9 +3516,9 @@ async function compileLightningCSS( minify: config.isProduction && !!config.build.cssMinify, sourceMap: // config.command === 'build' ? - !!config.build.sourcemap, - // TODO(underfin): deprecate css.devSourcemap - // : config.css.devSourcemap, + !!config.build.sourcemap, + // TODO(underfin): deprecate css.devSourcemap + // : config.css.devSourcemap, analyzeDependencies: true, cssModules: cssModuleRE.test(id) ? (config.css.lightningcss?.cssModules ?? true) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index 8e1b899ce461e7..cd9d925a9cdc90 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -206,8 +206,8 @@ export async function replaceDefine( define, sourcemap: // environment.config.command === 'build' ? - !!environment.config.build.sourcemap - // : true, + !!environment.config.build.sourcemap, + // : true, }) if (result.errors.length > 0) { diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index fdb496104867b0..32b87226181fc5 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -25,9 +25,11 @@ import { jsonPlugin } from './json' import { oxcResolvePlugin, resolvePlugin } from './resolve' // import { optimizedDepsPlugin } from './optimizedDeps' // import { importAnalysisPlugin } from './importAnalysis' -import { +import { // cssAnalysisPlugin, - cssPlugin, cssPostPlugin } from './css' + cssPlugin, + cssPostPlugin, +} from './css' import { assetPlugin } from './asset' // import { clientInjectionsPlugin } from './clientInjections' import { buildHtmlPlugin, htmlInlineProxyPlugin } from './html' @@ -174,7 +176,7 @@ export async function resolvePlugins( : wasmFallbackPlugin(), definePlugin(config), cssPostPlugin(config), - // isBuild && + // isBuild && buildHtmlPlugin(config), workerImportMetaUrlPlugin(config), assetImportMetaUrlPlugin(config), diff --git a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts index d3e87ec766c630..3647a0849c4767 100644 --- a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts +++ b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts @@ -6,7 +6,7 @@ import { isModernFlag } from './importAnalysisBuild' export const modulePreloadPolyfillId = 'vite/modulepreload-polyfill' const resolvedModulePreloadPolyfillId = '\0' + modulePreloadPolyfillId + '.js' -export function modulePreloadPolyfillPlugin(config: ResolvedConfig): Plugin { +export function modulePreloadPolyfillPlugin(_config: ResolvedConfig): Plugin { let polyfillString: string | undefined return { diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 44d2eb32840eb8..987ef66b4f1ce6 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -10,8 +10,9 @@ import type { InvokeSendData, } from '../../shared/invokeMethods' // import { CLIENT_DIR } from '../constants' -import { createDebugger, - // normalizePath +import { + createDebugger, + // normalizePath } from '../utils' import type { InferCustomEventPayload, ViteDevServer } from '..' // import { getHookHandler } from '../plugins' diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index c3630ef378a39d..606ff462a762e0 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -67,16 +67,12 @@ import { // import { // indexHtmlMiddleware, // } from './middlewares/indexHtml' -import { - servePublicMiddleware, -} from './middlewares/static' +import { servePublicMiddleware } from './middlewares/static' import { timeMiddleware } from './middlewares/time' import { notFoundMiddleware } from './middlewares/notFound' -import { errorMiddleware } from './middlewares/error' +import { errorMiddleware } from './middlewares/error' import type { HmrOptions, HotBroadcaster } from './hmr' -import { - createDeprecatedHotBroadcaster, -} from './hmr' +import { createDeprecatedHotBroadcaster } from './hmr' import { openBrowser as _openBrowser } from './openBrowser' import { searchForPackageRoot, searchForWorkspaceRoot } from './searchRoot' import type { DevEnvironment } from './environment' @@ -406,7 +402,7 @@ export interface ViteDevServer { * @internal */ _ssrCompatModuleRunner?: ModuleRunner - /** + /** * @internal */ memoryFiles: Record @@ -779,51 +775,51 @@ export async function _createServer( // } // const onFileAddUnlink = async (file: string, isUnlink: boolean) => { - // file = normalizePath(file) - // reloadOnTsconfigChange(server, file) - - // await pluginContainer.watchChange(file, { - // event: isUnlink ? 'delete' : 'create', - // }) - - // if (publicDir && publicFiles) { - // if (file.startsWith(publicDir)) { - // const path = file.slice(publicDir.length) - // publicFiles[isUnlink ? 'delete' : 'add'](path) - // if (!isUnlink) { - // const clientModuleGraph = server.environments.client.moduleGraph - // const moduleWithSamePath = - // await clientModuleGraph.getModuleByUrl(path) - // const etag = moduleWithSamePath?.transformResult?.etag - // if (etag) { - // // The public file should win on the next request over a module with the - // // same path. Prevent the transform etag fast path from serving the module - // clientModuleGraph.etagToModuleMap.delete(etag) - // } - // } - // } - // } - // if (isUnlink) { - // // invalidate module graph cache on file change - // for (const environment of Object.values(server.environments)) { - // environment.moduleGraph.onFileDelete(file) - // } - // } - // await onHMRUpdate(isUnlink ? 'delete' : 'create', file) + // file = normalizePath(file) + // reloadOnTsconfigChange(server, file) + + // await pluginContainer.watchChange(file, { + // event: isUnlink ? 'delete' : 'create', + // }) + + // if (publicDir && publicFiles) { + // if (file.startsWith(publicDir)) { + // const path = file.slice(publicDir.length) + // publicFiles[isUnlink ? 'delete' : 'add'](path) + // if (!isUnlink) { + // const clientModuleGraph = server.environments.client.moduleGraph + // const moduleWithSamePath = + // await clientModuleGraph.getModuleByUrl(path) + // const etag = moduleWithSamePath?.transformResult?.etag + // if (etag) { + // // The public file should win on the next request over a module with the + // // same path. Prevent the transform etag fast path from serving the module + // clientModuleGraph.etagToModuleMap.delete(etag) + // } + // } + // } + // } + // if (isUnlink) { + // // invalidate module graph cache on file change + // for (const environment of Object.values(server.environments)) { + // environment.moduleGraph.onFileDelete(file) + // } + // } + // await onHMRUpdate(isUnlink ? 'delete' : 'create', file) // } // watcher.on('change', async (file) => { - // file = normalizePath(file) - // TODO(underfin): handle ts config.json change - // reloadOnTsconfigChange(server, file) - - // TODO(underfin): watchChange hooks how to migrate - // await pluginContainer.watchChange(file, { event: 'update' }) - // invalidate module graph cache on file change - // for (const environment of Object.values(server.environments)) { - // environment.moduleGraph.onFileChange(file) - // } - // await onHMRUpdate('update', file) + // file = normalizePath(file) + // TODO(underfin): handle ts config.json change + // reloadOnTsconfigChange(server, file) + + // TODO(underfin): watchChange hooks how to migrate + // await pluginContainer.watchChange(file, { event: 'update' }) + // invalidate module graph cache on file change + // for (const environment of Object.values(server.environments)) { + // environment.moduleGraph.onFileChange(file) + // } + // await onHMRUpdate('update', file) // }) // watcher.on('add', (file) => { @@ -913,7 +909,7 @@ export async function _createServer( // middlewares.use(c(server)) // serve memory output dist files - middlewares.use(memoryFilesMiddleware(server, false)); + middlewares.use(memoryFilesMiddleware(server, false)) // html fallback if (config.appType === 'spa' || config.appType === 'mpa') { @@ -928,7 +924,7 @@ export async function _createServer( if (config.appType === 'spa' || config.appType === 'mpa') { // transform index.html // middlewares.use(indexHtmlMiddleware(root, server)) - middlewares.use(memoryFilesMiddleware(server, true)); + middlewares.use(memoryFilesMiddleware(server, true)) // handle 404s middlewares.use(notFoundMiddleware()) diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 77f2dcbbd4f4d8..42168635ea4a7d 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -4,9 +4,9 @@ import path from 'node:path' // import MagicString from 'magic-string' // import type { SourceMapInput } from 'rolldown' import type { Connect } from 'dep-types/connect' -// import type { +// import type { // DefaultTreeAdapterMap, -// Token +// Token // } from 'parse5' // import type { IndexHtmlTransformHook } from '../../plugins/html' // import { @@ -27,13 +27,16 @@ import type { Connect } from 'dep-types/connect' // resolveHtmlTransforms, // traverseHtml, // } from '../../plugins/html' -import type { PreviewServer, +import type { + PreviewServer, // ResolvedConfig, - ViteDevServer } from '../..' + ViteDevServer, +} from '../..' import { send } from '../send' -import { - // CLIENT_PUBLIC_PATH, - FS_PREFIX } from '../../constants' +import { + // CLIENT_PUBLIC_PATH, + FS_PREFIX, +} from '../../constants' import { // ensureWatchedFile, fsPathFromId, @@ -49,8 +52,9 @@ import { // import { checkPublicFile } from '../../publicDir' // import { isCSSRequest } from '../../plugins/css' // import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap' -import { cleanUrl, - // unwrapId, wrapId +import { + cleanUrl, + // unwrapId, wrapId } from '../../../shared/utils' // import { getNodeAssetAttributes } from '../../assetSource' diff --git a/packages/vite/src/node/server/middlewares/memoryFiles.ts b/packages/vite/src/node/server/middlewares/memoryFiles.ts index 7ead17eaec4cfe..5c7cf2680cdb70 100644 --- a/packages/vite/src/node/server/middlewares/memoryFiles.ts +++ b/packages/vite/src/node/server/middlewares/memoryFiles.ts @@ -3,34 +3,35 @@ import * as mrmime from 'mrmime' import { cleanUrl } from '../../../shared/utils' import type { ViteDevServer } from '..' -export function memoryFilesMiddleware(server: ViteDevServer, handleHtml: boolean): Connect.NextHandleFunction { - return function viteMemoryFilesMiddleware(req, res, next) { - const cleanedUrl = cleanUrl(req.url!).slice(1) // remove first / - if (cleanedUrl.endsWith('.html') && !handleHtml) { - return next() - } - const file = server.memoryFiles[cleanedUrl] - if ( - file - ) { - if (cleanedUrl.endsWith('.js')) { - res.setHeader('Content-Type', 'text/javascript') - } else { - const mime = mrmime.lookup(cleanedUrl) - if (mime) { - res.setHeader('Content-Type', mime) - } +export function memoryFilesMiddleware( + server: ViteDevServer, + handleHtml: boolean, +): Connect.NextHandleFunction { + return function viteMemoryFilesMiddleware(req, res, next) { + const cleanedUrl = cleanUrl(req.url!).slice(1) // remove first / + if (cleanedUrl.endsWith('.html') && !handleHtml) { + return next() + } + const file = server.memoryFiles[cleanedUrl] + if (file) { + if (cleanedUrl.endsWith('.js')) { + res.setHeader('Content-Type', 'text/javascript') + } else { + const mime = mrmime.lookup(cleanedUrl) + if (mime) { + res.setHeader('Content-Type', mime) } - - const headers = server.config.server.headers - if (headers) { - for (const name in headers) { - res.setHeader(name, headers[name]!) - } + } + + const headers = server.config.server.headers + if (headers) { + for (const name in headers) { + res.setHeader(name, headers[name]!) } - - return res.end(file) } - next() + + return res.end(file) } + next() + } } diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index ed4f5486069cde..cc32107d6738f3 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -225,7 +225,7 @@ export function createWebSocketServer( // [HMR_HEADER, 'vite-ping'].includes(protocol) && // parsedUrl.pathname === hmrBase // ) { - handleUpgrade(req, socket as Socket, head, protocol === 'vite-ping') + handleUpgrade(req, socket as Socket, head, protocol === 'vite-ping') // } } wsServer.on('upgrade', hmrServerWsListener) diff --git a/packages/vite/src/node/watch.ts b/packages/vite/src/node/watch.ts index 01fdbd7900eccf..62530b1f8370c0 100644 --- a/packages/vite/src/node/watch.ts +++ b/packages/vite/src/node/watch.ts @@ -64,9 +64,9 @@ export function resolveChokidarOptions( ] // TODO(underfin): revert it if the dev build only write output to memory. // if (emptyOutDir) { - ignored.push( - ...[...resolvedOutDirs].map((outDir) => escapePath(outDir) + '/**'), - ) + ignored.push( + ...[...resolvedOutDirs].map((outDir) => escapePath(outDir) + '/**'), + ) // } const resolvedWatchOptions: WatchOptions = { From d47db7c32c34bf58814a95a266978cbe2ba14e96 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 14 Apr 2025 16:26:39 +0800 Subject: [PATCH 14/76] fix: some isBuild using production mode --- packages/vite/src/node/config.ts | 2 +- packages/vite/src/node/idResolver.ts | 4 ++-- .../src/node/plugins/assetImportMetaUrl.ts | 2 +- packages/vite/src/node/plugins/css.ts | 20 ++++++++--------- packages/vite/src/node/plugins/define.ts | 22 +++++++++---------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 62c0db4b81d5ed..c9db1fc3a10512 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1310,7 +1310,7 @@ export async function resolveConfig( const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawPlugins) - const isBuild = command === 'build' + const isBuild = mode === 'production' // run config hooks const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins] diff --git a/packages/vite/src/node/idResolver.ts b/packages/vite/src/node/idResolver.ts index 6f43238ded87e2..57b8d442d3fc4f 100644 --- a/packages/vite/src/node/idResolver.ts +++ b/packages/vite/src/node/idResolver.ts @@ -66,7 +66,7 @@ export function createIdResolver( { root: config.root, isProduction: config.isProduction, - isBuild: config.command === 'build', + isBuild: config.mode === 'production', asSrc: true, preferRelative: false, tryIndex: true, @@ -80,7 +80,7 @@ export function createIdResolver( resolvePlugin({ root: config.root, isProduction: config.isProduction, - isBuild: config.command === 'build', + isBuild: config.mode === 'production', asSrc: true, preferRelative: false, tryIndex: true, diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index ab80ecea190cba..3085d5a70f2bfd 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -38,7 +38,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { ...config.resolve, root: config.root, isProduction: config.isProduction, - isBuild: true, // config.command === 'build' + isBuild: config.mode === 'production', packageCache: config.packageCache, asSrc: true, } diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 0281d2458fb5dd..dc83580791c28e 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -295,7 +295,7 @@ const cssUrlAssetRE = /__VITE_CSS_URL__([\da-f]+)__/g * Plugin applied before user plugins */ export function cssPlugin(config: ResolvedConfig): Plugin { - // const isBuild = config.command === 'build' + const isBuild = config.mode === 'production' let moduleCache: Map> const idResolver = createBackCompatIdResolver(config, { @@ -352,15 +352,15 @@ export function cssPlugin(config: ResolvedConfig): Plugin { // *.css?url // in dev, it's handled by assets plugin. - // if (isBuild) { - id = injectQuery(removeUrlQuery(id), 'transform-only') - return ( - `import ${JSON.stringify(id)};` + - `export default "__VITE_CSS_URL__${Buffer.from(id).toString( - 'hex', - )}__"` - ) - // } + if (isBuild) { + id = injectQuery(removeUrlQuery(id), 'transform-only') + return ( + `import ${JSON.stringify(id)};` + + `export default "__VITE_CSS_URL__${Buffer.from(id).toString( + 'hex', + )}__"` + ) + } } }, }, diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index cd9d925a9cdc90..11c487e8f773e2 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,7 +12,7 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - // const isBuild = config.command === 'build' + const isBuild = config.mode === 'production' const isBuildLib = config.build.lib // ignore replace process.env in lib build @@ -33,17 +33,17 @@ export function definePlugin(config: ResolvedConfig): Plugin { const importMetaKeys: Record = {} const importMetaEnvKeys: Record = {} const importMetaFallbackKeys: Record = {} - // if (isBuild) { - importMetaKeys['import.meta.hot'] = `undefined` - for (const key in config.env) { - const val = JSON.stringify(config.env[key]) - importMetaKeys[`import.meta.env.${key}`] = val - importMetaEnvKeys[key] = val + if (isBuild) { + importMetaKeys['import.meta.hot'] = `undefined` + for (const key in config.env) { + const val = JSON.stringify(config.env[key]) + importMetaKeys[`import.meta.env.${key}`] = val + importMetaEnvKeys[key] = val + } + // these will be set to a proper value in `generatePattern` + importMetaKeys['import.meta.env.SSR'] = `undefined` + importMetaFallbackKeys['import.meta.env'] = `undefined` } - // these will be set to a proper value in `generatePattern` - importMetaKeys['import.meta.env.SSR'] = `undefined` - importMetaFallbackKeys['import.meta.env'] = `undefined` - // } function generatePattern(environment: Environment) { const keepProcessEnv = environment.config.keepProcessEnv From 065e40601ba8d6b44ee62c01d055f71e5528a8c6 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 15 Apr 2025 16:10:56 +0800 Subject: [PATCH 15/76] feat: using vite hmr runtime --- packages/vite/src/client/client.ts | 21 ++--- packages/vite/src/client/hmrModuleRunner.ts | 88 +++++++++++++++++++ packages/vite/src/node/build.ts | 8 +- .../vite/src/node/plugins/clientInjections.ts | 63 +++++++++++++ packages/vite/types/hmrPayload.d.ts | 1 + vitest.config.e2e.ts | 3 + 6 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 packages/vite/src/client/hmrModuleRunner.ts diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 1db4a49aa2556a..69a0a7b8363253 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -7,7 +7,8 @@ import { } from '../shared/moduleRunnerTransport' import { createHMRHandler } from '../shared/hmrHandler' import { ErrorOverlay, overlayId } from './overlay' -import '@vite/env' +import './hmrModuleRunner' +// import '@vite/env' // injected by the hmr plugin when served declare const __BASE__: string @@ -140,19 +141,16 @@ const hmrClient = new HMRClient( }, transport, async function importUpdatedModule({ + url, acceptedPath, - timestamp, - explicitImportRequired, + // timestamp, + // explicitImportRequired, isWithinCircularImport, }) { - const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`) + // const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`) const importPromise = import( /* @vite-ignore */ - base + - acceptedPathWithoutQuery.slice(1) + - `?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${ - query ? `&${query}` : '' - }` + base + url ) if (isWithinCircularImport) { importPromise.catch(() => { @@ -163,7 +161,10 @@ const hmrClient = new HMRClient( pageReload() }) } - return await importPromise + // @ts-expect-error globalThis.__rolldown_runtime__ + return await importPromise.then(() => + globalThis.__rolldown_runtime__.loadExports(acceptedPath), + ) }, ) transport.connect!(createHMRHandler(handleMessage)) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts new file mode 100644 index 00000000000000..766855afc4661e --- /dev/null +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -0,0 +1,88 @@ +import { createHotContext } from './client' + +class DevRuntime { + modules: Record = {} + + static getInstance() { + // @ts-expect-error __rolldown_runtime__ + let instance = globalThis.__rolldown_runtime__; + if (!instance) { + instance = new DevRuntime(); + // @ts-expect-error __rolldown_runtime__ + globalThis.__rolldown_runtime__ = instance; + } + return instance + } + + createModuleHotContext(moduleId: string) { + return createHotContext(moduleId); + } + + applyUpdates(_boundaries: string[]) { + // trigger callbacks of accept() correctly + // for (const moduleId of boundaries) { + // const hotContext = this.moduleHotContexts.get(moduleId); + // if (hotContext) { + // const acceptCallbacks = hotContext.acceptCallbacks; + // acceptCallbacks.filter((cb) => { + // cb.fn(this.modules[moduleId].exports); + // }) + // } + // } + // this.moduleHotContextsToBeUpdated.forEach((hotContext, moduleId) => { + // this.moduleHotContexts[moduleId] = hotContext; + // }) + // this.moduleHotContextsToBeUpdated.clear() + // swap new contexts + } + + registerModule(id: string, exportGetters: Record any>) { + const exports = {}; + Object.keys(exportGetters).forEach((key) => { + if (Object.prototype.hasOwnProperty.call(exportGetters, key)) { + Object.defineProperty(exports, key, { + enumerable: true, + get: exportGetters[key], + }); + } + }) + if (this.modules[id]) { + this.modules[id] = { + exports, + } + } else { + // If the module is not in the cache, we need to register it. + this.modules[id] = { + exports, + }; + } + } + + loadExports(id: string) { + const module = this.modules[id]; + if (module) { + return module.exports; + } else { + console.warn(`Module ${id} not found`); + return {}; + } + } + + // __esmMin + // createEsmInitializer = (fn, res) => () => (fn && (res = fn(fn = 0)), res) + // __commonJSMin + // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) + } + + DevRuntime.getInstance(); + + // export function loadScript(url: string): void { + // const script = document.createElement('script'); + // script.src = url; + // script.type = 'module'; + // script.onerror = function () { + // console.error('Failed to load script: ' + url); + // } + // document.body.appendChild(script); + // } + \ No newline at end of file diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 07a3d307e8d84d..08279812d84e9b 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -90,6 +90,7 @@ import type { Plugin } from './plugin' import type { RollupPluginHooks } from './typeUtils' import { buildOxcPlugin } from './plugins/oxc' import type { ViteDevServer } from './server' +import { getHmrImplement } from './plugins/clientInjections' export interface BuildEnvironmentOptions { /** @@ -671,7 +672,12 @@ async function buildEnvironment( '.css': 'js', }, experimental: { - hmr: true, + hmr: server + ? { + implement: await getHmrImplement(environment.config), + } + : false, + // hmr: true, // hmr: server ? { // host: server._currentServerHost!, // port: server._currentServerPort!, diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index f59f7a77e9acee..9e07d66bc4785d 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -1,4 +1,5 @@ import path from 'node:path' +import fs from 'node:fs' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '../config' import { CLIENT_ENTRY, ENV_ENTRY } from '../constants' @@ -121,3 +122,65 @@ function escapeReplacement(value: string | number | boolean | null) { const jsonValue = JSON.stringify(value) return () => jsonValue } + +export async function getHmrImplement(config: ResolvedConfig): Promise { + const content = fs.readFileSync(normalizedClientEntry, 'utf-8') + const resolvedServerHostname = (await resolveHostname(config.server.host)) + .name + const resolvedServerPort = config.server.port! + const devBase = config.base + + const serverHost = `${resolvedServerHostname}:${resolvedServerPort}${devBase}` + + let hmrConfig = config.server.hmr + hmrConfig = isObject(hmrConfig) ? hmrConfig : undefined + const host = hmrConfig?.host || null + const protocol = hmrConfig?.protocol || null + const timeout = hmrConfig?.timeout || 30000 + const overlay = hmrConfig?.overlay !== false + const isHmrServerSpecified = !!hmrConfig?.server + const hmrConfigName = path.basename(config.configFile || 'vite.config.js') + + // hmr.clientPort -> hmr.port + // -> (24678 if middleware mode and HMR server is not specified) -> new URL(import.meta.url).port + let port = hmrConfig?.clientPort || hmrConfig?.port || null + if (config.server.middlewareMode && !isHmrServerSpecified) { + port ||= 24678 + } + + let directTarget = hmrConfig?.host || resolvedServerHostname + directTarget += `:${hmrConfig?.port || resolvedServerPort}` + directTarget += devBase + + let hmrBase = devBase + if (hmrConfig?.path) { + hmrBase = path.posix.join(hmrBase, hmrConfig.path) + } + + const modeReplacement = escapeReplacement(config.mode) + const baseReplacement = escapeReplacement(devBase) + const serverHostReplacement = escapeReplacement(serverHost) + const hmrProtocolReplacement = escapeReplacement(protocol) + const hmrHostnameReplacement = escapeReplacement(host) + const hmrPortReplacement = escapeReplacement(port) + const hmrDirectTargetReplacement = escapeReplacement(directTarget) + const hmrBaseReplacement = escapeReplacement(hmrBase) + const hmrTimeoutReplacement = escapeReplacement(timeout) + const hmrEnableOverlayReplacement = escapeReplacement(overlay) + const hmrConfigNameReplacement = escapeReplacement(hmrConfigName) + const wsTokenReplacement = escapeReplacement(config.webSocketToken) + + return content + .replace(`__MODE__`, modeReplacement) + .replace(/__BASE__/g, baseReplacement) + .replace(`__SERVER_HOST__`, serverHostReplacement) + .replace(`__HMR_PROTOCOL__`, hmrProtocolReplacement) + .replace(`__HMR_HOSTNAME__`, hmrHostnameReplacement) + .replace(`__HMR_PORT__`, hmrPortReplacement) + .replace(`__HMR_DIRECT_TARGET__`, hmrDirectTargetReplacement) + .replace(`__HMR_BASE__`, hmrBaseReplacement) + .replace(`__HMR_TIMEOUT__`, hmrTimeoutReplacement) + .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) + .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) + .replace(`__WS_TOKEN__`, wsTokenReplacement) +} diff --git a/packages/vite/types/hmrPayload.d.ts b/packages/vite/types/hmrPayload.d.ts index 0cbd649f7279da..6b46ae505348aa 100644 --- a/packages/vite/types/hmrPayload.d.ts +++ b/packages/vite/types/hmrPayload.d.ts @@ -24,6 +24,7 @@ export interface UpdatePayload { export interface Update { type: 'js-update' | 'css-update' + url?: string // the hmr chunk url path: string acceptedPath: string timestamp: number diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index f9e21a1c2fee67..cd91aed0e490f1 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -14,6 +14,9 @@ export default defineConfig({ test: { include: ['./playground/**/*.spec.[tj]s'], exclude: [ + './playground/hmr/**/*.spec.[tj]s', + './playground/ssr/**/*.spec.[tj]s', + './playground/ssr*/**/*.spec.[tj]s', './playground/legacy/**/*.spec.[tj]s', // system format ...(isBuild ? [ From 947ec1e779e3724c183a29ce22b92fc95a9869c1 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 16 Apr 2025 14:51:58 +0800 Subject: [PATCH 16/76] fix: using rolldown hmr info --- packages/vite/src/node/build.ts | 17 +++++++++++------ packages/vite/types/hmrPayload.d.ts | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 08279812d84e9b..e3ef29192a613e 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -948,15 +948,20 @@ async function buildEnvironment( if (server) { server.watcher.on('change', async (file) => { const startTime = Date.now() - const patch = await bundle!.generateHmrPatch([file]) - if (patch) { + const hmrOutput = (await bundle!.generateHmrPatch([file]))! + if (hmrOutput.patch) { const url = `${Date.now()}.js` - server.memoryFiles[url] = patch - // TODO(underfin): fix ws msg typing - // @ts-expect-error fix ws msg typing + server.memoryFiles[url] = hmrOutput.patch server.ws.send({ type: 'update', - url, + updates: hmrOutput.hmrBoundaries.map((boundary) => { + return { + type: 'js-update', + url, + path: boundary.boundary, + acceptedPath: boundary.acceptedVia, + } + }), }) logger.info( `${colors.green(`✓ Found ${path.relative(root, file)} changed, rebuilt in ${displayTime(Date.now() - startTime)}`)}`, diff --git a/packages/vite/types/hmrPayload.d.ts b/packages/vite/types/hmrPayload.d.ts index 6b46ae505348aa..adc05f7082d7d0 100644 --- a/packages/vite/types/hmrPayload.d.ts +++ b/packages/vite/types/hmrPayload.d.ts @@ -27,7 +27,7 @@ export interface Update { url?: string // the hmr chunk url path: string acceptedPath: string - timestamp: number + // timestamp: number /** @internal */ explicitImportRequired?: boolean /** @internal */ From 2cf5ff53007b74a2794e97de8c5b791cd9fe2637 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 16 Apr 2025 14:58:36 +0800 Subject: [PATCH 17/76] fix: define plugin should handle import.meta.hot --- packages/vite/src/node/plugins/define.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index 11c487e8f773e2..2b476dea185cc6 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,7 +12,7 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - const isBuild = config.mode === 'production' + // const isBuild = config.mode === 'production' const isBuildLib = config.build.lib // ignore replace process.env in lib build @@ -29,21 +29,20 @@ export function definePlugin(config: ResolvedConfig): Plugin { }) } - // during dev, import.meta properties are handled by importAnalysis plugin. const importMetaKeys: Record = {} const importMetaEnvKeys: Record = {} const importMetaFallbackKeys: Record = {} - if (isBuild) { - importMetaKeys['import.meta.hot'] = `undefined` - for (const key in config.env) { - const val = JSON.stringify(config.env[key]) - importMetaKeys[`import.meta.env.${key}`] = val - importMetaEnvKeys[key] = val - } - // these will be set to a proper value in `generatePattern` - importMetaKeys['import.meta.env.SSR'] = `undefined` - importMetaFallbackKeys['import.meta.env'] = `undefined` + // if (isBuild) { + importMetaKeys['import.meta.hot'] = `undefined` + for (const key in config.env) { + const val = JSON.stringify(config.env[key]) + importMetaKeys[`import.meta.env.${key}`] = val + importMetaEnvKeys[key] = val } + // these will be set to a proper value in `generatePattern` + importMetaKeys['import.meta.env.SSR'] = `undefined` + importMetaFallbackKeys['import.meta.env'] = `undefined` + // } function generatePattern(environment: Environment) { const keepProcessEnv = environment.config.keepProcessEnv From 44527315f3ba77d71cd5bdbd292cee395355bd58 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 18 Apr 2025 10:42:14 +0800 Subject: [PATCH 18/76] chore: revert ws connection logic --- packages/vite/src/node/server/ws.ts | 60 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index cc32107d6738f3..583a68457c8a2b 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -1,11 +1,11 @@ -// import path from 'node:path' +import path from 'node:path' import type { IncomingMessage, Server } from 'node:http' import { STATUS_CODES, createServer as createHttpServer } from 'node:http' import type { ServerOptions as HttpsServerOptions } from 'node:https' import { createServer as createHttpsServer } from 'node:https' import type { Socket } from 'node:net' import type { Duplex } from 'node:stream' -// import crypto from 'node:crypto' +import crypto from 'node:crypto' import colors from 'picocolors' import type { WebSocket as WebSocketRaw } from 'ws' import { WebSocketServer as WebSocketServerRaw_ } from 'ws' @@ -102,19 +102,19 @@ function noop() { // // using the query params means the token might be logged out in server or middleware logs // but we assume that is not an issue since the token is regenerated for each process -// function hasValidToken(config: ResolvedConfig, url: URL) { -// const token = url.searchParams.get('token') -// if (!token) return false +function hasValidToken(config: ResolvedConfig, url: URL) { + const token = url.searchParams.get('token') + if (!token) return false -// try { -// const isValidToken = crypto.timingSafeEqual( -// Buffer.from(token), -// Buffer.from(config.webSocketToken), -// ) -// return isValidToken -// } catch {} // an error is thrown when the length is incorrect -// return false -// } + try { + const isValidToken = crypto.timingSafeEqual( + Buffer.from(token), + Buffer.from(config.webSocketToken), + ) + return isValidToken + } catch {} // an error is thrown when the length is incorrect + return false +} export function createWebSocketServer( server: HttpServer | null, @@ -181,10 +181,10 @@ export function createWebSocketServer( // If the Origin header is set, this request might be coming from a browser. // Browsers always sets the Origin header for WebSocket connections. - // if (req.headers.origin) { - // const parsedUrl = new URL(`http://example.com${req.url!}`) - // return hasValidToken(config, parsedUrl) - // } + if (req.headers.origin) { + const parsedUrl = new URL(`http://example.com${req.url!}`) + return hasValidToken(config, parsedUrl) + } // We allow non-browser requests to connect without a token // for backward compat and convenience @@ -213,20 +213,20 @@ export function createWebSocketServer( wss.shouldHandle = shouldHandle if (wsServer) { - // let hmrBase = config.base - // const hmrPath = hmr ? hmr.path : undefined - // if (hmrPath) { - // hmrBase = path.posix.join(hmrBase, hmrPath) - // } + let hmrBase = config.base + const hmrPath = hmr ? hmr.path : undefined + if (hmrPath) { + hmrBase = path.posix.join(hmrBase, hmrPath) + } hmrServerWsListener = (req, socket, head) => { const protocol = req.headers['sec-websocket-protocol']! - // const parsedUrl = new URL(`http://example.com${req.url!}`) - // if ( - // [HMR_HEADER, 'vite-ping'].includes(protocol) && - // parsedUrl.pathname === hmrBase - // ) { - handleUpgrade(req, socket as Socket, head, protocol === 'vite-ping') - // } + const parsedUrl = new URL(`http://example.com${req.url!}`) + if ( + [HMR_HEADER, 'vite-ping'].includes(protocol) && + parsedUrl.pathname === hmrBase + ) { + handleUpgrade(req, socket as Socket, head, protocol === 'vite-ping') + } } wsServer.on('upgrade', hmrServerWsListener) } else { From f3fe74d238db63a5ef7997d1adfeeb347b43a30b Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 18 Apr 2025 10:46:42 +0800 Subject: [PATCH 19/76] chore: patch rolldown runtime update --- packages/vite/src/client/hmrModuleRunner.ts | 156 +++++++++++--------- vitest.config.e2e.ts | 2 + 2 files changed, 86 insertions(+), 72 deletions(-) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 766855afc4661e..6ccc1d693d83cb 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -1,88 +1,100 @@ import { createHotContext } from './client' class DevRuntime { - modules: Record = {} + modules: Record = {} - static getInstance() { + static getInstance() { + // @ts-expect-error __rolldown_runtime__ + let instance = globalThis.__rolldown_runtime__ + if (!instance) { + instance = new DevRuntime() // @ts-expect-error __rolldown_runtime__ - let instance = globalThis.__rolldown_runtime__; - if (!instance) { - instance = new DevRuntime(); - // @ts-expect-error __rolldown_runtime__ - globalThis.__rolldown_runtime__ = instance; - } - return instance + globalThis.__rolldown_runtime__ = instance } + return instance + } - createModuleHotContext(moduleId: string) { - return createHotContext(moduleId); - } + createModuleHotContext(moduleId: string) { + return createHotContext(moduleId) + } - applyUpdates(_boundaries: string[]) { - // trigger callbacks of accept() correctly - // for (const moduleId of boundaries) { - // const hotContext = this.moduleHotContexts.get(moduleId); - // if (hotContext) { - // const acceptCallbacks = hotContext.acceptCallbacks; - // acceptCallbacks.filter((cb) => { - // cb.fn(this.modules[moduleId].exports); - // }) - // } - // } - // this.moduleHotContextsToBeUpdated.forEach((hotContext, moduleId) => { - // this.moduleHotContexts[moduleId] = hotContext; - // }) - // this.moduleHotContextsToBeUpdated.clear() - // swap new contexts - } + applyUpdates(_boundaries: string[]) { + // trigger callbacks of accept() correctly + // for (const moduleId of boundaries) { + // const hotContext = this.moduleHotContexts.get(moduleId); + // if (hotContext) { + // const acceptCallbacks = hotContext.acceptCallbacks; + // acceptCallbacks.filter((cb) => { + // cb.fn(this.modules[moduleId].exports); + // }) + // } + // } + // this.moduleHotContextsToBeUpdated.forEach((hotContext, moduleId) => { + // this.moduleHotContexts[moduleId] = hotContext; + // }) + // this.moduleHotContextsToBeUpdated.clear() + // swap new contexts + } - registerModule(id: string, exportGetters: Record any>) { - const exports = {}; - Object.keys(exportGetters).forEach((key) => { - if (Object.prototype.hasOwnProperty.call(exportGetters, key)) { + registerModule( + id: string, + esmExportGettersOrCjsExports: Record any>, + meta: { cjs?: boolean } = {}, + ) { + const exports = {} + Object.keys(esmExportGettersOrCjsExports).forEach((key) => { + if ( + Object.prototype.hasOwnProperty.call(esmExportGettersOrCjsExports, key) + ) { + if (meta.cjs) { Object.defineProperty(exports, key, { enumerable: true, - get: exportGetters[key], - }); - } - }) - if (this.modules[id]) { - this.modules[id] = { - exports, + get: () => esmExportGettersOrCjsExports[key], + }) + } else { + Object.defineProperty(exports, key, { + enumerable: true, + get: esmExportGettersOrCjsExports[key], + }) } - } else { - // If the module is not in the cache, we need to register it. - this.modules[id] = { - exports, - }; } - } - - loadExports(id: string) { - const module = this.modules[id]; - if (module) { - return module.exports; - } else { - console.warn(`Module ${id} not found`); - return {}; + }) + if (this.modules[id]) { + this.modules[id] = { + exports, + } + } else { + // If the module is not in the cache, we need to register it. + this.modules[id] = { + exports, } } - - // __esmMin - // createEsmInitializer = (fn, res) => () => (fn && (res = fn(fn = 0)), res) - // __commonJSMin - // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) } - - DevRuntime.getInstance(); - - // export function loadScript(url: string): void { - // const script = document.createElement('script'); - // script.src = url; - // script.type = 'module'; - // script.onerror = function () { - // console.error('Failed to load script: ' + url); - // } - // document.body.appendChild(script); - // } - \ No newline at end of file + + loadExports(id: string) { + const module = this.modules[id] + if (module) { + return module.exports + } else { + console.warn(`Module ${id} not found`) + return {} + } + } + + // __esmMin + // createEsmInitializer = (fn, res) => () => (fn && (res = fn(fn = 0)), res) + // __commonJSMin + // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) +} + +DevRuntime.getInstance() + +// export function loadScript(url: string): void { +// const script = document.createElement('script'); +// script.src = url; +// script.type = 'module'; +// script.onerror = function () { +// console.error('Failed to load script: ' + url); +// } +// document.body.appendChild(script); +// } diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index cd91aed0e490f1..9669d16f5f8748 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -15,6 +15,8 @@ export default defineConfig({ include: ['./playground/**/*.spec.[tj]s'], exclude: [ './playground/hmr/**/*.spec.[tj]s', + './playground/html/**/*.spec.[tj]s', + './playground/resolve/**/*.spec.[tj]s', './playground/ssr/**/*.spec.[tj]s', './playground/ssr*/**/*.spec.[tj]s', './playground/legacy/**/*.spec.[tj]s', // system format From c90d37b54bfc4143e6c2d931c83368ce3947a287 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 18 Apr 2025 10:51:15 +0800 Subject: [PATCH 20/76] fix: disable chunksReporter at development build --- packages/vite/src/node/plugins/reporter.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index ed74b45c570476..a44ec2558958ca 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -292,7 +292,9 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { : {}), renderStart() { - chunksReporter(this).reset() + if (config.mode === 'production') { + chunksReporter(this).reset() + } }, renderChunk(_, chunk, options) { @@ -326,7 +328,9 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { } } - chunksReporter(this).register() + if (config.mode === 'production') { + chunksReporter(this).register() + } }, generateBundle() { @@ -334,7 +338,9 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { }, async writeBundle({ dir }, output) { - await chunksReporter(this).log(output, dir) + if (config.mode === 'production') { + await chunksReporter(this).log(output, dir) + } }, } } From f9c4c9f057e5e3ea4fc0e5dc3eb288408a13cd8c Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 18 Apr 2025 11:16:22 +0800 Subject: [PATCH 21/76] fix: aovid resolveConfig twice at dev build --- packages/vite/src/node/cli.ts | 25 ++++++++----------------- packages/vite/src/node/server/index.ts | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index b8e7712674e2df..6ef0dee4adecbe 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -188,24 +188,8 @@ cli filterDuplicateOptions(options) // output structure is preserved even after bundling so require() // is ok here - const { createServer } = await import('./server') + const { createServerWithResolvedConfig } = await import('./server') try { - const server = await createServer({ - root, - base: options.base, - mode: options.mode, - configFile: options.config, - configLoader: options.configLoader, - logLevel: options.logLevel, - clearScreen: options.clearScreen, - server: cleanGlobalCLIOptions(options), - forceOptimizeDeps: options.force, - }) - - if (!server.httpServer) { - throw new Error('HTTP server not available') - } - const { createBuilder } = await import('./build') const buildOptions: BuildEnvironmentOptions = @@ -223,6 +207,13 @@ cli ...(options.app ? { builder: {} } : {}), } const builder = await createBuilder(inlineConfig, null, 'serve') + + const server = await createServerWithResolvedConfig(builder.config) + + if (!server.httpServer) { + throw new Error('HTTP server not available') + } + await builder.buildApp(server) await server.listen() diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 606ff462a762e0..1bde6bd155c1f2 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -413,21 +413,26 @@ export interface ResolvedServerUrls { network: string[] } -export function createServer( +export async function createServer( inlineConfig: InlineConfig = {}, ): Promise { - return _createServer(inlineConfig, { listen: true }) + const config = await resolveConfig(inlineConfig, 'serve') + return _createServer(config, { listen: true }) +} + +export function createServerWithResolvedConfig( + config: ResolvedConfig, +): Promise { + return _createServer(config, { listen: true }) } export async function _createServer( - inlineConfig: InlineConfig = {}, + config: ResolvedConfig, options: { listen: boolean previousEnvironments?: Record }, ): Promise { - const config = await resolveConfig(inlineConfig, 'serve') - const initPublicFilesPromise = initPublicFiles(config) const { root, server: serverConfig } = config @@ -1187,7 +1192,8 @@ async function restartServer(server: ViteDevServer) { let newServer: ViteDevServer | null = null try { // delay ws server listen - newServer = await _createServer(inlineConfig, { + const config = await resolveConfig(inlineConfig, 'serve') + newServer = await _createServer(config, { listen: false, previousEnvironments: server.environments, }) From 01ed7451e7428ee3aa13e112c600d833764453ef Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 16:29:01 +0800 Subject: [PATCH 22/76] feat: add experimental.fullBundleMode option --- packages/vite/src/client/client.ts | 41 +- packages/vite/src/client/env.ts | 51 +- packages/vite/src/client/hmrModuleRunner.ts | 151 ++- packages/vite/src/node/build.ts | 20 +- packages/vite/src/node/cli.ts | 33 +- packages/vite/src/node/config.ts | 11 +- packages/vite/src/node/idResolver.ts | 7 +- packages/vite/src/node/plugins/asset.ts | 41 +- .../src/node/plugins/assetImportMetaUrl.ts | 4 +- .../vite/src/node/plugins/clientInjections.ts | 8 + packages/vite/src/node/plugins/css.ts | 147 +-- packages/vite/src/node/plugins/define.ts | 47 +- packages/vite/src/node/plugins/index.ts | 38 +- .../src/node/plugins/modulePreloadPolyfill.ts | 4 +- packages/vite/src/node/plugins/reporter.ts | 6 +- packages/vite/src/node/plugins/worker.ts | 3 +- .../src/node/plugins/workerImportMetaUrl.ts | 3 +- packages/vite/src/node/server/environment.ts | 40 +- packages/vite/src/node/server/hmr.ts | 589 ++++++------ packages/vite/src/node/server/index.ts | 429 +++++---- .../src/node/server/middlewares/indexHtml.ts | 886 +++++++++--------- packages/vite/src/node/server/warmup.ts | 30 +- packages/vite/src/node/watch.ts | 15 +- packages/vite/types/hmrPayload.d.ts | 4 +- playground/hmr/file-delete-restore/index.js | 7 +- vitest.config.e2e.ts | 5 - 26 files changed, 1341 insertions(+), 1279 deletions(-) diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 69a0a7b8363253..a56908b33903ae 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -8,7 +8,7 @@ import { import { createHMRHandler } from '../shared/hmrHandler' import { ErrorOverlay, overlayId } from './overlay' import './hmrModuleRunner' -// import '@vite/env' +import '@vite/env' // injected by the hmr plugin when served declare const __BASE__: string @@ -21,6 +21,7 @@ declare const __HMR_BASE__: string declare const __HMR_TIMEOUT__: number declare const __HMR_ENABLE_OVERLAY__: boolean declare const __WS_TOKEN__: string +declare const __FULL_BUNDLE_MODE__: boolean console.debug('[vite] connecting...') @@ -143,15 +144,34 @@ const hmrClient = new HMRClient( async function importUpdatedModule({ url, acceptedPath, - // timestamp, - // explicitImportRequired, + timestamp, + explicitImportRequired, isWithinCircularImport, }) { - // const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`) - const importPromise = import( - /* @vite-ignore */ - base + url - ) + function importModuleWithFullBundleMode() { + const importPromise = import(base + url) + return importPromise.then(() => + // @ts-expect-error globalThis.__rolldown_runtime__ + globalThis.__rolldown_runtime__.loadExports(acceptedPath), + ) + } + + function importModule() { + const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`) + const importPromise = import( + /* @vite-ignore */ + base + + acceptedPathWithoutQuery.slice(1) + + `?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${ + query ? `&${query}` : '' + }` + ) + return importPromise + } + + const importPromise = __FULL_BUNDLE_MODE__ + ? importModuleWithFullBundleMode() + : importModule() if (isWithinCircularImport) { importPromise.catch(() => { console.info( @@ -161,10 +181,7 @@ const hmrClient = new HMRClient( pageReload() }) } - // @ts-expect-error globalThis.__rolldown_runtime__ - return await importPromise.then(() => - globalThis.__rolldown_runtime__.loadExports(acceptedPath), - ) + return await importPromise }, ) transport.connect!(createHMRHandler(handleMessage)) diff --git a/packages/vite/src/client/env.ts b/packages/vite/src/client/env.ts index 158ce43f7bf36f..943e2d11214cfc 100644 --- a/packages/vite/src/client/env.ts +++ b/packages/vite/src/client/env.ts @@ -1,28 +1,31 @@ declare const __DEFINES__: Record +declare const __FULL_BUNDLE_MODE__: boolean -const context = (() => { - if (typeof globalThis !== 'undefined') { - return globalThis - } else if (typeof self !== 'undefined') { - return self - } else if (typeof window !== 'undefined') { - return window - } else { - return Function('return this')() - } -})() - -// assign defines -const defines = __DEFINES__ -Object.keys(defines).forEach((key) => { - const segments = key.split('.') - let target = context - for (let i = 0; i < segments.length; i++) { - const segment = segments[i] - if (i === segments.length - 1) { - target[segment] = defines[key] +if (!__FULL_BUNDLE_MODE__) { + const context = (() => { + if (typeof globalThis !== 'undefined') { + return globalThis + } else if (typeof self !== 'undefined') { + return self + } else if (typeof window !== 'undefined') { + return window } else { - target = target[segment] || (target[segment] = {}) + return Function('return this')() + } + })() + + // assign defines + const defines = __DEFINES__ + Object.keys(defines).forEach((key) => { + const segments = key.split('.') + let target = context + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + if (i === segments.length - 1) { + target[segment] = defines[key] + } else { + target = target[segment] || (target[segment] = {}) + } } - } -}) + }) +} diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 6ccc1d693d83cb..9a6e6c18d1ae2d 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -1,100 +1,83 @@ import { createHotContext } from './client' -class DevRuntime { - modules: Record = {} +declare const __FULL_BUNDLE_MODE__: boolean - static getInstance() { - // @ts-expect-error __rolldown_runtime__ - let instance = globalThis.__rolldown_runtime__ - if (!instance) { - instance = new DevRuntime() +if (__FULL_BUNDLE_MODE__) { + class DevRuntime { + modules: Record = {} + + static getInstance() { // @ts-expect-error __rolldown_runtime__ - globalThis.__rolldown_runtime__ = instance + let instance = globalThis.__rolldown_runtime__ + if (!instance) { + instance = new DevRuntime() + // @ts-expect-error __rolldown_runtime__ + globalThis.__rolldown_runtime__ = instance + } + return instance } - return instance - } - createModuleHotContext(moduleId: string) { - return createHotContext(moduleId) - } + createModuleHotContext(moduleId: string) { + return createHotContext(moduleId) + } - applyUpdates(_boundaries: string[]) { - // trigger callbacks of accept() correctly - // for (const moduleId of boundaries) { - // const hotContext = this.moduleHotContexts.get(moduleId); - // if (hotContext) { - // const acceptCallbacks = hotContext.acceptCallbacks; - // acceptCallbacks.filter((cb) => { - // cb.fn(this.modules[moduleId].exports); - // }) - // } - // } - // this.moduleHotContextsToBeUpdated.forEach((hotContext, moduleId) => { - // this.moduleHotContexts[moduleId] = hotContext; - // }) - // this.moduleHotContextsToBeUpdated.clear() - // swap new contexts - } + applyUpdates(_boundaries: string[]) { + // + } - registerModule( - id: string, - esmExportGettersOrCjsExports: Record any>, - meta: { cjs?: boolean } = {}, - ) { - const exports = {} - Object.keys(esmExportGettersOrCjsExports).forEach((key) => { - if ( - Object.prototype.hasOwnProperty.call(esmExportGettersOrCjsExports, key) - ) { - if (meta.cjs) { - Object.defineProperty(exports, key, { - enumerable: true, - get: () => esmExportGettersOrCjsExports[key], - }) - } else { - Object.defineProperty(exports, key, { - enumerable: true, - get: esmExportGettersOrCjsExports[key], - }) + registerModule( + id: string, + esmExportGettersOrCjsExports: Record any>, + meta: { cjs?: boolean } = {}, + ) { + const exports = {} + Object.keys(esmExportGettersOrCjsExports).forEach((key) => { + if ( + Object.prototype.hasOwnProperty.call( + esmExportGettersOrCjsExports, + key, + ) + ) { + if (meta.cjs) { + Object.defineProperty(exports, key, { + enumerable: true, + get: () => esmExportGettersOrCjsExports[key], + }) + } else { + Object.defineProperty(exports, key, { + enumerable: true, + get: esmExportGettersOrCjsExports[key], + }) + } + } + }) + if (this.modules[id]) { + this.modules[id] = { + exports, + } + } else { + // If the module is not in the cache, we need to register it. + this.modules[id] = { + exports, } - } - }) - if (this.modules[id]) { - this.modules[id] = { - exports, - } - } else { - // If the module is not in the cache, we need to register it. - this.modules[id] = { - exports, } } - } - loadExports(id: string) { - const module = this.modules[id] - if (module) { - return module.exports - } else { - console.warn(`Module ${id} not found`) - return {} + loadExports(id: string) { + const module = this.modules[id] + if (module) { + return module.exports + } else { + console.warn(`Module ${id} not found`) + return {} + } } + + // __esmMin + // createEsmInitializer = (fn, res) => () => (fn && (res = fn(fn = 0)), res) + // __commonJSMin + // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) } - // __esmMin - // createEsmInitializer = (fn, res) => () => (fn && (res = fn(fn = 0)), res) - // __commonJSMin - // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) + DevRuntime.getInstance() } - -DevRuntime.getInstance() - -// export function loadScript(url: string): void { -// const script = document.createElement('script'); -// script.src = url; -// script.type = 'module'; -// script.onerror = function () { -// console.error('Failed to load script: ' + url); -// } -// document.body.appendChild(script); -// } diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index e3ef29192a613e..bf0ab5b15ba178 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -587,7 +587,7 @@ async function buildEnvironment( environment: BuildEnvironment, server?: ViteDevServer ): Promise { - const { root, packageCache, mode } = environment.config + const { root, packageCache } = environment.config const options = environment.config.build const libOptions = options.lib const { logger } = environment @@ -677,11 +677,6 @@ async function buildEnvironment( implement: await getHmrImplement(environment.config), } : false, - // hmr: true, - // hmr: server ? { - // host: server._currentServerHost!, - // port: server._currentServerPort!, - // } : false, }, } @@ -826,13 +821,11 @@ async function buildEnvironment( (isSsrTargetWebworkerEnvironment && (typeof input === 'string' || Object.keys(input).length === 1)), minify: - mode === 'production' - ? options.minify === 'oxc' - ? true - : options.minify === false - ? 'dce-only' - : false - : false, + options.minify === 'oxc' + ? true + : options.minify === false + ? 'dce-only' + : false, ...output, } } @@ -960,6 +953,7 @@ async function buildEnvironment( url, path: boundary.boundary, acceptedPath: boundary.acceptedVia, + timestamp: 0, } }), }) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 6ef0dee4adecbe..390c84fecc61ba 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -32,6 +32,7 @@ interface GlobalCLIOptions { mode?: string force?: boolean w?: boolean + fullBundleMode?: boolean } interface BuilderCLIOptions { @@ -188,8 +189,9 @@ cli filterDuplicateOptions(options) // output structure is preserved even after bundling so require() // is ok here - const { createServerWithResolvedConfig } = await import('./server') - try { + async function createServerWithFullBundleMode() { + const { createServerWithResolvedConfig } = await import('./server') + const { createBuilder } = await import('./build') const buildOptions: BuildEnvironmentOptions = @@ -204,6 +206,9 @@ cli logLevel: options.logLevel, clearScreen: options.clearScreen, build: buildOptions, + experimental: { + fullBundleMode: true, + }, ...(options.app ? { builder: {} } : {}), } const builder = await createBuilder(inlineConfig, null, 'serve') @@ -218,6 +223,30 @@ cli await server.listen() + return server + } + + async function createServer() { + const { createServer } = await import('./server') + const server = await createServer({ + root, + base: options.base, + mode: options.mode, + configFile: options.config, + configLoader: options.configLoader, + logLevel: options.logLevel, + clearScreen: options.clearScreen, + server: cleanGlobalCLIOptions(options), + forceOptimizeDeps: options.force, + }) + return server + } + + try { + const server = options.fullBundleMode + ? await createServerWithFullBundleMode() + : await createServer() + const info = server.config.logger.info const modeString = diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index c9db1fc3a10512..0a247cf83db918 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -525,6 +525,13 @@ export interface ExperimentalOptions { * @default false */ enableNativePlugin?: boolean | 'resolver' + /** + * Enable full bundle mode at dev. + * + * @experimental + * @default false + */ + fullBundleMode?: boolean } export interface LegacyOptions { @@ -1310,7 +1317,9 @@ export async function resolveConfig( const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawPlugins) - const isBuild = mode === 'production' + const isBuild = config.experimental?.fullBundleMode + ? mode === 'production' + : command === 'build' // run config hooks const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins] diff --git a/packages/vite/src/node/idResolver.ts b/packages/vite/src/node/idResolver.ts index 57b8d442d3fc4f..f698fbbb633a12 100644 --- a/packages/vite/src/node/idResolver.ts +++ b/packages/vite/src/node/idResolver.ts @@ -56,6 +56,9 @@ export function createIdResolver( ): Promise { let pluginContainer = pluginContainerMap.get(environment) if (!pluginContainer) { + const isBuild = config.experimental?.fullBundleMode + ? config.mode === 'production' + : config.command === 'build' pluginContainer = await createEnvironmentPluginContainer( environment as Environment, [ @@ -66,7 +69,7 @@ export function createIdResolver( { root: config.root, isProduction: config.isProduction, - isBuild: config.mode === 'production', + isBuild, asSrc: true, preferRelative: false, tryIndex: true, @@ -80,7 +83,7 @@ export function createIdResolver( resolvePlugin({ root: config.root, isProduction: config.isProduction, - isBuild: config.mode === 'production', + isBuild, asSrc: true, preferRelative: false, tryIndex: true, diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index f3386242e7fb0d..aacd40720868cd 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -229,11 +229,17 @@ export function assetPlugin(config: ResolvedConfig): Plugin { // Force rollup to keep this module from being shared between other entry points if it's an entrypoint. // If the resulting chunk is empty, it will be removed in generateBundle. moduleSideEffects: - // config.command === 'build' && - this.getModuleInfo(id)?.isEntry ? 'no-treeshake' : false, - meta: /* config.command === 'build' ? */ { - 'vite:asset': true, - } /* : undefined */, + (config.command === 'build' || + !!config.experimental.fullBundleMode) && + this.getModuleInfo(id)?.isEntry + ? 'no-treeshake' + : false, + meta: + config.command === 'build' || !!config.experimental.fullBundleMode + ? { + 'vite:asset': true, + } + : undefined, moduleType: 'js', // NOTE: needs to be set to avoid double `export default` in `.txt`s } }, @@ -288,7 +294,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin { // do not emit assets for SSR build if ( - // config.command === 'build' && + (config.command === 'build' || !!config.experimental.fullBundleMode) && !this.environment.config.build.emitAssets ) { for (const file in bundle) { @@ -309,12 +315,15 @@ export async function fileToUrl( pluginContext: PluginContext, id: string, ): Promise { - // const { environment } = pluginContext - // if (environment.config.command === 'serve') { - // return fileToDevUrl(environment, id) - // } else { - return fileToBuiltUrl(pluginContext, id) - // } + const { environment } = pluginContext + if ( + environment.config.command === 'serve' && + !environment.config.experimental.fullBundleMode + ) { + return fileToDevUrl(environment, id) + } else { + return fileToBuiltUrl(pluginContext, id) + } } export async function fileToDevUrl( @@ -381,10 +390,10 @@ export function publicFileToBuiltUrl( url: string, config: ResolvedConfig, ): string { - // if (config.command !== 'build') { - // // We don't need relative base or renderBuiltUrl support during dev - // return joinUrlSegments(config.decodedBase, url) - // } + if (config.command !== 'build' && !config.experimental.fullBundleMode) { + // We don't need relative base or renderBuiltUrl support during dev + return joinUrlSegments(config.decodedBase, url) + } const hash = getHash(url) let cache = publicAssetUrlCache.get(config) if (!cache) { diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index 3085d5a70f2bfd..b65741304afe4d 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -38,7 +38,9 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { ...config.resolve, root: config.root, isProduction: config.isProduction, - isBuild: config.mode === 'production', + isBuild: config.experimental?.fullBundleMode + ? config.mode === 'production' + : config.command === 'build', packageCache: config.packageCache, asSrc: true, } diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index 9e07d66bc4785d..3fddc7c81d16c2 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -93,6 +93,10 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) .replace(`__WS_TOKEN__`, wsTokenReplacement) + .replace( + `__FULL_BUNDLE_MODE__`, + escapeReplacement(config.experimental.fullBundleMode || false), + ) } }, async transform(code, id, options) { @@ -183,4 +187,8 @@ export async function getHmrImplement(config: ResolvedConfig): Promise { .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) .replace(`__WS_TOKEN__`, wsTokenReplacement) + .replace( + `__FULL_BUNDLE_MODE__`, + escapeReplacement(config.experimental.fullBundleMode || false), + ) } diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index dc83580791c28e..be7354309fa3fd 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -38,7 +38,7 @@ import type { } from 'lightningcss' import type { CustomPluginOptionsVite } from 'types/metadata' import type { EsbuildTransformOptions } from 'types/internal/esbuildOptions' -// import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap' +import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap' import type { EnvironmentModuleNode } from '../server/moduleGraph' import { createToImportMetaURLBasedRelativeRuntime, @@ -48,7 +48,7 @@ import { } from '../build' import type { LibraryOptions } from '../build' import { - // CLIENT_PUBLIC_PATH, + CLIENT_PUBLIC_PATH, CSS_LANGS_RE, DEV_PROD_CONDITION, ESBUILD_MODULES_TARGET, @@ -73,7 +73,7 @@ import { isDataUrl, isExternalUrl, isObject, - // joinUrlSegments, + joinUrlSegments, mergeWithDefaults, normalizePath, processSrcSet, @@ -285,9 +285,9 @@ const postcssConfigCache = new WeakMap< PostCSSConfigResult | null | Promise >() -// function encodePublicUrlsInCSS(config: ResolvedConfig) { -// return config.command === 'build' -// } +function encodePublicUrlsInCSS(config: ResolvedConfig) { + return config.command === 'build' || config.experimental.fullBundleMode +} const cssUrlAssetRE = /__VITE_CSS_URL__([\da-f]+)__/g @@ -295,7 +295,9 @@ const cssUrlAssetRE = /__VITE_CSS_URL__([\da-f]+)__/g * Plugin applied before user plugins */ export function cssPlugin(config: ResolvedConfig): Plugin { - const isBuild = config.mode === 'production' + const isBuild = config.experimental.fullBundleMode + ? config.mode === 'production' + : config.command === 'build' let moduleCache: Map> const idResolver = createBackCompatIdResolver(config, { @@ -390,11 +392,11 @@ export function cssPlugin(config: ResolvedConfig): Plugin { const urlResolver: CssUrlResolver = async (url, importer) => { const decodedUrl = decodeURI(url) if (checkPublicFile(decodedUrl, config)) { - // if (encodePublicUrlsInCSS(config)) { - return [publicFileToBuiltUrl(decodedUrl, config), undefined] - // } else { - // return [joinUrlSegments(config.base, decodedUrl), undefined] - // } + if (encodePublicUrlsInCSS(config)) { + return [publicFileToBuiltUrl(decodedUrl, config), undefined] + } else { + return [joinUrlSegments(config.base, decodedUrl), undefined] + } } const [id, fragment] = decodedUrl.split('#') let resolved = await resolveUrl(id, importer) @@ -402,23 +404,23 @@ export function cssPlugin(config: ResolvedConfig): Plugin { if (fragment) resolved += '#' + fragment return [await fileToUrl(this, resolved), resolved] } - // if (config.command === 'build') { - const isExternal = config.build.rollupOptions.external - ? resolveUserExternal( - config.build.rollupOptions.external, - decodedUrl, // use URL as id since id could not be resolved - id, - false, - ) - : false + if (config.command === 'build' || config.experimental.fullBundleMode) { + const isExternal = config.build.rollupOptions.external + ? resolveUserExternal( + config.build.rollupOptions.external, + decodedUrl, // use URL as id since id could not be resolved + id, + false, + ) + : false - if (!isExternal) { - // #9800 If we cannot resolve the css url, leave a warning. - config.logger.warnOnce( - `\n${decodedUrl} referenced in ${id} didn't resolve at build time, it will remain unchanged to be resolved at runtime`, - ) + if (!isExternal) { + // #9800 If we cannot resolve the css url, leave a warning. + config.logger.warnOnce( + `\n${decodedUrl} referenced in ${id} didn't resolve at build time, it will remain unchanged to be resolved at runtime`, + ) + } } - // } return [url, undefined] } @@ -577,47 +579,47 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { !inlined && dataToEsm(modules, { namedExports: true, preferConst: true }) - // TODO(underfin): css hmr - // if (config.command === 'serve') { - // const getContentWithSourcemap = async (content: string) => { - // if (config.css.devSourcemap) { - // const sourcemap = this.getCombinedSourcemap() - // if (sourcemap.mappings) { - // await injectSourcesContent( - // sourcemap, - // cleanUrl(id), - // config.logger, - // ) - // } - // return getCodeWithSourcemap('css', content, sourcemap) - // } - // return content - // } - - // if (isDirectCSSRequest(id)) { - // return null - // } - // if (inlined) { - // return `export default ${JSON.stringify(css)}` - // } - // if (this.environment.config.consumer === 'server') { - // return modulesCode || 'export {}' - // } - - // const cssContent = await getContentWithSourcemap(css) - // const code = [ - // `import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify( - // path.posix.join(config.base, CLIENT_PUBLIC_PATH), - // )}`, - // `const __vite__id = ${JSON.stringify(id)}`, - // `const __vite__css = ${JSON.stringify(cssContent)}`, - // `__vite__updateStyle(__vite__id, __vite__css)`, - // // css modules exports change on edit so it can't self accept - // `${modulesCode || 'import.meta.hot.accept()'}`, - // `import.meta.hot.prune(() => __vite__removeStyle(__vite__id))`, - // ].join('\n') - // return { code, map: { mappings: '' } } - // } + // TODO(underfin): full bundle mode css hmr + if (config.command === 'serve' && !config.experimental.fullBundleMode) { + const getContentWithSourcemap = async (content: string) => { + if (config.css.devSourcemap) { + const sourcemap = this.getCombinedSourcemap() + if (sourcemap.mappings) { + await injectSourcesContent( + sourcemap, + cleanUrl(id), + config.logger, + ) + } + return getCodeWithSourcemap('css', content, sourcemap) + } + return content + } + + if (isDirectCSSRequest(id)) { + return null + } + if (inlined) { + return `export default ${JSON.stringify(css)}` + } + if (this.environment.config.consumer === 'server') { + return modulesCode || 'export {}' + } + + const cssContent = await getContentWithSourcemap(css) + const code = [ + `import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify( + path.posix.join(config.base, CLIENT_PUBLIC_PATH), + )}`, + `const __vite__id = ${JSON.stringify(id)}`, + `const __vite__css = ${JSON.stringify(cssContent)}`, + `__vite__updateStyle(__vite__id, __vite__css)`, + // css modules exports change on edit so it can't self accept + `${modulesCode || 'import.meta.hot.accept()'}`, + `import.meta.hot.prune(() => __vite__removeStyle(__vite__id))`, + ].join('\n') + return { code, map: { mappings: '' } } + } // build CSS handling ---------------------------------------------------- @@ -708,7 +710,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { chunkCSS: string, cssAssetName: string, ) => { - const encodedPublicUrls = true + const encodedPublicUrls = encodePublicUrlsInCSS(config) const relative = config.base === './' || config.base === '' const cssAssetDirname = @@ -3515,10 +3517,9 @@ async function compileLightningCSS( }, minify: config.isProduction && !!config.build.cssMinify, sourceMap: - // config.command === 'build' ? - !!config.build.sourcemap, - // TODO(underfin): deprecate css.devSourcemap - // : config.css.devSourcemap, + config.command === 'build' || config.experimental.fullBundleMode + ? !!config.build.sourcemap + : config.css.devSourcemap, analyzeDependencies: true, cssModules: cssModuleRE.test(id) ? (config.css.lightningcss?.cssModules ?? true) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index 2b476dea185cc6..922ba63a62db38 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,8 +12,9 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - // const isBuild = config.mode === 'production' - const isBuildLib = config.build.lib + const isBuild = + config.command === 'build' || config.experimental.fullBundleMode + const isBuildLib = isBuild && config.build.lib // ignore replace process.env in lib build const processEnv: Record = {} @@ -29,20 +30,21 @@ export function definePlugin(config: ResolvedConfig): Plugin { }) } + // during dev, import.meta properties are handled by importAnalysis plugin. const importMetaKeys: Record = {} const importMetaEnvKeys: Record = {} const importMetaFallbackKeys: Record = {} - // if (isBuild) { - importMetaKeys['import.meta.hot'] = `undefined` - for (const key in config.env) { - const val = JSON.stringify(config.env[key]) - importMetaKeys[`import.meta.env.${key}`] = val - importMetaEnvKeys[key] = val + if (isBuild) { + importMetaKeys['import.meta.hot'] = `undefined` + for (const key in config.env) { + const val = JSON.stringify(config.env[key]) + importMetaKeys[`import.meta.env.${key}`] = val + importMetaEnvKeys[key] = val + } + // these will be set to a proper value in `generatePattern` + importMetaKeys['import.meta.env.SSR'] = `undefined` + importMetaFallbackKeys['import.meta.env'] = `undefined` } - // these will be set to a proper value in `generatePattern` - importMetaKeys['import.meta.env.SSR'] = `undefined` - importMetaFallbackKeys['import.meta.env'] = `undefined` - // } function generatePattern(environment: Environment) { const keepProcessEnv = environment.config.keepProcessEnv @@ -53,7 +55,7 @@ export function definePlugin(config: ResolvedConfig): Plugin { userDefine[key] = handleDefineValue(environment.config.define[key]) // make sure `import.meta.env` object has user define properties - if (key.startsWith('import.meta.env.')) { + if (isBuild && key.startsWith('import.meta.env.')) { userDefineEnv[key.slice(16)] = environment.config.define[key] } } @@ -127,12 +129,12 @@ export function definePlugin(config: ResolvedConfig): Plugin { transform: { async handler(code, id) { - // if (this.environment.config.consumer === 'client' && !isBuild) { - // for dev we inject actual global defines in the vite client to - // avoid the transform cost. see the `clientInjection` and - // `importAnalysis` plugin. - // return - // } + if (this.environment.config.consumer === 'client' && !isBuild) { + // for dev we inject actual global defines in the vite client to + // avoid the transform cost. see the `clientInjection` and + // `importAnalysis` plugin. + return + } if ( // exclude html, css and static assets for performance @@ -204,9 +206,10 @@ export async function replaceDefine( sourceType: 'module', define, sourcemap: - // environment.config.command === 'build' ? - !!environment.config.build.sourcemap, - // : true, + environment.config.command === 'build' || + environment.config.experimental.fullBundleMode + ? !!environment.config.build.sourcemap + : true, }) if (result.errors.length > 0) { diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 32b87226181fc5..4f7b9416928a28 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -23,20 +23,16 @@ import { watchPackageDataPlugin } from '../packages' import { normalizePath } from '../utils' import { jsonPlugin } from './json' import { oxcResolvePlugin, resolvePlugin } from './resolve' -// import { optimizedDepsPlugin } from './optimizedDeps' -// import { importAnalysisPlugin } from './importAnalysis' -import { - // cssAnalysisPlugin, - cssPlugin, - cssPostPlugin, -} from './css' +import { optimizedDepsPlugin } from './optimizedDeps' +import { importAnalysisPlugin } from './importAnalysis' +import { cssAnalysisPlugin, cssPlugin, cssPostPlugin } from './css' import { assetPlugin } from './asset' -// import { clientInjectionsPlugin } from './clientInjections' +import { clientInjectionsPlugin } from './clientInjections' import { buildHtmlPlugin, htmlInlineProxyPlugin } from './html' import { wasmFallbackPlugin, wasmHelperPlugin } from './wasm' import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill' import { webWorkerPlugin } from './worker' -// import { preAliasPlugin } from './preAlias' +import { preAliasPlugin } from './preAlias' import { definePlugin } from './define' import { workerImportMetaUrlPlugin } from './workerImportMetaUrl' import { assetImportMetaUrlPlugin } from './assetImportMetaUrl' @@ -57,7 +53,8 @@ export async function resolvePlugins( normalPlugins: Plugin[], postPlugins: Plugin[], ): Promise { - const isBuild = true + const isBuild = + config.command === 'build' || !!config.experimental.fullBundleMode const isWorker = config.isWorker const buildPlugins = isBuild ? await (await import('../build')).resolveBuildPlugins(config) @@ -66,9 +63,9 @@ export async function resolvePlugins( const enableNativePlugin = config.experimental.enableNativePlugin return [ - // !isBuild ? optimizedDepsPlugin() : null, + !isBuild ? optimizedDepsPlugin() : null, !isWorker ? watchPackageDataPlugin(config.packageCache) : null, - // !isBuild ? preAliasPlugin(config) : null, + !isBuild ? preAliasPlugin(config) : null, enableNativePlugin === true ? nativeAliasPlugin({ entries: config.resolve.alias.map((item) => { @@ -176,8 +173,7 @@ export async function resolvePlugins( : wasmFallbackPlugin(), definePlugin(config), cssPostPlugin(config), - // isBuild && - buildHtmlPlugin(config), + isBuild && buildHtmlPlugin(config), workerImportMetaUrlPlugin(config), assetImportMetaUrlPlugin(config), ...buildPlugins.pre, @@ -196,13 +192,13 @@ export async function resolvePlugins( ...buildPlugins.post, // // internal server-only plugins are always applied after everything else - // ...(isBuild - // ? [] - // : [ - // clientInjectionsPlugin(config), - // cssAnalysisPlugin(config), - // importAnalysisPlugin(config), - // ]), + ...(isBuild + ? [] + : [ + clientInjectionsPlugin(config), + cssAnalysisPlugin(config), + importAnalysisPlugin(config), + ]), ].filter(Boolean) as Plugin[] } diff --git a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts index 3647a0849c4767..9477953480cc5e 100644 --- a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts +++ b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts @@ -6,7 +6,7 @@ import { isModernFlag } from './importAnalysisBuild' export const modulePreloadPolyfillId = 'vite/modulepreload-polyfill' const resolvedModulePreloadPolyfillId = '\0' + modulePreloadPolyfillId + '.js' -export function modulePreloadPolyfillPlugin(_config: ResolvedConfig): Plugin { +export function modulePreloadPolyfillPlugin(config: ResolvedConfig): Plugin { let polyfillString: string | undefined return { @@ -22,7 +22,7 @@ export function modulePreloadPolyfillPlugin(_config: ResolvedConfig): Plugin { handler(_id) { // `isModernFlag` is only available during build since it is resolved by `vite:build-import-analysis` if ( - // config.command !== 'build' || + (config.command !== 'build' && !config.experimental.fullBundleMode) || this.environment.config.consumer !== 'client' ) { return '' diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index a44ec2558958ca..a3a40ddc979494 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -292,7 +292,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { : {}), renderStart() { - if (config.mode === 'production') { + if (config.command === 'build' && !config.experimental.fullBundleMode) { chunksReporter(this).reset() } }, @@ -328,7 +328,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { } } - if (config.mode === 'production') { + if (config.command === 'build' && !config.experimental.fullBundleMode) { chunksReporter(this).register() } }, @@ -338,7 +338,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { }, async writeBundle({ dir }, output) { - if (config.mode === 'production') { + if (config.command === 'build' && !config.experimental.fullBundleMode) { await chunksReporter(this).log(output, dir) } }, diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 240d61e6abb285..3ece6891ee5ddb 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -285,7 +285,8 @@ export function webWorkerPostPlugin(): Plugin { } export function webWorkerPlugin(config: ResolvedConfig): Plugin { - const isBuild = true + const isBuild = + config.command === 'build' || config.experimental.fullBundleMode const isWorker = config.isWorker return { diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 80036f86d51981..d9e34a9882f1e8 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -184,7 +184,8 @@ const workerImportMetaUrlRE = /new\s+(?:Worker|SharedWorker).+new\s+URL.+import\.meta\.url/s export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { - const isBuild = true + const isBuild = + config.command === 'build' || !!config.experimental.fullBundleMode let workerResolver: ResolveIdFn const fsResolveOptions: InternalResolveOptions = { diff --git a/packages/vite/src/node/server/environment.ts b/packages/vite/src/node/server/environment.ts index 7199c6c28a0759..7da15747f62673 100644 --- a/packages/vite/src/node/server/environment.ts +++ b/packages/vite/src/node/server/environment.ts @@ -13,11 +13,11 @@ import type { import { mergeConfig } from '../utils' import { fetchModule } from '../ssr/fetchModule' import type { DepsOptimizer } from '../optimizer' -// import { isDepOptimizationDisabled } from '../optimizer' -// import { -// createDepsOptimizer, -// createExplicitDepsOptimizer, -// } from '../optimizer/optimizer' +import { isDepOptimizationDisabled } from '../optimizer' +import { + createDepsOptimizer, + createExplicitDepsOptimizer, +} from '../optimizer/optimizer' import { resolveEnvironmentPlugins } from '../plugin' import { ERR_OUTDATED_OPTIMIZED_DEP } from '../../shared/constants' import { promiseWithResolvers } from '../../shared/utils' @@ -145,18 +145,18 @@ export class DevEnvironment extends BaseEnvironment { }, ) - // const { optimizeDeps } = this.config - // if (context.depsOptimizer) { - // this.depsOptimizer = context.depsOptimizer - // } else if (isDepOptimizationDisabled(optimizeDeps)) { - // this.depsOptimizer = undefined - // } else { - // this.depsOptimizer = ( - // optimizeDeps.noDiscovery - // ? createExplicitDepsOptimizer - // : createDepsOptimizer - // )(this) - // } + const { optimizeDeps, experimental } = this.config + if (context.depsOptimizer && !experimental.fullBundleMode) { + this.depsOptimizer = context.depsOptimizer + } else if (isDepOptimizationDisabled(optimizeDeps)) { + this.depsOptimizer = undefined + } else { + this.depsOptimizer = ( + optimizeDeps.noDiscovery + ? createExplicitDepsOptimizer + : createDepsOptimizer + )(this) + } } async init(options?: { @@ -188,8 +188,10 @@ export class DevEnvironment extends BaseEnvironment { */ async listen(server: ViteDevServer): Promise { this.hot.listen() - await this.depsOptimizer?.init() - warmupFiles(server, this) + if (!this.config.experimental.fullBundleMode) { + await this.depsOptimizer?.init() + warmupFiles(server, this) + } } fetchModule( diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 987ef66b4f1ce6..4f3154ad615547 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -1,4 +1,4 @@ -// import fsp from 'node:fs/promises' +import fsp from 'node:fs/promises' import path from 'node:path' import { EventEmitter } from 'node:events' import colors from 'picocolors' @@ -9,35 +9,32 @@ import type { InvokeResponseData, InvokeSendData, } from '../../shared/invokeMethods' -// import { CLIENT_DIR } from '../constants' -import { - createDebugger, - // normalizePath -} from '../utils' +import { CLIENT_DIR } from '../constants' +import { createDebugger, normalizePath } from '../utils' import type { InferCustomEventPayload, ViteDevServer } from '..' -// import { getHookHandler } from '../plugins' +import { getHookHandler } from '../plugins' import { isCSSRequest } from '../plugins/css' import { isExplicitImportRequired } from '../plugins/importAnalysis' -// import { getEnvFilesForMode } from '../env' -// import type { Environment } from '../environment' +import { getEnvFilesForMode } from '../env' +import type { Environment } from '../environment' import { withTrailingSlash, wrapId } from '../../shared/utils' import type { Plugin } from '../plugin' -// import { -// ignoreDeprecationWarnings, -// warnFutureDeprecation, -// } from '../deprecations' +import { + ignoreDeprecationWarnings, + warnFutureDeprecation, +} from '../deprecations' import type { EnvironmentModuleNode } from './moduleGraph' import type { ModuleNode } from './mixedModuleGraph' import type { DevEnvironment } from './environment' -// import { prepareError } from './middlewares/error' +import { prepareError } from './middlewares/error' import type { HttpServer } from '.' -// import { restartServerWithUrls } from '.' +import { restartServerWithUrls } from '.' export const debugHmr = createDebugger('vite:hmr') const whitespaceRE = /\s/ -// const normalizedClientDir = normalizePath(CLIENT_DIR) +const normalizedClientDir = normalizePath(CLIENT_DIR) export interface HmrOptions { protocol?: string @@ -365,268 +362,268 @@ export function getSortedPluginsByHotUpdateHook( return sortedPlugins } -// const sortedHotUpdatePluginsCache = new WeakMap() -// function getSortedHotUpdatePlugins(environment: Environment): Plugin[] { -// let sortedPlugins = sortedHotUpdatePluginsCache.get(environment) -// if (!sortedPlugins) { -// sortedPlugins = getSortedPluginsByHotUpdateHook(environment.plugins) -// sortedHotUpdatePluginsCache.set(environment, sortedPlugins) -// } -// return sortedPlugins -// } - -// export async function handleHMRUpdate( -// type: 'create' | 'delete' | 'update', -// file: string, -// server: ViteDevServer, -// ): Promise { -// const { config } = server -// const mixedModuleGraph = ignoreDeprecationWarnings(() => server.moduleGraph) - -// const environments = Object.values(server.environments) -// const shortFile = getShortName(file, config.root) - -// const isConfig = file === config.configFile -// const isConfigDependency = config.configFileDependencies.some( -// (name) => file === name, -// ) - -// const isEnv = -// config.envDir !== false && -// getEnvFilesForMode(config.mode, config.envDir).includes(file) -// if (isConfig || isConfigDependency || isEnv) { -// // auto restart server -// debugHmr?.(`[config change] ${colors.dim(shortFile)}`) -// config.logger.info( -// colors.green( -// `${normalizePath( -// path.relative(process.cwd(), file), -// )} changed, restarting server...`, -// ), -// { clear: true, timestamp: true }, -// ) -// try { -// await restartServerWithUrls(server) -// } catch (e) { -// config.logger.error(colors.red(e)) -// } -// return -// } - -// debugHmr?.(`[file change] ${colors.dim(shortFile)}`) - -// // (dev only) the client itself cannot be hot updated. -// if (file.startsWith(withTrailingSlash(normalizedClientDir))) { -// environments.forEach(({ hot }) => -// hot.send({ -// type: 'full-reload', -// path: '*', -// triggeredBy: path.resolve(config.root, file), -// }), -// ) -// return -// } - -// const timestamp = Date.now() -// const contextMeta = { -// type, -// file, -// timestamp, -// read: () => readModifiedFile(file), -// server, -// } -// const hotMap = new Map< -// Environment, -// { options: HotUpdateOptions; error?: Error } -// >() - -// for (const environment of Object.values(server.environments)) { -// const mods = new Set(environment.moduleGraph.getModulesByFile(file)) -// if (type === 'create') { -// for (const mod of environment.moduleGraph._hasResolveFailedErrorModules) { -// mods.add(mod) -// } -// } -// const options = { -// ...contextMeta, -// modules: [...mods], -// // later on hotUpdate will be called for each runtime with a new HotUpdateOptions -// environment, -// } -// hotMap.set(environment, { options }) -// } - -// const mixedMods = new Set(mixedModuleGraph.getModulesByFile(file)) - -// const mixedHmrContext: HmrContext = { -// ...contextMeta, -// modules: [...mixedMods], -// } - -// const clientEnvironment = server.environments.client -// const ssrEnvironment = server.environments.ssr -// const clientContext = { environment: clientEnvironment } -// const clientHotUpdateOptions = hotMap.get(clientEnvironment)!.options -// const ssrHotUpdateOptions = hotMap.get(ssrEnvironment)?.options -// try { -// for (const plugin of getSortedHotUpdatePlugins( -// server.environments.client, -// )) { -// if (plugin.hotUpdate) { -// const filteredModules = await getHookHandler(plugin.hotUpdate).call( -// clientContext, -// clientHotUpdateOptions, -// ) -// if (filteredModules) { -// clientHotUpdateOptions.modules = filteredModules -// // Invalidate the hmrContext to force compat modules to be updated -// mixedHmrContext.modules = mixedHmrContext.modules.filter( -// (mixedMod) => -// filteredModules.some((mod) => mixedMod.id === mod.id) || -// ssrHotUpdateOptions?.modules.some( -// (ssrMod) => ssrMod.id === mixedMod.id, -// ), -// ) -// mixedHmrContext.modules.push( -// ...filteredModules -// .filter( -// (mod) => -// !mixedHmrContext.modules.some( -// (mixedMod) => mixedMod.id === mod.id, -// ), -// ) -// .map((mod) => -// mixedModuleGraph.getBackwardCompatibleModuleNode(mod), -// ), -// ) -// } -// } else if (type === 'update') { -// warnFutureDeprecation( -// config, -// 'removePluginHookHandleHotUpdate', -// `Used in plugin "${plugin.name}".`, -// false, -// ) -// // later on, we'll need: if (runtime === 'client') -// // Backward compatibility with mixed client and ssr moduleGraph -// const filteredModules = await getHookHandler(plugin.handleHotUpdate!)( -// mixedHmrContext, -// ) -// if (filteredModules) { -// mixedHmrContext.modules = filteredModules -// clientHotUpdateOptions.modules = -// clientHotUpdateOptions.modules.filter((mod) => -// filteredModules.some((mixedMod) => mod.id === mixedMod.id), -// ) -// clientHotUpdateOptions.modules.push( -// ...(filteredModules -// .filter( -// (mixedMod) => -// !clientHotUpdateOptions.modules.some( -// (mod) => mod.id === mixedMod.id, -// ), -// ) -// .map((mixedMod) => mixedMod._clientModule) -// .filter(Boolean) as EnvironmentModuleNode[]), -// ) -// if (ssrHotUpdateOptions) { -// ssrHotUpdateOptions.modules = ssrHotUpdateOptions.modules.filter( -// (mod) => -// filteredModules.some((mixedMod) => mod.id === mixedMod.id), -// ) -// ssrHotUpdateOptions.modules.push( -// ...(filteredModules -// .filter( -// (mixedMod) => -// !ssrHotUpdateOptions.modules.some( -// (mod) => mod.id === mixedMod.id, -// ), -// ) -// .map((mixedMod) => mixedMod._ssrModule) -// .filter(Boolean) as EnvironmentModuleNode[]), -// ) -// } -// } -// } -// } -// } catch (error) { -// hotMap.get(server.environments.client)!.error = error -// } - -// for (const environment of Object.values(server.environments)) { -// if (environment.name === 'client') continue -// const hot = hotMap.get(environment)! -// const environmentThis = { environment } -// try { -// for (const plugin of getSortedHotUpdatePlugins(environment)) { -// if (plugin.hotUpdate) { -// const filteredModules = await getHookHandler(plugin.hotUpdate).call( -// environmentThis, -// hot.options, -// ) -// if (filteredModules) { -// hot.options.modules = filteredModules -// } -// } -// } -// } catch (error) { -// hot.error = error -// } -// } - -// async function hmr(environment: DevEnvironment) { -// try { -// const { options, error } = hotMap.get(environment)! -// if (error) { -// throw error -// } -// if (!options.modules.length) { -// // html file cannot be hot updated -// if (file.endsWith('.html') && environment.name === 'client') { -// environment.logger.info( -// colors.green(`page reload `) + colors.dim(shortFile), -// { -// clear: true, -// timestamp: true, -// }, -// ) -// environment.hot.send({ -// type: 'full-reload', -// path: config.server.middlewareMode -// ? '*' -// : '/' + normalizePath(path.relative(config.root, file)), -// }) -// } else { -// // loaded but not in the module graph, probably not js -// debugHmr?.( -// `(${environment.name}) [no modules matched] ${colors.dim(shortFile)}`, -// ) -// } -// return -// } - -// updateModules(environment, shortFile, options.modules, timestamp) -// } catch (err) { -// environment.hot.send({ -// type: 'error', -// err: prepareError(err), -// }) -// } -// } - -// const hotUpdateEnvironments = -// server.config.server.hotUpdateEnvironments ?? -// ((server, hmr) => { -// // Run HMR in parallel for all environments by default -// return Promise.all( -// Object.values(server.environments).map((environment) => -// hmr(environment), -// ), -// ) -// }) - -// await hotUpdateEnvironments(server, hmr) -// } +const sortedHotUpdatePluginsCache = new WeakMap() +function getSortedHotUpdatePlugins(environment: Environment): Plugin[] { + let sortedPlugins = sortedHotUpdatePluginsCache.get(environment) + if (!sortedPlugins) { + sortedPlugins = getSortedPluginsByHotUpdateHook(environment.plugins) + sortedHotUpdatePluginsCache.set(environment, sortedPlugins) + } + return sortedPlugins +} + +export async function handleHMRUpdate( + type: 'create' | 'delete' | 'update', + file: string, + server: ViteDevServer, +): Promise { + const { config } = server + const mixedModuleGraph = ignoreDeprecationWarnings(() => server.moduleGraph) + + const environments = Object.values(server.environments) + const shortFile = getShortName(file, config.root) + + const isConfig = file === config.configFile + const isConfigDependency = config.configFileDependencies.some( + (name) => file === name, + ) + + const isEnv = + config.envDir !== false && + getEnvFilesForMode(config.mode, config.envDir).includes(file) + if (isConfig || isConfigDependency || isEnv) { + // auto restart server + debugHmr?.(`[config change] ${colors.dim(shortFile)}`) + config.logger.info( + colors.green( + `${normalizePath( + path.relative(process.cwd(), file), + )} changed, restarting server...`, + ), + { clear: true, timestamp: true }, + ) + try { + await restartServerWithUrls(server) + } catch (e) { + config.logger.error(colors.red(e)) + } + return + } + + debugHmr?.(`[file change] ${colors.dim(shortFile)}`) + + // (dev only) the client itself cannot be hot updated. + if (file.startsWith(withTrailingSlash(normalizedClientDir))) { + environments.forEach(({ hot }) => + hot.send({ + type: 'full-reload', + path: '*', + triggeredBy: path.resolve(config.root, file), + }), + ) + return + } + + const timestamp = Date.now() + const contextMeta = { + type, + file, + timestamp, + read: () => readModifiedFile(file), + server, + } + const hotMap = new Map< + Environment, + { options: HotUpdateOptions; error?: Error } + >() + + for (const environment of Object.values(server.environments)) { + const mods = new Set(environment.moduleGraph.getModulesByFile(file)) + if (type === 'create') { + for (const mod of environment.moduleGraph._hasResolveFailedErrorModules) { + mods.add(mod) + } + } + const options = { + ...contextMeta, + modules: [...mods], + // later on hotUpdate will be called for each runtime with a new HotUpdateOptions + environment, + } + hotMap.set(environment, { options }) + } + + const mixedMods = new Set(mixedModuleGraph.getModulesByFile(file)) + + const mixedHmrContext: HmrContext = { + ...contextMeta, + modules: [...mixedMods], + } + + const clientEnvironment = server.environments.client + const ssrEnvironment = server.environments.ssr + const clientContext = { environment: clientEnvironment } + const clientHotUpdateOptions = hotMap.get(clientEnvironment)!.options + const ssrHotUpdateOptions = hotMap.get(ssrEnvironment)?.options + try { + for (const plugin of getSortedHotUpdatePlugins( + server.environments.client, + )) { + if (plugin.hotUpdate) { + const filteredModules = await getHookHandler(plugin.hotUpdate).call( + clientContext, + clientHotUpdateOptions, + ) + if (filteredModules) { + clientHotUpdateOptions.modules = filteredModules + // Invalidate the hmrContext to force compat modules to be updated + mixedHmrContext.modules = mixedHmrContext.modules.filter( + (mixedMod) => + filteredModules.some((mod) => mixedMod.id === mod.id) || + ssrHotUpdateOptions?.modules.some( + (ssrMod) => ssrMod.id === mixedMod.id, + ), + ) + mixedHmrContext.modules.push( + ...filteredModules + .filter( + (mod) => + !mixedHmrContext.modules.some( + (mixedMod) => mixedMod.id === mod.id, + ), + ) + .map((mod) => + mixedModuleGraph.getBackwardCompatibleModuleNode(mod), + ), + ) + } + } else if (type === 'update') { + warnFutureDeprecation( + config, + 'removePluginHookHandleHotUpdate', + `Used in plugin "${plugin.name}".`, + false, + ) + // later on, we'll need: if (runtime === 'client') + // Backward compatibility with mixed client and ssr moduleGraph + const filteredModules = await getHookHandler(plugin.handleHotUpdate!)( + mixedHmrContext, + ) + if (filteredModules) { + mixedHmrContext.modules = filteredModules + clientHotUpdateOptions.modules = + clientHotUpdateOptions.modules.filter((mod) => + filteredModules.some((mixedMod) => mod.id === mixedMod.id), + ) + clientHotUpdateOptions.modules.push( + ...(filteredModules + .filter( + (mixedMod) => + !clientHotUpdateOptions.modules.some( + (mod) => mod.id === mixedMod.id, + ), + ) + .map((mixedMod) => mixedMod._clientModule) + .filter(Boolean) as EnvironmentModuleNode[]), + ) + if (ssrHotUpdateOptions) { + ssrHotUpdateOptions.modules = ssrHotUpdateOptions.modules.filter( + (mod) => + filteredModules.some((mixedMod) => mod.id === mixedMod.id), + ) + ssrHotUpdateOptions.modules.push( + ...(filteredModules + .filter( + (mixedMod) => + !ssrHotUpdateOptions.modules.some( + (mod) => mod.id === mixedMod.id, + ), + ) + .map((mixedMod) => mixedMod._ssrModule) + .filter(Boolean) as EnvironmentModuleNode[]), + ) + } + } + } + } + } catch (error) { + hotMap.get(server.environments.client)!.error = error + } + + for (const environment of Object.values(server.environments)) { + if (environment.name === 'client') continue + const hot = hotMap.get(environment)! + const environmentThis = { environment } + try { + for (const plugin of getSortedHotUpdatePlugins(environment)) { + if (plugin.hotUpdate) { + const filteredModules = await getHookHandler(plugin.hotUpdate).call( + environmentThis, + hot.options, + ) + if (filteredModules) { + hot.options.modules = filteredModules + } + } + } + } catch (error) { + hot.error = error + } + } + + async function hmr(environment: DevEnvironment) { + try { + const { options, error } = hotMap.get(environment)! + if (error) { + throw error + } + if (!options.modules.length) { + // html file cannot be hot updated + if (file.endsWith('.html') && environment.name === 'client') { + environment.logger.info( + colors.green(`page reload `) + colors.dim(shortFile), + { + clear: true, + timestamp: true, + }, + ) + environment.hot.send({ + type: 'full-reload', + path: config.server.middlewareMode + ? '*' + : '/' + normalizePath(path.relative(config.root, file)), + }) + } else { + // loaded but not in the module graph, probably not js + debugHmr?.( + `(${environment.name}) [no modules matched] ${colors.dim(shortFile)}`, + ) + } + return + } + + updateModules(environment, shortFile, options.modules, timestamp) + } catch (err) { + environment.hot.send({ + type: 'error', + err: prepareError(err), + }) + } + } + + const hotUpdateEnvironments = + server.config.server.hotUpdateEnvironments ?? + ((server, hmr) => { + // Run HMR in parallel for all environments by default + return Promise.all( + Object.values(server.environments).map((environment) => + hmr(environment), + ), + ) + }) + + await hotUpdateEnvironments(server, hmr) +} type HasDeadEnd = string | boolean @@ -1100,24 +1097,24 @@ function error(pos: number) { // vitejs/vite#610 when hot-reloading Vue files, we read immediately on file // change event and sometimes this can be too early and get an empty buffer. // Poll until the file's modified time has changed before reading again. -// async function readModifiedFile(file: string): Promise { -// const content = await fsp.readFile(file, 'utf-8') -// if (!content) { -// const mtime = (await fsp.stat(file)).mtimeMs - -// for (let n = 0; n < 10; n++) { -// await new Promise((r) => setTimeout(r, 10)) -// const newMtime = (await fsp.stat(file)).mtimeMs -// if (newMtime !== mtime) { -// break -// } -// } - -// return await fsp.readFile(file, 'utf-8') -// } else { -// return content -// } -// } +async function readModifiedFile(file: string): Promise { + const content = await fsp.readFile(file, 'utf-8') + if (!content) { + const mtime = (await fsp.stat(file)).mtimeMs + + for (let n = 0; n < 10; n++) { + await new Promise((r) => setTimeout(r, 10)) + const newMtime = (await fsp.stat(file)).mtimeMs + if (newMtime !== mtime) { + break + } + } + + return await fsp.readFile(file, 'utf-8') + } else { + return content + } +} export type ServerHotChannelApi = { innerEmitter: EventEmitter diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 1bde6bd155c1f2..a4cc275c887946 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -13,6 +13,7 @@ import chokidar from 'chokidar' import type { FSWatcher, WatchOptions } from 'dep-types/chokidar' import type { Connect } from 'dep-types/connect' import launchEditorMiddleware from 'launch-editor-middleware' +import type { SourceMap } from 'rolldown' import type { ModuleRunner } from 'vite/module-runner' import type { CommonServerOptions } from '../http' import { @@ -37,8 +38,13 @@ import { setupSIGTERMListener, teardownSIGTERMListener, } from '../utils' +import { ssrLoadModule } from '../ssr/ssrModuleLoader' +import { ssrFixStacktrace, ssrRewriteStacktrace } from '../ssr/ssrStacktrace' +import { ssrTransform } from '../ssr/ssrTransform' +import { reloadOnTsconfigChange } from '../plugins/esbuild' import { bindCLIShortcuts } from '../shortcuts' import type { BindCLIShortcutsOptions } from '../shortcuts' +import { ERR_OUTDATED_OPTIMIZED_DEP } from '../../shared/constants' import { CLIENT_DIR, DEFAULT_DEV_PORT, @@ -46,6 +52,7 @@ import { } from '../constants' import type { Logger } from '../logger' import { printServerUrls } from '../logger' +import { warnFutureDeprecation } from '../deprecations' import { createNoopWatcher, getResolvedOutDirs, @@ -55,6 +62,8 @@ import { import { initPublicFiles } from '../publicDir' import { getEnvFilesForMode } from '../env' import type { RequiredExceptFor } from '../typeUtils' +import type { PluginContainer } from './pluginContainer' +import { ERR_CLOSED_SERVER, createPluginContainer } from './pluginContainer' import type { WebSocketServer } from './ws' import { createWebSocketServer } from './ws' import { baseMiddleware } from './middlewares/base' @@ -62,18 +71,31 @@ import { proxyMiddleware } from './middlewares/proxy' import { htmlFallbackMiddleware } from './middlewares/htmlFallback' import { cachedTransformMiddleware, - // transformMiddleware, + transformMiddleware, } from './middlewares/transform' -// import { -// indexHtmlMiddleware, -// } from './middlewares/indexHtml' -import { servePublicMiddleware } from './middlewares/static' +import { + createDevHtmlTransformFn, + indexHtmlMiddleware, +} from './middlewares/indexHtml' +import { + servePublicMiddleware, + serveRawFsMiddleware, + serveStaticMiddleware, +} from './middlewares/static' import { timeMiddleware } from './middlewares/time' +import { ModuleGraph } from './mixedModuleGraph' +import type { ModuleNode } from './mixedModuleGraph' import { notFoundMiddleware } from './middlewares/notFound' -import { errorMiddleware } from './middlewares/error' +import { buildErrorMessage, errorMiddleware } from './middlewares/error' import type { HmrOptions, HotBroadcaster } from './hmr' -import { createDeprecatedHotBroadcaster } from './hmr' +import { + createDeprecatedHotBroadcaster, + handleHMRUpdate, + updateModules, +} from './hmr' import { openBrowser as _openBrowser } from './openBrowser' +import type { TransformOptions, TransformResult } from './transformRequest' +import { transformRequest } from './transformRequest' import { searchForPackageRoot, searchForWorkspaceRoot } from './searchRoot' import type { DevEnvironment } from './environment' import { hostCheckMiddleware } from './middlewares/hostCheck' @@ -264,7 +286,7 @@ export interface ViteDevServer { /** * Rollup plugin container that can run plugin hooks on a given file */ - // pluginContainer: PluginContainer + pluginContainer: PluginContainer /** * Module execution environments attached to the Vite server. */ @@ -273,7 +295,7 @@ export interface ViteDevServer { * Module graph that tracks the import relationships, url to file mapping * and hmr state. */ - // moduleGraph: ModuleGraph + moduleGraph: ModuleGraph /** * The resolved urls Vite prints on the CLI (URL-encoded). Returns `null` * in middleware mode or if the server is not listening on any port. @@ -283,53 +305,53 @@ export interface ViteDevServer { * Programmatically resolve, load and transform a URL and get the result * without going through the http request pipeline. */ - // transformRequest( - // url: string, - // options?: TransformOptions, - // ): Promise + transformRequest( + url: string, + options?: TransformOptions, + ): Promise /** * Same as `transformRequest` but only warm up the URLs so the next request * will already be cached. The function will never throw as it handles and * reports errors internally. */ - // warmupRequest(url: string, options?: TransformOptions): Promise + warmupRequest(url: string, options?: TransformOptions): Promise /** * Apply vite built-in HTML transforms and any plugin HTML transforms. */ - // transformIndexHtml( - // url: string, - // html: string, - // originalUrl?: string, - // ): Promise + transformIndexHtml( + url: string, + html: string, + originalUrl?: string, + ): Promise /** * Transform module code into SSR format. */ - // ssrTransform( - // code: string, - // inMap: SourceMap | { mappings: '' } | null, - // url: string, - // originalCode?: string, - // ): Promise + ssrTransform( + code: string, + inMap: SourceMap | { mappings: '' } | null, + url: string, + originalCode?: string, + ): Promise /** * Load a given URL as an instantiated module for SSR. */ - // ssrLoadModule( - // url: string, - // opts?: { fixStacktrace?: boolean }, - // ): Promise> + ssrLoadModule( + url: string, + opts?: { fixStacktrace?: boolean }, + ): Promise> /** * Returns a fixed version of the given stack */ - // ssrRewriteStacktrace(stack: string): string + ssrRewriteStacktrace(stack: string): string /** * Mutates the given SSR error by rewriting the stacktrace */ - // ssrFixStacktrace(e: Error): void + ssrFixStacktrace(e: Error): void /** * Triggers HMR for a module in the module graph. You can use the `server.moduleGraph` * API to retrieve the module to be reloaded. If `hmr` is false, this is a no-op. */ - // reloadModule(module: ModuleNode): Promise + reloadModule(module: ModuleNode): Promise /** * Start the server. */ @@ -390,10 +412,6 @@ export interface ViteDevServer { * @internal */ _currentServerPort?: number | undefined - /** - * @internal - */ - _currentServerHost?: string | undefined /** * @internal */ @@ -457,6 +475,7 @@ export async function _createServer( resolvedOutDirs, emptyOutDir, config.cacheDir, + !!config.experimental.fullBundleMode, ) const middlewares = connect() as Connect.Server @@ -512,15 +531,15 @@ export async function _createServer( // Backward compatibility - // let moduleGraph = new ModuleGraph({ - // client: () => environments.client.moduleGraph, - // ssr: () => environments.ssr.moduleGraph, - // }) - // const pluginContainer = createPluginContainer(environments) + let moduleGraph = new ModuleGraph({ + client: () => environments.client.moduleGraph, + ssr: () => environments.ssr.moduleGraph, + }) + const pluginContainer = createPluginContainer(environments) const closeHttpServer = createServerCloseFn(httpServer) - // const devHtmlTransformFn = createDevHtmlTransformFn(config) + const devHtmlTransformFn = createDevHtmlTransformFn(config) // Promise used by `server.close()` to ensure `closeServer()` is only called once let closeServerPromise: Promise | undefined @@ -554,90 +573,90 @@ export async function _createServer( hot: createDeprecatedHotBroadcaster(ws), environments, - // pluginContainer, - // get moduleGraph() { - // warnFutureDeprecation(config, 'removeServerModuleGraph') - // return moduleGraph - // }, - // set moduleGraph(graph) { - // moduleGraph = graph - // }, + pluginContainer, + get moduleGraph() { + warnFutureDeprecation(config, 'removeServerModuleGraph') + return moduleGraph + }, + set moduleGraph(graph) { + moduleGraph = graph + }, resolvedUrls: null, // will be set on listen - // ssrTransform( - // code: string, - // inMap: SourceMap | { mappings: '' } | null, - // url: string, - // originalCode = code, - // ) { - // return ssrTransform(code, inMap, url, originalCode, { - // json: { - // stringify: - // config.json.stringify === true && config.json.namedExports !== true, - // }, - // }) - // }, + ssrTransform( + code: string, + inMap: SourceMap | { mappings: '' } | null, + url: string, + originalCode = code, + ) { + return ssrTransform(code, inMap, url, originalCode, { + json: { + stringify: + config.json.stringify === true && config.json.namedExports !== true, + }, + }) + }, // environment.transformRequest and .warmupRequest don't take an options param for now, // so the logic and error handling needs to be duplicated here. // The only param in options that could be important is `html`, but we may remove it as // that is part of the internal control flow for the vite dev server to be able to bail // out and do the html fallback - // transformRequest(url, options) { - // warnFutureDeprecation( - // config, - // 'removeServerTransformRequest', - // 'server.transformRequest() is deprecated. Use environment.transformRequest() instead.', - // ) - // const environment = server.environments[options?.ssr ? 'ssr' : 'client'] - // return transformRequest(environment, url, options) - // }, - // async warmupRequest(url, options) { - // try { - // const environment = server.environments[options?.ssr ? 'ssr' : 'client'] - // await transformRequest(environment, url, options) - // } catch (e) { - // if ( - // e?.code === ERR_OUTDATED_OPTIMIZED_DEP || - // e?.code === ERR_CLOSED_SERVER - // ) { - // // these are expected errors - // return - // } - // // Unexpected error, log the issue but avoid an unhandled exception - // server.config.logger.error( - // buildErrorMessage(e, [`Pre-transform error: ${e.message}`], false), - // { - // error: e, - // timestamp: true, - // }, - // ) - // } - // }, - // transformIndexHtml(url, html, originalUrl) { - // return devHtmlTransformFn(server, url, html, originalUrl) - // }, - // async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { - // warnFutureDeprecation(config, 'removeSsrLoadModule') - // return ssrLoadModule(url, server, opts?.fixStacktrace) - // }, - // ssrFixStacktrace(e) { - // ssrFixStacktrace(e, server.environments.ssr.moduleGraph) - // }, - // ssrRewriteStacktrace(stack: string) { - // return ssrRewriteStacktrace(stack, server.environments.ssr.moduleGraph) - // }, - // async reloadModule(module) { - // if (serverConfig.hmr !== false && module.file) { - // // TODO: Should we also update the node moduleGraph for backward compatibility? - // const environmentModule = (module._clientModule ?? module._ssrModule)! - // updateModules( - // environments[environmentModule.environment]!, - // module.file, - // [environmentModule], - // Date.now(), - // ) - // } - // }, + transformRequest(url, options) { + warnFutureDeprecation( + config, + 'removeServerTransformRequest', + 'server.transformRequest() is deprecated. Use environment.transformRequest() instead.', + ) + const environment = server.environments[options?.ssr ? 'ssr' : 'client'] + return transformRequest(environment, url, options) + }, + async warmupRequest(url, options) { + try { + const environment = server.environments[options?.ssr ? 'ssr' : 'client'] + await transformRequest(environment, url, options) + } catch (e) { + if ( + e?.code === ERR_OUTDATED_OPTIMIZED_DEP || + e?.code === ERR_CLOSED_SERVER + ) { + // these are expected errors + return + } + // Unexpected error, log the issue but avoid an unhandled exception + server.config.logger.error( + buildErrorMessage(e, [`Pre-transform error: ${e.message}`], false), + { + error: e, + timestamp: true, + }, + ) + } + }, + transformIndexHtml(url, html, originalUrl) { + return devHtmlTransformFn(server, url, html, originalUrl) + }, + async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { + warnFutureDeprecation(config, 'removeSsrLoadModule') + return ssrLoadModule(url, server, opts?.fixStacktrace) + }, + ssrFixStacktrace(e) { + ssrFixStacktrace(e, server.environments.ssr.moduleGraph) + }, + ssrRewriteStacktrace(stack: string) { + return ssrRewriteStacktrace(stack, server.environments.ssr.moduleGraph) + }, + async reloadModule(module) { + if (serverConfig.hmr !== false && module.file) { + // TODO: Should we also update the node moduleGraph for backward compatibility? + const environmentModule = (module._clientModule ?? module._ssrModule)! + updateModules( + environments[environmentModule.environment]!, + module.file, + [environmentModule], + Date.now(), + ) + } + }, async listen(port?: number, isRestart?: boolean) { await startServer(server, port) if (httpServer) { @@ -719,7 +738,6 @@ export async function _createServer( bindCLIShortcuts(options) { bindCLIShortcuts(server, options) }, - // TODO(underfin): server restart need to update the rolldown runtime ws url async restart(forceOptimize?: boolean) { if (!server._restartPromise) { server._forceOptimizeOnRestart = !!forceOptimize @@ -770,69 +788,69 @@ export async function _createServer( setupSIGTERMListener(closeServerAndExit) } - // const onHMRUpdate = async ( - // type: 'create' | 'delete' | 'update', - // file: string, - // ) => { - // if (serverConfig.hmr !== false) { - // await handleHMRUpdate(type, file, server) - // } - // } - - // const onFileAddUnlink = async (file: string, isUnlink: boolean) => { - // file = normalizePath(file) - // reloadOnTsconfigChange(server, file) - - // await pluginContainer.watchChange(file, { - // event: isUnlink ? 'delete' : 'create', - // }) - - // if (publicDir && publicFiles) { - // if (file.startsWith(publicDir)) { - // const path = file.slice(publicDir.length) - // publicFiles[isUnlink ? 'delete' : 'add'](path) - // if (!isUnlink) { - // const clientModuleGraph = server.environments.client.moduleGraph - // const moduleWithSamePath = - // await clientModuleGraph.getModuleByUrl(path) - // const etag = moduleWithSamePath?.transformResult?.etag - // if (etag) { - // // The public file should win on the next request over a module with the - // // same path. Prevent the transform etag fast path from serving the module - // clientModuleGraph.etagToModuleMap.delete(etag) - // } - // } - // } - // } - // if (isUnlink) { - // // invalidate module graph cache on file change - // for (const environment of Object.values(server.environments)) { - // environment.moduleGraph.onFileDelete(file) - // } - // } - // await onHMRUpdate(isUnlink ? 'delete' : 'create', file) - // } - - // watcher.on('change', async (file) => { - // file = normalizePath(file) - // TODO(underfin): handle ts config.json change - // reloadOnTsconfigChange(server, file) - - // TODO(underfin): watchChange hooks how to migrate - // await pluginContainer.watchChange(file, { event: 'update' }) - // invalidate module graph cache on file change - // for (const environment of Object.values(server.environments)) { - // environment.moduleGraph.onFileChange(file) - // } - // await onHMRUpdate('update', file) - // }) - - // watcher.on('add', (file) => { - // onFileAddUnlink(file, false) - // }) - // watcher.on('unlink', (file) => { - // onFileAddUnlink(file, true) - // }) + const onHMRUpdate = async ( + type: 'create' | 'delete' | 'update', + file: string, + ) => { + if (serverConfig.hmr !== false) { + await handleHMRUpdate(type, file, server) + } + } + + const onFileAddUnlink = async (file: string, isUnlink: boolean) => { + file = normalizePath(file) + reloadOnTsconfigChange(server, file) + + await pluginContainer.watchChange(file, { + event: isUnlink ? 'delete' : 'create', + }) + + if (publicDir && publicFiles) { + if (file.startsWith(publicDir)) { + const path = file.slice(publicDir.length) + publicFiles[isUnlink ? 'delete' : 'add'](path) + if (!isUnlink) { + const clientModuleGraph = server.environments.client.moduleGraph + const moduleWithSamePath = + await clientModuleGraph.getModuleByUrl(path) + const etag = moduleWithSamePath?.transformResult?.etag + if (etag) { + // The public file should win on the next request over a module with the + // same path. Prevent the transform etag fast path from serving the module + clientModuleGraph.etagToModuleMap.delete(etag) + } + } + } + } + if (isUnlink) { + // invalidate module graph cache on file change + for (const environment of Object.values(server.environments)) { + environment.moduleGraph.onFileDelete(file) + } + } + await onHMRUpdate(isUnlink ? 'delete' : 'create', file) + } + + watcher.on('change', async (file) => { + file = normalizePath(file) + // TODO(underfin): handle ts config.json change at full bundle mode + reloadOnTsconfigChange(server, file) + + // TODO(underfin): watchChange hooks how to migrate at full bundle mode + await pluginContainer.watchChange(file, { event: 'update' }) + // invalidate module graph cache on file change + for (const environment of Object.values(server.environments)) { + environment.moduleGraph.onFileChange(file) + } + await onHMRUpdate('update', file) + }) + + watcher.on('add', (file) => { + onFileAddUnlink(file, false) + }) + watcher.on('unlink', (file) => { + onFileAddUnlink(file, true) + }) if (!middlewareMode && httpServer) { httpServer.once('listening', () => { @@ -898,23 +916,24 @@ export async function _createServer( } }) - // TODO(underfin): public dir also should be served to dist - // serve static files under /public - // this applies before the transform middleware so that these files are served - // as-is without transforms. - if (publicDir) { - middlewares.use(servePublicMiddleware(server, publicFiles)) - } - - // main transform middleware - // middlewares.use(transformMiddleware(server)) + if (config.experimental.fullBundleMode) { + // serve memory output dist files + middlewares.use(memoryFilesMiddleware(server, false)) + } else { + // serve static files under /public + // this applies before the transform middleware so that these files are served + // as-is without transforms. + if (publicDir) { + middlewares.use(servePublicMiddleware(server, publicFiles)) + } - // serve static files - // middlewares.use(serveRawFsMiddleware(server)) - // middlewares.use(c(server)) + // main transform middleware + middlewares.use(transformMiddleware(server)) - // serve memory output dist files - middlewares.use(memoryFilesMiddleware(server, false)) + // serve static files + middlewares.use(serveRawFsMiddleware(server)) + middlewares.use(serveStaticMiddleware(server)) + } // html fallback if (config.appType === 'spa' || config.appType === 'mpa') { @@ -927,9 +946,12 @@ export async function _createServer( postHooks.forEach((fn) => fn && fn()) if (config.appType === 'spa' || config.appType === 'mpa') { - // transform index.html - // middlewares.use(indexHtmlMiddleware(root, server)) - middlewares.use(memoryFilesMiddleware(server, true)) + if (config.experimental.fullBundleMode) { + middlewares.use(memoryFilesMiddleware(server, true)) + } else { + // transform index.html + middlewares.use(indexHtmlMiddleware(root, server)) + } // handle 404s middlewares.use(notFoundMiddleware()) @@ -951,7 +973,9 @@ export async function _createServer( // For backward compatibility, we call buildStart for the client // environment when initing the server. For other environments // buildStart will be called when the first request is transformed - // await environments.client.pluginContainer.buildStart() + if (!config.experimental.fullBundleMode) { + await environments.client.pluginContainer.buildStart() + } // ensure ws server started if (onListen || options.listen) { @@ -1013,7 +1037,6 @@ async function startServer( logger: server.config.logger, }) server._currentServerPort = serverPort - server._currentServerHost = hostname.host } export function createServerCloseFn( diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 42168635ea4a7d..6e88f2145dc967 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -1,447 +1,433 @@ import fs from 'node:fs' import fsp from 'node:fs/promises' import path from 'node:path' -// import MagicString from 'magic-string' -// import type { SourceMapInput } from 'rolldown' +import MagicString from 'magic-string' +import type { SourceMapInput } from 'rolldown' import type { Connect } from 'dep-types/connect' -// import type { -// DefaultTreeAdapterMap, -// Token -// } from 'parse5' -// import type { IndexHtmlTransformHook } from '../../plugins/html' -// import { -// addToHTMLProxyCache, -// applyHtmlTransforms, -// extractImportExpressionFromClassicScript, -// findNeedTransformStyleAttribute, -// getScriptInfo, -// htmlEnvHook, -// htmlProxyResult, -// injectCspNonceMetaTagHook, -// injectNonceAttributeTagHook, -// nodeIsElement, -// overwriteAttrValue, -// postImportMapHook, -// preImportMapHook, -// removeViteIgnoreAttr, -// resolveHtmlTransforms, -// traverseHtml, -// } from '../../plugins/html' -import type { - PreviewServer, - // ResolvedConfig, - ViteDevServer, -} from '../..' -import { send } from '../send' +import type { DefaultTreeAdapterMap, Token } from 'parse5' +import type { IndexHtmlTransformHook } from '../../plugins/html' import { - // CLIENT_PUBLIC_PATH, - FS_PREFIX, -} from '../../constants' + addToHTMLProxyCache, + applyHtmlTransforms, + extractImportExpressionFromClassicScript, + findNeedTransformStyleAttribute, + getScriptInfo, + htmlEnvHook, + htmlProxyResult, + injectCspNonceMetaTagHook, + injectNonceAttributeTagHook, + nodeIsElement, + overwriteAttrValue, + postImportMapHook, + preImportMapHook, + removeViteIgnoreAttr, + resolveHtmlTransforms, + traverseHtml, +} from '../../plugins/html' +import type { PreviewServer, ResolvedConfig, ViteDevServer } from '../..' +import { send } from '../send' +import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants' import { - // ensureWatchedFile, + ensureWatchedFile, fsPathFromId, - // getHash, - // injectQuery, + getHash, + injectQuery, isDevServer, - // isJSRequest, - // joinUrlSegments, - // normalizePath, - // processSrcSetSync, - // stripBase, + isJSRequest, + joinUrlSegments, + normalizePath, + processSrcSetSync, + stripBase, } from '../../utils' -// import { checkPublicFile } from '../../publicDir' -// import { isCSSRequest } from '../../plugins/css' -// import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap' -import { - cleanUrl, - // unwrapId, wrapId -} from '../../../shared/utils' -// import { getNodeAssetAttributes } from '../../assetSource' - -// interface AssetNode { -// start: number -// end: number -// code: string -// } - -// interface InlineStyleAttribute { -// index: number -// location: Token.Location -// code: string -// } - -// export function createDevHtmlTransformFn( -// config: ResolvedConfig, -// ): ( -// server: ViteDevServer, -// url: string, -// html: string, -// originalUrl?: string, -// ) => Promise { -// const [preHooks, normalHooks, postHooks] = resolveHtmlTransforms( -// config.plugins, -// config.logger, -// ) -// const transformHooks = [ -// preImportMapHook(config), -// injectCspNonceMetaTagHook(config), -// ...preHooks, -// htmlEnvHook(config), -// // devHtmlHook, -// ...normalHooks, -// ...postHooks, -// injectNonceAttributeTagHook(config), -// postImportMapHook(), -// ] -// return ( -// server: ViteDevServer, -// url: string, -// html: string, -// originalUrl?: string, -// ): Promise => { -// return applyHtmlTransforms(html, transformHooks, { -// path: url, -// filename: getHtmlFilename(url, server), -// server, -// originalUrl, -// }) -// } -// } - -// function getHtmlFilename(url: string, server: ViteDevServer) { -// if (url.startsWith(FS_PREFIX)) { -// return decodeURIComponent(fsPathFromId(url)) -// } else { -// return decodeURIComponent( -// normalizePath(path.join(server.config.root, url.slice(1))), -// ) -// } -// } - -// function shouldPreTransform(url: string, config: ResolvedConfig) { -// return ( -// !checkPublicFile(url, config) && (isJSRequest(url) || isCSSRequest(url)) -// ) -// } - -// const wordCharRE = /\w/ - -// function isBareRelative(url: string) { -// return wordCharRE.test(url[0]) && !url.includes(':') -// } - -// const processNodeUrl = ( -// url: string, -// useSrcSetReplacer: boolean, -// config: ResolvedConfig, -// htmlPath: string, -// originalUrl?: string, -// server?: ViteDevServer, -// isClassicScriptLink?: boolean, -// ): string => { -// // prefix with base (dev only, base is never relative) -// const replacer = (url: string) => { -// if ( -// (url[0] === '/' && url[1] !== '/') || -// // #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets -// // path will add `/a/` prefix, it will caused 404. -// // -// // skip if url contains `:` as it implies a url protocol or Windows path that we don't want to replace. -// // -// // rewrite `./index.js` -> `localhost:5173/a/index.js`. -// // rewrite `../index.js` -> `localhost:5173/index.js`. -// // rewrite `relative/index.js` -> `localhost:5173/a/relative/index.js`. -// ((url[0] === '.' || isBareRelative(url)) && -// originalUrl && -// originalUrl !== '/' && -// htmlPath === '/index.html') -// ) { -// url = path.posix.join(config.base, url) -// } - -// let preTransformUrl: string | undefined - -// if (!isClassicScriptLink && shouldPreTransform(url, config)) { -// if (url[0] === '/' && url[1] !== '/') { -// preTransformUrl = url -// } else if (url[0] === '.' || isBareRelative(url)) { -// preTransformUrl = path.posix.join( -// config.base, -// path.posix.dirname(htmlPath), -// url, -// ) -// } -// } - -// if (server) { -// const mod = server.environments.client.moduleGraph.urlToModuleMap.get( -// preTransformUrl || url, -// ) -// if (mod && mod.lastHMRTimestamp > 0) { -// url = injectQuery(url, `t=${mod.lastHMRTimestamp}`) -// } -// } - -// if (server && preTransformUrl) { -// try { -// preTransformUrl = decodeURI(preTransformUrl) -// } catch { -// // Malformed uri. Skip pre-transform. -// return url -// } -// preTransformRequest(server, preTransformUrl, config.decodedBase) -// } - -// return url -// } - -// const processedUrl = useSrcSetReplacer -// ? processSrcSetSync(url, ({ url }) => replacer(url)) -// : replacer(url) -// return processedUrl -// } -// const devHtmlHook: IndexHtmlTransformHook = async ( -// html, -// { path: htmlPath, filename, server, originalUrl }, -// ) => { -// const { config, watcher } = server! -// const base = config.base || '/' -// const decodedBase = config.decodedBase || '/' - -// let proxyModulePath: string -// let proxyModuleUrl: string - -// const trailingSlash = htmlPath.endsWith('/') -// if (!trailingSlash && fs.existsSync(filename)) { -// proxyModulePath = htmlPath -// proxyModuleUrl = proxyModulePath -// } else { -// // There are users of vite.transformIndexHtml calling it with url '/' -// // for SSR integrations #7993, filename is root for this case -// // A user may also use a valid name for a virtual html file -// // Mark the path as virtual in both cases so sourcemaps aren't processed -// // and ids are properly handled -// const validPath = `${htmlPath}${trailingSlash ? 'index.html' : ''}` -// proxyModulePath = `\0${validPath}` -// proxyModuleUrl = wrapId(proxyModulePath) -// } -// proxyModuleUrl = joinUrlSegments(decodedBase, proxyModuleUrl) - -// const s = new MagicString(html) -// let inlineModuleIndex = -1 -// // The key to the proxyHtml cache is decoded, as it will be compared -// // against decoded URLs by the HTML plugins. -// const proxyCacheUrl = decodeURI( -// cleanUrl(proxyModulePath).replace(normalizePath(config.root), ''), -// ) -// const styleUrl: AssetNode[] = [] -// const inlineStyles: InlineStyleAttribute[] = [] -// const inlineModulePaths: string[] = [] - -// const addInlineModule = ( -// node: DefaultTreeAdapterMap['element'], -// ext: 'js', -// ) => { -// inlineModuleIndex++ - -// const contentNode = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] - -// const code = contentNode.value - -// let map: SourceMapInput | undefined -// if (proxyModulePath[0] !== '\0') { -// map = new MagicString(html) -// .snip( -// contentNode.sourceCodeLocation!.startOffset, -// contentNode.sourceCodeLocation!.endOffset, -// ) -// .generateMap({ hires: 'boundary' }) -// map.sources = [filename] -// map.file = filename -// } - -// // add HTML Proxy to Map -// addToHTMLProxyCache(config, proxyCacheUrl, inlineModuleIndex, { code, map }) - -// // inline js module. convert to src="proxy" (dev only, base is never relative) -// const modulePath = `${proxyModuleUrl}?html-proxy&index=${inlineModuleIndex}.${ext}` -// inlineModulePaths.push(modulePath) - -// s.update( -// node.sourceCodeLocation!.startOffset, -// node.sourceCodeLocation!.endOffset, -// ``, -// ) -// preTransformRequest(server!, modulePath, decodedBase) -// } - -// await traverseHtml(html, filename, (node) => { -// if (!nodeIsElement(node)) { -// return -// } - -// // script tags -// if (node.nodeName === 'script') { -// const { src, srcSourceCodeLocation, isModule, isIgnored } = -// getScriptInfo(node) - -// if (isIgnored) { -// removeViteIgnoreAttr(s, node.sourceCodeLocation!) -// } else if (src) { -// const processedUrl = processNodeUrl( -// src.value, -// /* useSrcSetReplacer */ false, -// config, -// htmlPath, -// originalUrl, -// server, -// !isModule, -// ) -// if (processedUrl !== src.value) { -// overwriteAttrValue(s, srcSourceCodeLocation!, processedUrl) -// } -// } else if (isModule && node.childNodes.length) { -// addInlineModule(node, 'js') -// } else if (node.childNodes.length) { -// const scriptNode = node.childNodes[ -// node.childNodes.length - 1 -// ] as DefaultTreeAdapterMap['textNode'] -// for (const { -// url, -// start, -// end, -// } of extractImportExpressionFromClassicScript(scriptNode)) { -// const processedUrl = processNodeUrl( -// url, -// false, -// config, -// htmlPath, -// originalUrl, -// ) -// if (processedUrl !== url) { -// s.update(start, end, processedUrl) -// } -// } -// } -// } - -// const inlineStyle = findNeedTransformStyleAttribute(node) -// if (inlineStyle) { -// inlineModuleIndex++ -// inlineStyles.push({ -// index: inlineModuleIndex, -// location: inlineStyle.location!, -// code: inlineStyle.attr.value, -// }) -// } - -// if (node.nodeName === 'style' && node.childNodes.length) { -// const children = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] -// styleUrl.push({ -// start: children.sourceCodeLocation!.startOffset, -// end: children.sourceCodeLocation!.endOffset, -// code: children.value, -// }) -// } - -// // elements with [href/src] attrs -// const assetAttributes = getNodeAssetAttributes(node) -// for (const attr of assetAttributes) { -// if (attr.type === 'remove') { -// s.remove(attr.location.startOffset, attr.location.endOffset) -// } else { -// const processedUrl = processNodeUrl( -// attr.value, -// attr.type === 'srcset', -// config, -// htmlPath, -// originalUrl, -// ) -// if (processedUrl !== attr.value) { -// overwriteAttrValue(s, attr.location, processedUrl) -// } -// } -// } -// }) - -// // invalidate the module so the newly cached contents will be served -// const clientModuelGraph = server?.environments.client.moduleGraph -// if (clientModuelGraph) { -// await Promise.all( -// inlineModulePaths.map(async (url) => { -// const module = await clientModuelGraph.getModuleByUrl(url) -// if (module) { -// clientModuelGraph.invalidateModule(module) -// } -// }), -// ) -// } - -// await Promise.all([ -// ...styleUrl.map(async ({ start, end, code }, index) => { -// const url = `${proxyModulePath}?html-proxy&direct&index=${index}.css` - -// // ensure module in graph after successful load -// const mod = -// await server!.environments.client.moduleGraph.ensureEntryFromUrl( -// url, -// false, -// ) -// ensureWatchedFile(watcher, mod.file, config.root) - -// const result = await server!.pluginContainer.transform(code, mod.id!, { -// environment: server!.environments.client, -// }) -// let content = '' -// if (result.map && 'version' in result.map) { -// if (result.map.mappings) { -// await injectSourcesContent(result.map, proxyModulePath, config.logger) -// } -// content = getCodeWithSourcemap('css', result.code, result.map) -// } else { -// content = result.code -// } -// s.overwrite(start, end, content) -// }), -// ...inlineStyles.map(async ({ index, location, code }) => { -// // will transform with css plugin and cache result with css-post plugin -// const url = `${proxyModulePath}?html-proxy&inline-css&style-attr&index=${index}.css` - -// const mod = -// await server!.environments.client.moduleGraph.ensureEntryFromUrl( -// url, -// false, -// ) -// ensureWatchedFile(watcher, mod.file, config.root) - -// await server?.pluginContainer.transform(code, mod.id!, { -// environment: server!.environments.client, -// }) - -// const hash = getHash(cleanUrl(mod.id!)) -// const result = htmlProxyResult.get(`${hash}_${index}`) -// overwriteAttrValue(s, location, result ?? '') -// }), -// ]) - -// html = s.toString() - -// return { -// html, -// tags: [ -// { -// tag: 'script', -// attrs: { -// type: 'module', -// src: path.posix.join(base, CLIENT_PUBLIC_PATH), -// }, -// injectTo: 'head-prepend', -// }, -// ], -// } -// } - -// TODO(underfin): it only used for preview +import { checkPublicFile } from '../../publicDir' +import { isCSSRequest } from '../../plugins/css' +import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap' +import { cleanUrl, unwrapId, wrapId } from '../../../shared/utils' +import { getNodeAssetAttributes } from '../../assetSource' + +interface AssetNode { + start: number + end: number + code: string +} + +interface InlineStyleAttribute { + index: number + location: Token.Location + code: string +} + +export function createDevHtmlTransformFn( + config: ResolvedConfig, +): ( + server: ViteDevServer, + url: string, + html: string, + originalUrl?: string, +) => Promise { + const [preHooks, normalHooks, postHooks] = resolveHtmlTransforms( + config.plugins, + config.logger, + ) + const transformHooks = [ + preImportMapHook(config), + injectCspNonceMetaTagHook(config), + ...preHooks, + htmlEnvHook(config), + devHtmlHook, + ...normalHooks, + ...postHooks, + injectNonceAttributeTagHook(config), + postImportMapHook(), + ] + return ( + server: ViteDevServer, + url: string, + html: string, + originalUrl?: string, + ): Promise => { + return applyHtmlTransforms(html, transformHooks, { + path: url, + filename: getHtmlFilename(url, server), + server, + originalUrl, + }) + } +} + +function getHtmlFilename(url: string, server: ViteDevServer) { + if (url.startsWith(FS_PREFIX)) { + return decodeURIComponent(fsPathFromId(url)) + } else { + return decodeURIComponent( + normalizePath(path.join(server.config.root, url.slice(1))), + ) + } +} + +function shouldPreTransform(url: string, config: ResolvedConfig) { + return ( + !checkPublicFile(url, config) && (isJSRequest(url) || isCSSRequest(url)) + ) +} + +const wordCharRE = /\w/ + +function isBareRelative(url: string) { + return wordCharRE.test(url[0]) && !url.includes(':') +} + +const processNodeUrl = ( + url: string, + useSrcSetReplacer: boolean, + config: ResolvedConfig, + htmlPath: string, + originalUrl?: string, + server?: ViteDevServer, + isClassicScriptLink?: boolean, +): string => { + // prefix with base (dev only, base is never relative) + const replacer = (url: string) => { + if ( + (url[0] === '/' && url[1] !== '/') || + // #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets + // path will add `/a/` prefix, it will caused 404. + // + // skip if url contains `:` as it implies a url protocol or Windows path that we don't want to replace. + // + // rewrite `./index.js` -> `localhost:5173/a/index.js`. + // rewrite `../index.js` -> `localhost:5173/index.js`. + // rewrite `relative/index.js` -> `localhost:5173/a/relative/index.js`. + ((url[0] === '.' || isBareRelative(url)) && + originalUrl && + originalUrl !== '/' && + htmlPath === '/index.html') + ) { + url = path.posix.join(config.base, url) + } + + let preTransformUrl: string | undefined + + if (!isClassicScriptLink && shouldPreTransform(url, config)) { + if (url[0] === '/' && url[1] !== '/') { + preTransformUrl = url + } else if (url[0] === '.' || isBareRelative(url)) { + preTransformUrl = path.posix.join( + config.base, + path.posix.dirname(htmlPath), + url, + ) + } + } + + if (server) { + const mod = server.environments.client.moduleGraph.urlToModuleMap.get( + preTransformUrl || url, + ) + if (mod && mod.lastHMRTimestamp > 0) { + url = injectQuery(url, `t=${mod.lastHMRTimestamp}`) + } + } + + if (server && preTransformUrl) { + try { + preTransformUrl = decodeURI(preTransformUrl) + } catch { + // Malformed uri. Skip pre-transform. + return url + } + preTransformRequest(server, preTransformUrl, config.decodedBase) + } + + return url + } + + const processedUrl = useSrcSetReplacer + ? processSrcSetSync(url, ({ url }) => replacer(url)) + : replacer(url) + return processedUrl +} +const devHtmlHook: IndexHtmlTransformHook = async ( + html, + { path: htmlPath, filename, server, originalUrl }, +) => { + const { config, watcher } = server! + const base = config.base || '/' + const decodedBase = config.decodedBase || '/' + + let proxyModulePath: string + let proxyModuleUrl: string + + const trailingSlash = htmlPath.endsWith('/') + if (!trailingSlash && fs.existsSync(filename)) { + proxyModulePath = htmlPath + proxyModuleUrl = proxyModulePath + } else { + // There are users of vite.transformIndexHtml calling it with url '/' + // for SSR integrations #7993, filename is root for this case + // A user may also use a valid name for a virtual html file + // Mark the path as virtual in both cases so sourcemaps aren't processed + // and ids are properly handled + const validPath = `${htmlPath}${trailingSlash ? 'index.html' : ''}` + proxyModulePath = `\0${validPath}` + proxyModuleUrl = wrapId(proxyModulePath) + } + proxyModuleUrl = joinUrlSegments(decodedBase, proxyModuleUrl) + + const s = new MagicString(html) + let inlineModuleIndex = -1 + // The key to the proxyHtml cache is decoded, as it will be compared + // against decoded URLs by the HTML plugins. + const proxyCacheUrl = decodeURI( + cleanUrl(proxyModulePath).replace(normalizePath(config.root), ''), + ) + const styleUrl: AssetNode[] = [] + const inlineStyles: InlineStyleAttribute[] = [] + const inlineModulePaths: string[] = [] + + const addInlineModule = ( + node: DefaultTreeAdapterMap['element'], + ext: 'js', + ) => { + inlineModuleIndex++ + + const contentNode = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] + + const code = contentNode.value + + let map: SourceMapInput | undefined + if (proxyModulePath[0] !== '\0') { + map = new MagicString(html) + .snip( + contentNode.sourceCodeLocation!.startOffset, + contentNode.sourceCodeLocation!.endOffset, + ) + .generateMap({ hires: 'boundary' }) + map.sources = [filename] + map.file = filename + } + + // add HTML Proxy to Map + addToHTMLProxyCache(config, proxyCacheUrl, inlineModuleIndex, { code, map }) + + // inline js module. convert to src="proxy" (dev only, base is never relative) + const modulePath = `${proxyModuleUrl}?html-proxy&index=${inlineModuleIndex}.${ext}` + inlineModulePaths.push(modulePath) + + s.update( + node.sourceCodeLocation!.startOffset, + node.sourceCodeLocation!.endOffset, + ``, + ) + preTransformRequest(server!, modulePath, decodedBase) + } + + await traverseHtml(html, filename, (node) => { + if (!nodeIsElement(node)) { + return + } + + // script tags + if (node.nodeName === 'script') { + const { src, srcSourceCodeLocation, isModule, isIgnored } = + getScriptInfo(node) + + if (isIgnored) { + removeViteIgnoreAttr(s, node.sourceCodeLocation!) + } else if (src) { + const processedUrl = processNodeUrl( + src.value, + /* useSrcSetReplacer */ false, + config, + htmlPath, + originalUrl, + server, + !isModule, + ) + if (processedUrl !== src.value) { + overwriteAttrValue(s, srcSourceCodeLocation!, processedUrl) + } + } else if (isModule && node.childNodes.length) { + addInlineModule(node, 'js') + } else if (node.childNodes.length) { + const scriptNode = node.childNodes[ + node.childNodes.length - 1 + ] as DefaultTreeAdapterMap['textNode'] + for (const { + url, + start, + end, + } of extractImportExpressionFromClassicScript(scriptNode)) { + const processedUrl = processNodeUrl( + url, + false, + config, + htmlPath, + originalUrl, + ) + if (processedUrl !== url) { + s.update(start, end, processedUrl) + } + } + } + } + + const inlineStyle = findNeedTransformStyleAttribute(node) + if (inlineStyle) { + inlineModuleIndex++ + inlineStyles.push({ + index: inlineModuleIndex, + location: inlineStyle.location!, + code: inlineStyle.attr.value, + }) + } + + if (node.nodeName === 'style' && node.childNodes.length) { + const children = node.childNodes[0] as DefaultTreeAdapterMap['textNode'] + styleUrl.push({ + start: children.sourceCodeLocation!.startOffset, + end: children.sourceCodeLocation!.endOffset, + code: children.value, + }) + } + + // elements with [href/src] attrs + const assetAttributes = getNodeAssetAttributes(node) + for (const attr of assetAttributes) { + if (attr.type === 'remove') { + s.remove(attr.location.startOffset, attr.location.endOffset) + } else { + const processedUrl = processNodeUrl( + attr.value, + attr.type === 'srcset', + config, + htmlPath, + originalUrl, + ) + if (processedUrl !== attr.value) { + overwriteAttrValue(s, attr.location, processedUrl) + } + } + } + }) + + // invalidate the module so the newly cached contents will be served + const clientModuelGraph = server?.environments.client.moduleGraph + if (clientModuelGraph) { + await Promise.all( + inlineModulePaths.map(async (url) => { + const module = await clientModuelGraph.getModuleByUrl(url) + if (module) { + clientModuelGraph.invalidateModule(module) + } + }), + ) + } + + await Promise.all([ + ...styleUrl.map(async ({ start, end, code }, index) => { + const url = `${proxyModulePath}?html-proxy&direct&index=${index}.css` + + // ensure module in graph after successful load + const mod = + await server!.environments.client.moduleGraph.ensureEntryFromUrl( + url, + false, + ) + ensureWatchedFile(watcher, mod.file, config.root) + + const result = await server!.pluginContainer.transform(code, mod.id!, { + environment: server!.environments.client, + }) + let content = '' + if (result.map && 'version' in result.map) { + if (result.map.mappings) { + await injectSourcesContent(result.map, proxyModulePath, config.logger) + } + content = getCodeWithSourcemap('css', result.code, result.map) + } else { + content = result.code + } + s.overwrite(start, end, content) + }), + ...inlineStyles.map(async ({ index, location, code }) => { + // will transform with css plugin and cache result with css-post plugin + const url = `${proxyModulePath}?html-proxy&inline-css&style-attr&index=${index}.css` + + const mod = + await server!.environments.client.moduleGraph.ensureEntryFromUrl( + url, + false, + ) + ensureWatchedFile(watcher, mod.file, config.root) + + await server?.pluginContainer.transform(code, mod.id!, { + environment: server!.environments.client, + }) + + const hash = getHash(cleanUrl(mod.id!)) + const result = htmlProxyResult.get(`${hash}_${index}`) + overwriteAttrValue(s, location, result ?? '') + }), + ]) + + html = s.toString() + + return { + html, + tags: [ + { + tag: 'script', + attrs: { + type: 'module', + src: path.posix.join(base, CLIENT_PUBLIC_PATH), + }, + injectTo: 'head-prepend', + }, + ], + } +} + export function indexHtmlMiddleware( root: string, server: ViteDevServer | PreviewServer, @@ -470,10 +456,10 @@ export function indexHtmlMiddleware( : server.config.preview.headers try { - const html = await fsp.readFile(filePath, 'utf-8') - // if (isDev) { - // html = await server.transformIndexHtml(url, html, req.originalUrl) - // } + let html = await fsp.readFile(filePath, 'utf-8') + if (isDev) { + html = await server.transformIndexHtml(url, html, req.originalUrl) + } return send(req, res, html, 'html', { headers }) } catch (e) { return next(e) @@ -486,14 +472,14 @@ export function indexHtmlMiddleware( // NOTE: We usually don't prefix `url` and `base` with `decoded`, but in this file particularly // we're dealing with mixed encoded/decoded paths often, so we make this explicit for now. -// function preTransformRequest( -// server: ViteDevServer, -// decodedUrl: string, -// decodedBase: string, -// ) { -// if (!server.config.server.preTransformRequests) return - -// // transform all url as non-ssr as html includes client-side assets only -// decodedUrl = unwrapId(stripBase(decodedUrl, decodedBase)) -// server.warmupRequest(decodedUrl) -// } +function preTransformRequest( + server: ViteDevServer, + decodedUrl: string, + decodedBase: string, +) { + if (!server.config.server.preTransformRequests) return + + // transform all url as non-ssr as html includes client-side assets only + decodedUrl = unwrapId(stripBase(decodedUrl, decodedBase)) + server.warmupRequest(decodedUrl) +} diff --git a/packages/vite/src/node/server/warmup.ts b/packages/vite/src/node/server/warmup.ts index 33109534ba976d..7911e0b647df07 100644 --- a/packages/vite/src/node/server/warmup.ts +++ b/packages/vite/src/node/server/warmup.ts @@ -1,6 +1,6 @@ -// import fs from 'node:fs/promises' +import fs from 'node:fs/promises' import path from 'node:path' -// import colors from 'picocolors' +import colors from 'picocolors' import { glob, isDynamicPattern } from 'tinyglobby' import { FS_PREFIX } from '../constants' import { normalizePath } from '../utils' @@ -30,19 +30,19 @@ async function warmupFile( if (file.endsWith('.html')) { const url = htmlFileToUrl(file, server.config.root) if (url) { - // try { - // const html = await fs.readFile(file, 'utf-8') - // await server.transformIndexHtml(url, html) - // } catch (e) { - // // Unexpected error, log the issue but avoid an unhandled exception - // environment.logger.error( - // `Pre-transform error (${colors.cyan(file)}): ${e.message}`, - // { - // error: e, - // timestamp: true, - // }, - // ) - // } + try { + const html = await fs.readFile(file, 'utf-8') + await server.transformIndexHtml(url, html) + } catch (e) { + // Unexpected error, log the issue but avoid an unhandled exception + environment.logger.error( + `Pre-transform error (${colors.cyan(file)}): ${e.message}`, + { + error: e, + timestamp: true, + }, + ) + } } } // for other files, pass it through `transformRequest` with warmup diff --git a/packages/vite/src/node/watch.ts b/packages/vite/src/node/watch.ts index 62530b1f8370c0..b3ba7cef736bd4 100644 --- a/packages/vite/src/node/watch.ts +++ b/packages/vite/src/node/watch.ts @@ -51,8 +51,9 @@ export function resolveEmptyOutDir( export function resolveChokidarOptions( options: WatchOptions | undefined, resolvedOutDirs: Set, - _emptyOutDir: boolean, + emptyOutDir: boolean, cacheDir: string, + fullBundleMode: boolean, ): WatchOptions { const { ignored: ignoredList, ...otherOptions } = options ?? {} const ignored: WatchOptions['ignored'] = [ @@ -62,12 +63,12 @@ export function resolveChokidarOptions( escapePath(cacheDir) + '/**', ...arraify(ignoredList || []), ] - // TODO(underfin): revert it if the dev build only write output to memory. - // if (emptyOutDir) { - ignored.push( - ...[...resolvedOutDirs].map((outDir) => escapePath(outDir) + '/**'), - ) - // } + // TODO(underfin): revert it if the dev build only write output to memory at full bundle mode. + if (emptyOutDir || fullBundleMode) { + ignored.push( + ...[...resolvedOutDirs].map((outDir) => escapePath(outDir) + '/**'), + ) + } const resolvedWatchOptions: WatchOptions = { ignored, diff --git a/packages/vite/types/hmrPayload.d.ts b/packages/vite/types/hmrPayload.d.ts index adc05f7082d7d0..36aac97895df42 100644 --- a/packages/vite/types/hmrPayload.d.ts +++ b/packages/vite/types/hmrPayload.d.ts @@ -24,10 +24,10 @@ export interface UpdatePayload { export interface Update { type: 'js-update' | 'css-update' - url?: string // the hmr chunk url + url?: string // the hmr chunk url, it only exists for full bundle mode path: string acceptedPath: string - // timestamp: number + timestamp: number /** @internal */ explicitImportRequired?: boolean /** @internal */ diff --git a/playground/hmr/file-delete-restore/index.js b/playground/hmr/file-delete-restore/index.js index 4e4dd963730774..fa4908a32662ac 100644 --- a/playground/hmr/file-delete-restore/index.js +++ b/playground/hmr/file-delete-restore/index.js @@ -1,5 +1,4 @@ -// TODO(underfin): https://github.com/rolldown/rolldown/issues/4061 -// import { render } from './runtime' -// import { childValue, parentValue } from './parent' +import { render } from './runtime' +import { childValue, parentValue } from './parent' -// render({ parent: parentValue, child: childValue }) +render({ parent: parentValue, child: childValue }) diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index 9669d16f5f8748..f9e21a1c2fee67 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -14,11 +14,6 @@ export default defineConfig({ test: { include: ['./playground/**/*.spec.[tj]s'], exclude: [ - './playground/hmr/**/*.spec.[tj]s', - './playground/html/**/*.spec.[tj]s', - './playground/resolve/**/*.spec.[tj]s', - './playground/ssr/**/*.spec.[tj]s', - './playground/ssr*/**/*.spec.[tj]s', './playground/legacy/**/*.spec.[tj]s', // system format ...(isBuild ? [ From 2e990e6daa45c11e850a6984d05451ab6f6cd327 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 16:33:50 +0800 Subject: [PATCH 23/76] chore: add comment code --- playground/vitestSetup.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index ada72439094c79..3e1794e32aa3f3 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -238,6 +238,11 @@ export async function startDefaultServe(): Promise { if (!isBuild) { process.env.VITE_INLINE = 'inline-serve' const config = await loadConfig({ command: 'serve', mode: 'development' }) + // test full bundle mode + // viteServer = server = await createServer(config) + // const builder = await createBuilder(config, null, 'serve') + // await builder.buildApp(server) + // await server.listen() viteServer = server = await (await createServer(config)).listen() viteTestUrl = stripTrailingSlashIfNeeded( server.resolvedUrls.local[0], From 86e5869d4c8f7613d9f0e0e74c65db988389609b Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 16:36:43 +0800 Subject: [PATCH 24/76] chore: update pnpm.lock --- pnpm-lock.yaml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3217ca4590619..50e29989b42601 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7534,7 +7534,6 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true -<<<<<<< HEAD vitepress-plugin-group-icons@1.5.2: resolution: {integrity: sha512-zen07KxZ83y3eecou4EraaEgwIriwHaB5Q0cHAmS4yO1UZEQvbljTylHPqiJ7LNkV39U8VehfcyquAJXg/26LA==} @@ -9928,13 +9927,17 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 +<<<<<<< HEAD '@vitest/mocker@3.1.1(vite@packages+vite)': +======= + '@vitest/mocker@3.0.8(vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.29.3)(sass-embedded@1.85.1(source-map-js@1.2.1))(sass@1.85.1)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': +>>>>>>> a2d094f19 (chore: make test work, avoid overrides vitest vite) dependencies: '@vitest/spy': 3.1.3 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.29.3)(sass-embedded@1.85.1(source-map-js@1.2.1))(sass@1.85.1)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + vite: 6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) '@vitest/pretty-format@3.1.3': dependencies: @@ -13846,10 +13849,7 @@ snapshots: transitivePeerDependencies: - supports-color -<<<<<<< HEAD - vitepress-plugin-group-icons@1.5.2: -======= - vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.29.3)(sass-embedded@1.85.1(source-map-js@1.2.1))(sass@1.85.1)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: esbuild: 0.25.0 postcss: 8.5.3 @@ -13858,18 +13858,17 @@ snapshots: '@types/node': 22.13.6 fsevents: 2.3.3 jiti: 2.4.2 - less: 4.2.2 + less: 4.3.0 lightningcss: 1.29.3 - sass: 1.85.1 - sass-embedded: 1.85.1(source-map-js@1.2.1) + sass: 1.86.3 + sass-embedded: 1.86.3(source-map-js@1.2.1) stylus: 0.64.0 sugarss: 5.0.0(postcss@8.5.3) terser: 5.39.0 tsx: 4.19.3 yaml: 2.7.0 - vitepress-plugin-group-icons@1.3.7: ->>>>>>> a2d094f19 (chore: make test work, avoid overrides vitest vite) + vitepress-plugin-group-icons@1.5.2: dependencies: '@iconify-json/logos': 1.2.4 '@iconify-json/vscode-icons': 1.2.19 @@ -13934,10 +13933,11 @@ snapshots: - typescript - universal-cookie +<<<<<<< HEAD vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.13.6): dependencies: '@vitest/expect': 3.1.1 - '@vitest/mocker': 3.1.1(vite@packages+vite) + '@vitest/mocker': 3.1.1(vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) '@vitest/pretty-format': 3.1.1 '@vitest/runner': 3.1.1 '@vitest/snapshot': 3.1.1 @@ -13954,8 +13954,7 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 -<<<<<<< HEAD - vite: link:packages/vite + vite: 6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) vite-node: 3.1.1 why-is-node-running: 2.3.0 optionalDependencies: From 02942289a4b4bbe1db09107f9082d9fb87ab29d5 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 16:42:52 +0800 Subject: [PATCH 25/76] chore: fix createBuilder typing --- packages/vite/src/node/build.ts | 2 +- playground/vitestSetup.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index bf0ab5b15ba178..e17b136b81b763 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -1752,7 +1752,7 @@ export type ResolvedBuilderOptions = Required export async function createBuilder( inlineConfig: InlineConfig = {}, useLegacyBuilder: null | boolean = false, - command: 'build' | 'serve', + command: 'build' | 'serve' = 'build', ): Promise { const patchConfig = (resolved: ResolvedConfig) => { if (!(useLegacyBuilder ?? !resolved.builder)) return diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index 3e1794e32aa3f3..ce08cbbb94c7ff 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -266,7 +266,7 @@ export async function startDefaultServe(): Promise { }, ) if (buildConfig.builder) { - const builder = await createBuilder(buildConfig, null, 'build') + const builder = await createBuilder(buildConfig) await builder.buildApp() } else { const rollupOutput = await build(buildConfig) From 076873610ead8e4b281fe30efae49a33ebaa8684 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 16:53:17 +0800 Subject: [PATCH 26/76] chore: fix cli fullBundleMode --- packages/vite/src/node/build.ts | 14 ++++++++------ packages/vite/src/node/cli.ts | 6 ++++++ playground/define/package.json | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index e17b136b81b763..2e41ca93aa2457 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -587,7 +587,7 @@ async function buildEnvironment( environment: BuildEnvironment, server?: ViteDevServer ): Promise { - const { root, packageCache } = environment.config + const { root, packageCache, experimental, command } = environment.config const options = environment.config.build const libOptions = options.lib const { logger } = environment @@ -821,11 +821,13 @@ async function buildEnvironment( (isSsrTargetWebworkerEnvironment && (typeof input === 'string' || Object.keys(input).length === 1)), minify: - options.minify === 'oxc' - ? true - : options.minify === false - ? 'dce-only' - : false, + experimental.fullBundleMode && command === 'serve' + ? false + : options.minify === 'oxc' + ? true + : options.minify === false + ? 'dce-only' + : false, ...output, } } diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 390c84fecc61ba..3d46da8d323232 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -98,6 +98,7 @@ function cleanGlobalCLIOptions( delete ret.mode delete ret.force delete ret.w + delete ret.fullBundleMode // convert the sourcemap option to a boolean if necessary if ('sourcemap' in ret) { @@ -177,6 +178,10 @@ cli '--force', `[boolean] force the optimizer to ignore the cache and re-bundle`, ) + .option( + '--fullBundleMode', + `[boolean] Enable bundle at dev to instead of esm dev server`, + ) // TODO(underfin): Consider how to merge the build option into dev command. .action( async ( @@ -239,6 +244,7 @@ cli server: cleanGlobalCLIOptions(options), forceOptimizeDeps: options.force, }) + await server.listen() return server } diff --git a/playground/define/package.json b/playground/define/package.json index a65b36c1c3df67..2cbf15718cc1d4 100644 --- a/playground/define/package.json +++ b/playground/define/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --fullBundleMode", "build": "vite build", "debug": "node --inspect-brk ../../packages/vite/bin/vite", "preview": "vite preview" From 8a0f40c7f1a3fa684180e1a02e163a00375e2f81 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 17:02:33 +0800 Subject: [PATCH 27/76] fix: __FULL_BUNDLE_MODE__ replace --- packages/vite/src/node/plugins/clientInjections.ts | 4 ++-- playground/define/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index 3fddc7c81d16c2..0cd428dd464e78 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -93,7 +93,7 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) .replace(`__WS_TOKEN__`, wsTokenReplacement) - .replace( + .replaceAll( `__FULL_BUNDLE_MODE__`, escapeReplacement(config.experimental.fullBundleMode || false), ) @@ -187,7 +187,7 @@ export async function getHmrImplement(config: ResolvedConfig): Promise { .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) .replace(`__WS_TOKEN__`, wsTokenReplacement) - .replace( + .replaceAll( `__FULL_BUNDLE_MODE__`, escapeReplacement(config.experimental.fullBundleMode || false), ) diff --git a/playground/define/package.json b/playground/define/package.json index 2cbf15718cc1d4..a65b36c1c3df67 100644 --- a/playground/define/package.json +++ b/playground/define/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite --fullBundleMode", + "dev": "vite", "build": "vite build", "debug": "node --inspect-brk ../../packages/vite/bin/vite", "preview": "vite preview" From 7aba83207d63aa286d4aa8f6421c8818e33a150d Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:44:38 +0900 Subject: [PATCH 28/76] fix: merge experimental options --- packages/vite/src/node/build.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 2e41ca93aa2457..7ebbab2d74c52b 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -672,6 +672,7 @@ async function buildEnvironment( '.css': 'js', }, experimental: { + ...options.rollupOptions.experimental, hmr: server ? { implement: await getHmrImplement(environment.config), From 3d991d90b248ae1941922b85ea73ba91bb405a06 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 17:50:51 +0800 Subject: [PATCH 29/76] fix: aovid replace import.meta.hot at full bundle mode --- packages/vite/src/node/plugins/define.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index 922ba63a62db38..d12d494f7d061a 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,8 +12,7 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - const isBuild = - config.command === 'build' || config.experimental.fullBundleMode + const isBuild = config.command === 'build' const isBuildLib = isBuild && config.build.lib // ignore replace process.env in lib build From 0d4f34c542edbba1631625a6e2551cba7ff668d8 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 21 Apr 2025 18:05:46 +0800 Subject: [PATCH 30/76] fix: unit test --- packages/vite/src/node/__tests__/plugins/import.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/vite/src/node/__tests__/plugins/import.spec.ts b/packages/vite/src/node/__tests__/plugins/import.spec.ts index c43bfed53115a8..63c82af2bad023 100644 --- a/packages/vite/src/node/__tests__/plugins/import.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/import.spec.ts @@ -75,10 +75,7 @@ describe('transformCjsImport', () => { ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + - 'const react = ((m) => m?.__esModule ? m : {\n' + - '\t...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {},\n' + - '\tdefault: m\n' + - '})(__vite__cjsImport0_react)', + 'const react = ((m) => m?.__esModule ? m : { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m })(__vite__cjsImport0_react)', ) }) From 92782456e0c066f0f52ba6a2d3e1978f5255aa06 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 11:09:56 +0800 Subject: [PATCH 31/76] fix: define at full bundle mode --- packages/vite/src/node/plugins/define.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index d12d494f7d061a..922ba63a62db38 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,7 +12,8 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - const isBuild = config.command === 'build' + const isBuild = + config.command === 'build' || config.experimental.fullBundleMode const isBuildLib = isBuild && config.build.lib // ignore replace process.env in lib build From 647fba4835ea1bf3fa8e69215556520acd137b93 Mon Sep 17 00:00:00 2001 From: underfin <2218301630@qq.com> Date: Tue, 22 Apr 2025 11:19:22 +0800 Subject: [PATCH 32/76] Update packages/vite/src/client/hmrModuleRunner.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 翠 / green --- packages/vite/src/client/hmrModuleRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 9a6e6c18d1ae2d..028399db45bb54 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -79,5 +79,5 @@ if (__FULL_BUNDLE_MODE__) { // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) } - DevRuntime.getInstance() + globalThis.__rolldown_runtime__ ||= new DevRuntime() } From 3909c8bb4937afde478b258210044c5d2fae093a Mon Sep 17 00:00:00 2001 From: underfin <2218301630@qq.com> Date: Tue, 22 Apr 2025 11:19:58 +0800 Subject: [PATCH 33/76] Update packages/vite/src/client/hmrModuleRunner.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 翠 / green --- packages/vite/src/client/hmrModuleRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 028399db45bb54..3987d4499d3070 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -27,7 +27,7 @@ if (__FULL_BUNDLE_MODE__) { registerModule( id: string, - esmExportGettersOrCjsExports: Record any>, + esmExportGettersOrCjsExports: Record, meta: { cjs?: boolean } = {}, ) { const exports = {} From 9f4cdc574c2b98010a6f60e826c0342cab6780ca Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 11:24:26 +0800 Subject: [PATCH 34/76] chore: update clientInjection --- .../vite/src/node/plugins/clientInjections.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index 0cd428dd464e78..b55a18d95695cc 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -78,6 +78,9 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { const hmrEnableOverlayReplacement = escapeReplacement(overlay) const hmrConfigNameReplacement = escapeReplacement(hmrConfigName) const wsTokenReplacement = escapeReplacement(config.webSocketToken) + const fullBundleModeReplacement = escapeReplacement( + config.experimental.fullBundleMode || false, + ) injectConfigValues = (code: string) => { return code @@ -93,10 +96,7 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) .replace(`__WS_TOKEN__`, wsTokenReplacement) - .replaceAll( - `__FULL_BUNDLE_MODE__`, - escapeReplacement(config.experimental.fullBundleMode || false), - ) + .replaceAll(`__FULL_BUNDLE_MODE__`, fullBundleModeReplacement) } }, async transform(code, id, options) { @@ -173,6 +173,9 @@ export async function getHmrImplement(config: ResolvedConfig): Promise { const hmrEnableOverlayReplacement = escapeReplacement(overlay) const hmrConfigNameReplacement = escapeReplacement(hmrConfigName) const wsTokenReplacement = escapeReplacement(config.webSocketToken) + const fullBundleModeReplacement = escapeReplacement( + config.experimental.fullBundleMode || false, + ) return content .replace(`__MODE__`, modeReplacement) @@ -187,8 +190,5 @@ export async function getHmrImplement(config: ResolvedConfig): Promise { .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) .replace(`__WS_TOKEN__`, wsTokenReplacement) - .replaceAll( - `__FULL_BUNDLE_MODE__`, - escapeReplacement(config.experimental.fullBundleMode || false), - ) + .replace(`__FULL_BUNDLE_MODE__`, fullBundleModeReplacement) } From 23d8d36ba66b06b34fe21a540a488490e16f6056 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 11:26:16 +0800 Subject: [PATCH 35/76] fix: skip cachedTransformMiddleware at full bundle mode --- packages/vite/src/node/server/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index a4cc275c887946..25c19066363812 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -888,7 +888,9 @@ export async function _createServer( middlewares.use(hostCheckMiddleware(config, false)) } - middlewares.use(cachedTransformMiddleware(server)) + if (!config.experimental.fullBundleMode) { + middlewares.use(cachedTransformMiddleware(server)) + } // proxy const { proxy } = serverConfig From cb6710f3ec62bc82d9e030741e1f0c37bc404b95 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 14:59:46 +0800 Subject: [PATCH 36/76] fix: enable servePublicMiddleware at full bundle mode --- packages/vite/src/node/server/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 25c19066363812..0370566f832ad1 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -918,17 +918,17 @@ export async function _createServer( } }) + // serve static files under /public + // this applies before the transform middleware so that these files are served + // as-is without transforms. + if (publicDir) { + middlewares.use(servePublicMiddleware(server, publicFiles)) + } + if (config.experimental.fullBundleMode) { // serve memory output dist files middlewares.use(memoryFilesMiddleware(server, false)) } else { - // serve static files under /public - // this applies before the transform middleware so that these files are served - // as-is without transforms. - if (publicDir) { - middlewares.use(servePublicMiddleware(server, publicFiles)) - } - // main transform middleware middlewares.use(transformMiddleware(server)) From 5e9a7ca6c211f8f970cfa970aeb7ed93c2d3cbef Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 15:18:16 +0800 Subject: [PATCH 37/76] fix: make sure the server port and then start build --- packages/vite/src/node/cli.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 3d46da8d323232..1f739b5713d113 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -223,11 +223,11 @@ cli if (!server.httpServer) { throw new Error('HTTP server not available') } + // Need to make sure the server port and then start build. + await server.listen() await builder.buildApp(server) - await server.listen() - return server } From cff15fb8004232192cab26c772fc64bc237e531b Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 15:58:24 +0800 Subject: [PATCH 38/76] refatcor: extract replaceClientConfigValues --- packages/vite/src/client/hmrModuleRunner.ts | 1 + .../vite/src/node/plugins/clientInjections.ts | 85 +++---------------- 2 files changed, 13 insertions(+), 73 deletions(-) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 3987d4499d3070..497a1100a9fa99 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -79,5 +79,6 @@ if (__FULL_BUNDLE_MODE__) { // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) } + // @ts-expect-error __rolldown_runtime__ globalThis.__rolldown_runtime__ ||= new DevRuntime() } diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index b55a18d95695cc..950af270c23849 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -16,8 +16,6 @@ const normalizedEnvEntry = normalizePath(ENV_ENTRY) * @server-only */ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { - let injectConfigValues: (code: string) => string - const getDefineReplacer = perEnvironmentState((environment) => { const userDefine: Record = {} for (const key in environment.config.define) { @@ -33,78 +31,12 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:client-inject', - async buildStart() { - const resolvedServerHostname = (await resolveHostname(config.server.host)) - .name - const resolvedServerPort = config.server.port! - const devBase = config.base - - const serverHost = `${resolvedServerHostname}:${resolvedServerPort}${devBase}` - - let hmrConfig = config.server.hmr - hmrConfig = isObject(hmrConfig) ? hmrConfig : undefined - const host = hmrConfig?.host || null - const protocol = hmrConfig?.protocol || null - const timeout = hmrConfig?.timeout || 30000 - const overlay = hmrConfig?.overlay !== false - const isHmrServerSpecified = !!hmrConfig?.server - const hmrConfigName = path.basename(config.configFile || 'vite.config.js') - - // hmr.clientPort -> hmr.port - // -> (24678 if middleware mode and HMR server is not specified) -> new URL(import.meta.url).port - let port = hmrConfig?.clientPort || hmrConfig?.port || null - if (config.server.middlewareMode && !isHmrServerSpecified) { - port ||= 24678 - } - - let directTarget = hmrConfig?.host || resolvedServerHostname - directTarget += `:${hmrConfig?.port || resolvedServerPort}` - directTarget += devBase - - let hmrBase = devBase - if (hmrConfig?.path) { - hmrBase = path.posix.join(hmrBase, hmrConfig.path) - } - - const modeReplacement = escapeReplacement(config.mode) - const baseReplacement = escapeReplacement(devBase) - const serverHostReplacement = escapeReplacement(serverHost) - const hmrProtocolReplacement = escapeReplacement(protocol) - const hmrHostnameReplacement = escapeReplacement(host) - const hmrPortReplacement = escapeReplacement(port) - const hmrDirectTargetReplacement = escapeReplacement(directTarget) - const hmrBaseReplacement = escapeReplacement(hmrBase) - const hmrTimeoutReplacement = escapeReplacement(timeout) - const hmrEnableOverlayReplacement = escapeReplacement(overlay) - const hmrConfigNameReplacement = escapeReplacement(hmrConfigName) - const wsTokenReplacement = escapeReplacement(config.webSocketToken) - const fullBundleModeReplacement = escapeReplacement( - config.experimental.fullBundleMode || false, - ) - - injectConfigValues = (code: string) => { - return code - .replace(`__MODE__`, modeReplacement) - .replace(/__BASE__/g, baseReplacement) - .replace(`__SERVER_HOST__`, serverHostReplacement) - .replace(`__HMR_PROTOCOL__`, hmrProtocolReplacement) - .replace(`__HMR_HOSTNAME__`, hmrHostnameReplacement) - .replace(`__HMR_PORT__`, hmrPortReplacement) - .replace(`__HMR_DIRECT_TARGET__`, hmrDirectTargetReplacement) - .replace(`__HMR_BASE__`, hmrBaseReplacement) - .replace(`__HMR_TIMEOUT__`, hmrTimeoutReplacement) - .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) - .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) - .replace(`__WS_TOKEN__`, wsTokenReplacement) - .replaceAll(`__FULL_BUNDLE_MODE__`, fullBundleModeReplacement) - } - }, async transform(code, id, options) { // TODO: Remove options?.ssr, Vitest currently hijacks this plugin const ssr = options?.ssr ?? this.environment.config.consumer === 'server' if (id === normalizedClientEntry || id === normalizedEnvEntry) { const defineReplacer = getDefineReplacer(this) - return defineReplacer(injectConfigValues(code)) + return defineReplacer(await replaceClientConfigValues(code, config)) } else if (!ssr && code.includes('process.env.NODE_ENV')) { // replace process.env.NODE_ENV instead of defining a global // for it to avoid shimming a `process` object during dev, @@ -127,8 +59,10 @@ function escapeReplacement(value: string | number | boolean | null) { return () => jsonValue } -export async function getHmrImplement(config: ResolvedConfig): Promise { - const content = fs.readFileSync(normalizedClientEntry, 'utf-8') +async function replaceClientConfigValues( + code: string, + config: ResolvedConfig, +): Promise { const resolvedServerHostname = (await resolveHostname(config.server.host)) .name const resolvedServerPort = config.server.port! @@ -177,7 +111,7 @@ export async function getHmrImplement(config: ResolvedConfig): Promise { config.experimental.fullBundleMode || false, ) - return content + return code .replace(`__MODE__`, modeReplacement) .replace(/__BASE__/g, baseReplacement) .replace(`__SERVER_HOST__`, serverHostReplacement) @@ -190,5 +124,10 @@ export async function getHmrImplement(config: ResolvedConfig): Promise { .replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement) .replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement) .replace(`__WS_TOKEN__`, wsTokenReplacement) - .replace(`__FULL_BUNDLE_MODE__`, fullBundleModeReplacement) + .replaceAll(`__FULL_BUNDLE_MODE__`, fullBundleModeReplacement) +} + +export async function getHmrImplement(config: ResolvedConfig): Promise { + const content = fs.readFileSync(normalizedClientEntry, 'utf-8') + return replaceClientConfigValues(content, config) } From 7966b11cfad2b8dc880b68054c0bf66829a30237 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 16:01:16 +0800 Subject: [PATCH 39/76] fix: aoivd import @vite/env at custome rolldown runtime --- packages/vite/src/client/env.ts | 51 +++++++++---------- .../vite/src/node/plugins/clientInjections.ts | 6 ++- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/packages/vite/src/client/env.ts b/packages/vite/src/client/env.ts index 943e2d11214cfc..158ce43f7bf36f 100644 --- a/packages/vite/src/client/env.ts +++ b/packages/vite/src/client/env.ts @@ -1,31 +1,28 @@ declare const __DEFINES__: Record -declare const __FULL_BUNDLE_MODE__: boolean -if (!__FULL_BUNDLE_MODE__) { - const context = (() => { - if (typeof globalThis !== 'undefined') { - return globalThis - } else if (typeof self !== 'undefined') { - return self - } else if (typeof window !== 'undefined') { - return window - } else { - return Function('return this')() - } - })() +const context = (() => { + if (typeof globalThis !== 'undefined') { + return globalThis + } else if (typeof self !== 'undefined') { + return self + } else if (typeof window !== 'undefined') { + return window + } else { + return Function('return this')() + } +})() - // assign defines - const defines = __DEFINES__ - Object.keys(defines).forEach((key) => { - const segments = key.split('.') - let target = context - for (let i = 0; i < segments.length; i++) { - const segment = segments[i] - if (i === segments.length - 1) { - target[segment] = defines[key] - } else { - target = target[segment] || (target[segment] = {}) - } +// assign defines +const defines = __DEFINES__ +Object.keys(defines).forEach((key) => { + const segments = key.split('.') + let target = context + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + if (i === segments.length - 1) { + target[segment] = defines[key] + } else { + target = target[segment] || (target[segment] = {}) } - }) -} + } +}) diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index 950af270c23849..93c784b2d28b98 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -129,5 +129,9 @@ async function replaceClientConfigValues( export async function getHmrImplement(config: ResolvedConfig): Promise { const content = fs.readFileSync(normalizedClientEntry, 'utf-8') - return replaceClientConfigValues(content, config) + return ( + (await replaceClientConfigValues(content, config)) + // the rolldown runtime shouldn't be importer a module + .replace(`import '@vite/env'`, '') + ) } From 1aafd0d89da9f9c1e5e1c6b6dc3baff1a641ef8b Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 16:31:01 +0800 Subject: [PATCH 40/76] fix: hmr root --- packages/vite/src/node/build.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 7ebbab2d74c52b..4474fa1f803dca 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -945,6 +945,18 @@ async function buildEnvironment( server.watcher.on('change', async (file) => { const startTime = Date.now() const hmrOutput = (await bundle!.generateHmrPatch([file]))! + // @ts-expect-error Need to upgrade rolldown + if (hmrOutput.fullReload) { + await build() + server.ws.send({ + type: 'full-reload', + }) + logger.info(colors.green(`page reload `) + colors.dim(file), { + clear: true, + timestamp: true, + }) + } + if (hmrOutput.patch) { const url = `${Date.now()}.js` server.memoryFiles[url] = hmrOutput.patch From d3feb622e3cae01be997a6e2a22290a1b34d23c4 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 22 Apr 2025 16:39:09 +0800 Subject: [PATCH 41/76] fix: close bundle at cli exit --- packages/vite/src/node/build.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 4474fa1f803dca..cd80f536de507e 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -62,6 +62,7 @@ import { mergeWithDefaults, normalizePath, partialEncodeURIPath, + setupSIGTERMListener, unique, } from './utils' import { perEnvironmentPlugin, resolveEnvironmentPlugins } from './plugin' @@ -993,8 +994,20 @@ async function buildEnvironment( } throw e } finally { - // close the bundle will make the rolldown hmr invalid, so dev build need to disable it. - if (bundle && !server) await bundle.close() + // Note: close the bundle will make the rolldown hmr invalid, so dev build need to disable it. + if (server) { + const closeBundleAndExit = async (_: unknown, exitCode?: number) => { + try { + if (bundle) await bundle.close() + } finally { + process.exitCode ??= exitCode ? 128 + exitCode : undefined + process.exit() + } + } + setupSIGTERMListener(closeBundleAndExit) + } else { + if (bundle) await bundle.close() + } } } From e75f4e9d61cb8b94fe65e0cf5f7eef514d918009 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 23 Apr 2025 15:58:25 +0800 Subject: [PATCH 42/76] fix: aovid replace import.met.hot to undefined --- packages/vite/src/node/plugins/define.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index 922ba63a62db38..121d0ea947cbf5 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,8 +12,7 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - const isBuild = - config.command === 'build' || config.experimental.fullBundleMode + const isBuild = config.command === 'build' const isBuildLib = isBuild && config.build.lib // ignore replace process.env in lib build @@ -34,7 +33,7 @@ export function definePlugin(config: ResolvedConfig): Plugin { const importMetaKeys: Record = {} const importMetaEnvKeys: Record = {} const importMetaFallbackKeys: Record = {} - if (isBuild) { + if (isBuild && !config.experimental.fullBundleMode) { importMetaKeys['import.meta.hot'] = `undefined` for (const key in config.env) { const val = JSON.stringify(config.env[key]) @@ -55,7 +54,11 @@ export function definePlugin(config: ResolvedConfig): Plugin { userDefine[key] = handleDefineValue(environment.config.define[key]) // make sure `import.meta.env` object has user define properties - if (isBuild && key.startsWith('import.meta.env.')) { + if ( + isBuild && + !config.experimental.fullBundleMode && + key.startsWith('import.meta.env.') + ) { userDefineEnv[key.slice(16)] = environment.config.define[key] } } @@ -129,7 +132,11 @@ export function definePlugin(config: ResolvedConfig): Plugin { transform: { async handler(code, id) { - if (this.environment.config.consumer === 'client' && !isBuild) { + if ( + this.environment.config.consumer === 'client' && + !isBuild && + !config.experimental.fullBundleMode + ) { // for dev we inject actual global defines in the vite client to // avoid the transform cost. see the `clientInjection` and // `importAnalysis` plugin. From 9b34da0fc4cba04f30394b561611a693ee7db603 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 23 Apr 2025 16:47:33 +0800 Subject: [PATCH 43/76] feat: setup full bundle mode test --- package.json | 1 + packages/vite/index.cjs | 1 + packages/vite/src/node/index.ts | 2 +- playground/vitestSetup.ts | 20 ++++++++++++++------ vitest.config.e2e.ts | 8 +++++++- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f5d544ab609552..e22addcdc322a9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "lint": "eslint --cache .", "typecheck": "tsc -p scripts --noEmit && pnpm -r --parallel run typecheck", "test": "pnpm test-unit && pnpm test-serve && pnpm test-build", + "test-full-bundle-mode": "VITE_TEST_FULL_BUNDLE_MODE=1 vitest run -c vitest.config.e2e.ts", "test-serve": "vitest run -c vitest.config.e2e.ts", "test-build": "VITE_TEST_BUILD=1 vitest run -c vitest.config.e2e.ts", "test-unit": "vitest run", diff --git a/packages/vite/index.cjs b/packages/vite/index.cjs index ec356c719ff4b9..61edace193cb82 100644 --- a/packages/vite/index.cjs +++ b/packages/vite/index.cjs @@ -13,6 +13,7 @@ Object.assign(module.exports, require('./dist/node-cjs/publicUtils.cjs')) const asyncFunctions = [ 'build', 'createServer', + 'createServerWithResolvedConfig', 'preview', 'transformWithEsbuild', 'transformWithOxc', diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 1286b2ca2c7c9d..e770ccd022d17d 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -11,7 +11,7 @@ export { } from './config' export { perEnvironmentPlugin } from './plugin' export { perEnvironmentState } from './environment' -export { createServer } from './server' +export { createServer, createServerWithResolvedConfig } from './server' export { preview } from './preview' export { build, createBuilder } from './build' diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index ce08cbbb94c7ff..07756b025b4265 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -15,6 +15,7 @@ import { build, createBuilder, createServer, + createServerWithResolvedConfig, loadConfigFromFile, mergeConfig, preview, @@ -227,6 +228,9 @@ async function loadConfig(configEnv: ConfigEnv) { // tests are flaky when `emptyOutDir` is `true` emptyOutDir: false, }, + experimental: { + fullBundleMode: !!process.env.VITE_TEST_FULL_BUNDLE_MODE, + }, customLogger: createInMemoryLogger(serverLogs), } return mergeConfig(options, config || {}) @@ -238,12 +242,16 @@ export async function startDefaultServe(): Promise { if (!isBuild) { process.env.VITE_INLINE = 'inline-serve' const config = await loadConfig({ command: 'serve', mode: 'development' }) - // test full bundle mode - // viteServer = server = await createServer(config) - // const builder = await createBuilder(config, null, 'serve') - // await builder.buildApp(server) - // await server.listen() - viteServer = server = await (await createServer(config)).listen() + + if (process.env.VITE_TEST_FULL_BUNDLE_MODE) { + const builder = await createBuilder(config, null, 'serve') + viteServer = server = await createServerWithResolvedConfig(builder.config) + await server.listen() + await builder.buildApp(server) + } else { + viteServer = server = await (await createServer(config)).listen() + } + viteTestUrl = stripTrailingSlashIfNeeded( server.resolvedUrls.local[0], server.config.base, diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index f9e21a1c2fee67..16d8385ebeb0ad 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -12,7 +12,13 @@ export default defineConfig({ }, }, test: { - include: ['./playground/**/*.spec.[tj]s'], + include: process.env.VITE_TEST_FULL_BUNDLE_MODE + ? [ + './playground/define/**/*.spec.[tj]s', + // './playground/hmr-root/**/*.spec.[tj]s', + // './playground/hmr/**/*.spec.[tj]s' + ] + : ['./playground/**/*.spec.[tj]s'], exclude: [ './playground/legacy/**/*.spec.[tj]s', // system format ...(isBuild From 60b406ebfea7ee9e4f1977a3ed11b309203d76e5 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 23 Apr 2025 16:59:15 +0800 Subject: [PATCH 44/76] fix: aovid replace import.met.hot to undefined --- packages/vite/src/node/plugins/define.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index 121d0ea947cbf5..64b99a0c6fc99b 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -12,7 +12,8 @@ const importMetaEnvMarker = '__vite_import_meta_env__' const importMetaEnvKeyReCache = new Map() export function definePlugin(config: ResolvedConfig): Plugin { - const isBuild = config.command === 'build' + const isBuild = + config.command === 'build' || !!config.experimental.fullBundleMode const isBuildLib = isBuild && config.build.lib // ignore replace process.env in lib build @@ -33,8 +34,11 @@ export function definePlugin(config: ResolvedConfig): Plugin { const importMetaKeys: Record = {} const importMetaEnvKeys: Record = {} const importMetaFallbackKeys: Record = {} - if (isBuild && !config.experimental.fullBundleMode) { - importMetaKeys['import.meta.hot'] = `undefined` + if (isBuild) { + // import.meta.hot need to avoid replace undefined at full bundle mode + if (!config.experimental.fullBundleMode) { + importMetaKeys['import.meta.hot'] = `undefined` + } for (const key in config.env) { const val = JSON.stringify(config.env[key]) importMetaKeys[`import.meta.env.${key}`] = val @@ -54,11 +58,7 @@ export function definePlugin(config: ResolvedConfig): Plugin { userDefine[key] = handleDefineValue(environment.config.define[key]) // make sure `import.meta.env` object has user define properties - if ( - isBuild && - !config.experimental.fullBundleMode && - key.startsWith('import.meta.env.') - ) { + if (isBuild && key.startsWith('import.meta.env.')) { userDefineEnv[key.slice(16)] = environment.config.define[key] } } @@ -132,11 +132,7 @@ export function definePlugin(config: ResolvedConfig): Plugin { transform: { async handler(code, id) { - if ( - this.environment.config.consumer === 'client' && - !isBuild && - !config.experimental.fullBundleMode - ) { + if (this.environment.config.consumer === 'client' && !isBuild) { // for dev we inject actual global defines in the vite client to // avoid the transform cost. see the `clientInjection` and // `importAnalysis` plugin. From c509336f6527cb3822280baa50db8238f41db506 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 24 Apr 2025 17:11:38 +0800 Subject: [PATCH 45/76] fix: enable createEsmInitializer createCjsInitializer --- packages/vite/src/client/hmrModuleRunner.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 497a1100a9fa99..500a9be140e3bb 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -74,9 +74,13 @@ if (__FULL_BUNDLE_MODE__) { } // __esmMin - // createEsmInitializer = (fn, res) => () => (fn && (res = fn(fn = 0)), res) + // @ts-expect-error need to add typing + createEsmInitializer = (fn, res) => () => (fn && (res = fn((fn = 0))), res) // __commonJSMin - // createCjsInitializer = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports) + // @ts-expect-error need to add typing + createCjsInitializer = (cb, mod) => () => ( + mod || cb((mod = { exports: {} }).exports, mod), mod.exports + ) } // @ts-expect-error __rolldown_runtime__ From b2493f22c006ceb6e2a3b5bc64458aa0d1870311 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 24 Apr 2025 17:12:02 +0800 Subject: [PATCH 46/76] chore: update hmr log --- packages/vite/src/node/build.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index cd80f536de507e..d986666e7b7662 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -32,6 +32,7 @@ import type { RollupCommonJSOptions } from 'dep-types/commonjs' import type { RollupDynamicImportVarsOptions } from 'dep-types/dynamicImportVars' import type { EsbuildTarget } from 'types/internal/esbuildOptions' import type { ChunkMetadata } from 'types/metadata' +import type { Update } from 'types/hmrPayload' import { withTrailingSlash } from '../shared/utils' import { DEFAULT_ASSETS_INLINE_LIMIT, @@ -944,6 +945,7 @@ async function buildEnvironment( if (server) { server.watcher.on('change', async (file) => { + logger.info(`${colors.green(`${path.relative(root, file)} changed.`)}`) const startTime = Date.now() const hmrOutput = (await bundle!.generateHmrPatch([file]))! // @ts-expect-error Need to upgrade rolldown @@ -961,23 +963,29 @@ async function buildEnvironment( if (hmrOutput.patch) { const url = `${Date.now()}.js` server.memoryFiles[url] = hmrOutput.patch + const updates = hmrOutput.hmrBoundaries.map((boundary) => { + return { + type: 'js-update', + url, + path: boundary.boundary, + acceptedPath: boundary.acceptedVia, + timestamp: 0, + } + }) as Update[] server.ws.send({ type: 'update', - updates: hmrOutput.hmrBoundaries.map((boundary) => { - return { - type: 'js-update', - url, - path: boundary.boundary, - acceptedPath: boundary.acceptedVia, - timestamp: 0, - } - }), + updates, }) logger.info( - `${colors.green(`✓ Found ${path.relative(root, file)} changed, rebuilt in ${displayTime(Date.now() - startTime)}`)}`, + colors.green(`hmr update `) + + colors.dim([...new Set(updates.map((u) => u.path))].join(', ')), + { clear: true, timestamp: true }, ) await build() + logger.info( + `${colors.green(`✓ rebuilt in ${displayTime(Date.now() - startTime)}`)}`, + ) } }) } From 10b6a47f901c115263551466b4bee812e2d84a4b Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 24 Apr 2025 17:12:54 +0800 Subject: [PATCH 47/76] chore: update hmr test --- playground/hmr/__tests__/hmr.spec.ts | 2 ++ vitest.config.e2e.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 19d4257fa17b26..438ab7a2f38b31 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -30,6 +30,7 @@ if (!isBuild) { browserLogs.length = 0 }) + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('self accept', async () => { const el = await page.$('.app') await untilBrowserLogAfter( @@ -1099,3 +1100,4 @@ if (!isBuild) { }, [/connected/, 'a.js']) }) } +} \ No newline at end of file diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index 16d8385ebeb0ad..784921a55ee061 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -15,8 +15,8 @@ export default defineConfig({ include: process.env.VITE_TEST_FULL_BUNDLE_MODE ? [ './playground/define/**/*.spec.[tj]s', - // './playground/hmr-root/**/*.spec.[tj]s', - // './playground/hmr/**/*.spec.[tj]s' + './playground/hmr-root/**/*.spec.[tj]s', + './playground/hmr/**/*.spec.[tj]s', ] : ['./playground/**/*.spec.[tj]s'], exclude: [ From 532b4f0fd8a5c3c205ac852a483a2ce0567fc12a Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 24 Apr 2025 17:23:28 +0800 Subject: [PATCH 48/76] fix: pass hmr self accept test --- packages/vite/src/client/client.ts | 1 + packages/vite/src/node/build.ts | 1 + packages/vite/src/shared/hmr.ts | 9 +- playground/hmr/__tests__/hmr.spec.ts | 1700 +++++++++++++------------- 4 files changed, 868 insertions(+), 843 deletions(-) diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index a56908b33903ae..a16facfd399f09 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -183,6 +183,7 @@ const hmrClient = new HMRClient( } return await importPromise }, + __FULL_BUNDLE_MODE__, ) transport.connect!(createHMRHandler(handleMessage)) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index d986666e7b7662..25ac410be4605e 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -655,6 +655,7 @@ async function buildEnvironment( // ? 'strict' // : false, // cache: options.watch ? undefined : false, + cwd: root, ...options.rollupOptions, output: options.rollupOptions.output, input, diff --git a/packages/vite/src/shared/hmr.ts b/packages/vite/src/shared/hmr.ts index 85b0916c5f4f5f..81e9efe39b9304 100644 --- a/packages/vite/src/shared/hmr.ts +++ b/packages/vite/src/shared/hmr.ts @@ -181,6 +181,7 @@ export class HMRClient { private transport: NormalizedModuleRunnerTransport, // This allows implementing reloading via different methods depending on the environment private importUpdatedModule: (update: Update) => Promise, + private fullBundleMode: boolean, ) {} public async notifyListeners( @@ -296,7 +297,13 @@ export class HMRClient { ), ) } - const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}` + const loggedPath = this.fullBundleMode + ? isSelfUpdate + ? `/${path}` + : `/${acceptedPath} via /${path}` + : isSelfUpdate + ? path + : `${acceptedPath} via ${path}` this.logger.debug(`hot updated: ${loggedPath}`) } finally { this.currentFirstInvalidatedBy = undefined diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 438ab7a2f38b31..c2441decafad3c 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -30,7 +30,6 @@ if (!isBuild) { browserLogs.length = 0 }) - if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('self accept', async () => { const el = await page.$('.app') await untilBrowserLogAfter( @@ -68,121 +67,94 @@ if (!isBuild) { await untilUpdated(() => el.textContent(), '3') }) - test('accept dep', async () => { - const el = await page.$('.dep') - await untilBrowserLogAfter( - () => - editFile('hmrDep.js', (code) => - code.replace('const foo = 1', 'const foo = 2'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 1', - '(dep) foo from dispose: 1', - '(single dep) foo is now: 2', - '(single dep) nested foo is now: 1', - '(multi deps) foo is now: 2', - '(multi deps) nested foo is now: 1', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '2') - - await untilBrowserLogAfter( - () => - editFile('hmrDep.js', (code) => - code.replace('const foo = 2', 'const foo = 3'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 2', - '(dep) foo from dispose: 2', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 1', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 1', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '3') - }) - - test('nested dep propagation', async () => { - const el = await page.$('.nested') - await untilBrowserLogAfter( - () => - editFile('hmrNestedDep.js', (code) => - code.replace('const foo = 1', 'const foo = 2'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 3', - '(dep) foo from dispose: 3', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 2', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 2', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '2') + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { + test('accept dep', async () => { + const el = await page.$('.dep') + await untilBrowserLogAfter( + () => + editFile('hmrDep.js', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 1', + '(dep) foo from dispose: 1', + '(single dep) foo is now: 2', + '(single dep) nested foo is now: 1', + '(multi deps) foo is now: 2', + '(multi deps) nested foo is now: 1', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '2') - await untilBrowserLogAfter( - () => - editFile('hmrNestedDep.js', (code) => - code.replace('const foo = 2', 'const foo = 3'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 3', - '(dep) foo from dispose: 3', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 3', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 3', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '3') - }) + await untilBrowserLogAfter( + () => + editFile('hmrDep.js', (code) => + code.replace('const foo = 2', 'const foo = 3'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 2', + '(dep) foo from dispose: 2', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 1', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 1', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '3') + }) - test('invalidate', async () => { - const el = await page.$('.invalidation-parent') - await untilBrowserLogAfter( - () => - editFile('invalidation/child.js', (code) => - code.replace('child', 'child updated'), - ), - [ - '>>> vite:beforeUpdate -- update', - '>>> vite:invalidate -- /invalidation/child.js', - '[vite] invalidate /invalidation/child.js', - '[vite] hot updated: /invalidation/child.js', - '>>> vite:afterUpdate -- update', - '>>> vite:beforeUpdate -- update', - '(invalidation) parent is executing', - '[vite] hot updated: /invalidation/parent.js', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), 'child updated') - }) + test('nested dep propagation', async () => { + const el = await page.$('.nested') + await untilBrowserLogAfter( + () => + editFile('hmrNestedDep.js', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 3', + '(dep) foo from dispose: 3', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 2', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 2', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '2') - test('invalidate works with multiple tabs', async () => { - let page2: Page - try { - page2 = await browser.newPage() - await page2.goto(viteTestUrl) + await untilBrowserLogAfter( + () => + editFile('hmrNestedDep.js', (code) => + code.replace('const foo = 2', 'const foo = 3'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 3', + '(dep) foo from dispose: 3', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 3', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 3', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '3') + }) + test('invalidate', async () => { const el = await page.$('.invalidation-parent') await untilBrowserLogAfter( () => @@ -195,7 +167,6 @@ if (!isBuild) { '[vite] invalidate /invalidation/child.js', '[vite] hot updated: /invalidation/child.js', '>>> vite:afterUpdate -- update', - // if invalidate dedupe doesn't work correctly, this beforeUpdate will be called twice '>>> vite:beforeUpdate -- update', '(invalidation) parent is executing', '[vite] hot updated: /invalidation/parent.js', @@ -204,900 +175,945 @@ if (!isBuild) { true, ) await untilUpdated(() => el.textContent(), 'child updated') - } finally { - await page2.close() - } - }) - - test('invalidate on root triggers page reload', async () => { - editFile('invalidation/root.js', (code) => code.replace('Init', 'Updated')) - await page.waitForEvent('load') - await untilUpdated( - async () => (await page.$('.invalidation-root')).textContent(), - 'Updated', - ) - }) - - test('soft invalidate', async () => { - const el = await page.$('.soft-invalidation') - expect(await el.textContent()).toBe( - 'soft-invalidation/index.js is transformed 1 times. child is bar', - ) - editFile('soft-invalidation/child.js', (code) => - code.replace('bar', 'updated'), - ) - await untilUpdated( - () => el.textContent(), - 'soft-invalidation/index.js is transformed 1 times. child is updated', - ) - - editFile('soft-invalidation/index.js', (code) => - code.replace('child is', 'child is now'), - ) - editFile('soft-invalidation/child.js', (code) => - code.replace('updated', 'updated?'), - ) - await untilUpdated( - () => el.textContent(), - 'soft-invalidation/index.js is transformed 2 times. child is now updated?', - ) - }) - - test('invalidate in circular dep should not trigger infinite HMR', async () => { - const el = await page.$('.invalidation-circular-deps') - await untilUpdated(() => el.textContent(), 'child') - editFile( - 'invalidation-circular-deps/circular-invalidate/child.js', - (code) => code.replace('child', 'child updated'), - ) - await page.waitForEvent('load') - await untilUpdated( - () => page.textContent('.invalidation-circular-deps'), - 'child updated', - ) - }) + }) - test('invalidate in circular dep should be hot updated if possible', async () => { - const el = await page.$('.invalidation-circular-deps-handled') - await untilUpdated(() => el.textContent(), 'child') - editFile( - 'invalidation-circular-deps/invalidate-handled-in-circle/child.js', - (code) => code.replace('child', 'child updated'), - ) - await untilUpdated(() => el.textContent(), 'child updated') - }) + test('invalidate works with multiple tabs', async () => { + let page2: Page + try { + page2 = await browser.newPage() + await page2.goto(viteTestUrl) - test('plugin hmr handler + custom event', async () => { - const el = await page.$('.custom') - editFile('customFile.js', (code) => code.replace('custom', 'edited')) - await untilUpdated(() => el.textContent(), 'edited') - }) + const el = await page.$('.invalidation-parent') + await untilBrowserLogAfter( + () => + editFile('invalidation/child.js', (code) => + code.replace('child', 'child updated'), + ), + [ + '>>> vite:beforeUpdate -- update', + '>>> vite:invalidate -- /invalidation/child.js', + '[vite] invalidate /invalidation/child.js', + '[vite] hot updated: /invalidation/child.js', + '>>> vite:afterUpdate -- update', + // if invalidate dedupe doesn't work correctly, this beforeUpdate will be called twice + '>>> vite:beforeUpdate -- update', + '(invalidation) parent is executing', + '[vite] hot updated: /invalidation/parent.js', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), 'child updated') + } finally { + await page2.close() + } + }) - test('plugin hmr remove custom events', async () => { - const el = await page.$('.toRemove') - editFile('customFile.js', (code) => code.replace('custom', 'edited')) - await untilUpdated(() => el.textContent(), 'edited') - editFile('customFile.js', (code) => code.replace('edited', 'custom')) - await untilUpdated(() => el.textContent(), 'edited') - }) + test('invalidate on root triggers page reload', async () => { + editFile('invalidation/root.js', (code) => + code.replace('Init', 'Updated'), + ) + await page.waitForEvent('load') + await untilUpdated( + async () => (await page.$('.invalidation-root')).textContent(), + 'Updated', + ) + }) - test('plugin client-server communication', async () => { - const el = await page.$('.custom-communication') - await untilUpdated(() => el.textContent(), '3') - }) + test('soft invalidate', async () => { + const el = await page.$('.soft-invalidation') + expect(await el.textContent()).toBe( + 'soft-invalidation/index.js is transformed 1 times. child is bar', + ) + editFile('soft-invalidation/child.js', (code) => + code.replace('bar', 'updated'), + ) + await untilUpdated( + () => el.textContent(), + 'soft-invalidation/index.js is transformed 1 times. child is updated', + ) - test('full-reload encodeURI path', async () => { - await page.goto( - viteTestUrl + '/unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', - ) - const el = await page.$('#app') - expect(await el.textContent()).toBe('title') - editFile('unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', (code) => - code.replace('title', 'title2'), - ) - await page.waitForEvent('load') - await untilUpdated( - async () => (await page.$('#app')).textContent(), - 'title2', - ) - }) + editFile('soft-invalidation/index.js', (code) => + code.replace('child is', 'child is now'), + ) + editFile('soft-invalidation/child.js', (code) => + code.replace('updated', 'updated?'), + ) + await untilUpdated( + () => el.textContent(), + 'soft-invalidation/index.js is transformed 2 times. child is now updated?', + ) + }) - test('CSS update preserves query params', async () => { - await page.goto(viteTestUrl) + test('invalidate in circular dep should not trigger infinite HMR', async () => { + const el = await page.$('.invalidation-circular-deps') + await untilUpdated(() => el.textContent(), 'child') + editFile( + 'invalidation-circular-deps/circular-invalidate/child.js', + (code) => code.replace('child', 'child updated'), + ) + await page.waitForEvent('load') + await untilUpdated( + () => page.textContent('.invalidation-circular-deps'), + 'child updated', + ) + }) - editFile('global.css', (code) => code.replace('white', 'tomato')) + test('invalidate in circular dep should be hot updated if possible', async () => { + const el = await page.$('.invalidation-circular-deps-handled') + await untilUpdated(() => el.textContent(), 'child') + editFile( + 'invalidation-circular-deps/invalidate-handled-in-circle/child.js', + (code) => code.replace('child', 'child updated'), + ) + await untilUpdated(() => el.textContent(), 'child updated') + }) - const elprev = await page.$('.css-prev') - const elpost = await page.$('.css-post') - await untilUpdated(() => elprev.textContent(), 'param=required') - await untilUpdated(() => elpost.textContent(), 'param=required') - const textprev = await elprev.textContent() - const textpost = await elpost.textContent() - expect(textprev).not.toBe(textpost) - expect(textprev).not.toMatch('direct') - expect(textpost).not.toMatch('direct') - }) + test('plugin hmr handler + custom event', async () => { + const el = await page.$('.custom') + editFile('customFile.js', (code) => code.replace('custom', 'edited')) + await untilUpdated(() => el.textContent(), 'edited') + }) - test('it swaps out link tags', async () => { - await page.goto(viteTestUrl) + test('plugin hmr remove custom events', async () => { + const el = await page.$('.toRemove') + editFile('customFile.js', (code) => code.replace('custom', 'edited')) + await untilUpdated(() => el.textContent(), 'edited') + editFile('customFile.js', (code) => code.replace('edited', 'custom')) + await untilUpdated(() => el.textContent(), 'edited') + }) - editFile('global.css', (code) => code.replace('white', 'tomato')) + test('plugin client-server communication', async () => { + const el = await page.$('.custom-communication') + await untilUpdated(() => el.textContent(), '3') + }) - let el = await page.$('.link-tag-added') - await untilUpdated(() => el.textContent(), 'yes') + test('full-reload encodeURI path', async () => { + await page.goto( + viteTestUrl + '/unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', + ) + const el = await page.$('#app') + expect(await el.textContent()).toBe('title') + editFile('unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', (code) => + code.replace('title', 'title2'), + ) + await page.waitForEvent('load') + await untilUpdated( + async () => (await page.$('#app')).textContent(), + 'title2', + ) + }) - el = await page.$('.link-tag-removed') - await untilUpdated(() => el.textContent(), 'yes') + test('CSS update preserves query params', async () => { + await page.goto(viteTestUrl) - expect((await page.$$('link')).length).toBe(1) - }) + editFile('global.css', (code) => code.replace('white', 'tomato')) - test('not loaded dynamic import', async () => { - await page.goto(viteTestUrl + '/counter/index.html', { waitUntil: 'load' }) - - let btn = await page.$('button') - expect(await btn.textContent()).toBe('Counter 0') - await btn.click() - expect(await btn.textContent()).toBe('Counter 1') - - // Modifying `index.ts` triggers a page reload, as expected - const indexTsLoadPromise = page.waitForEvent('load') - editFile('counter/index.ts', (code) => code) - await indexTsLoadPromise - btn = await page.$('button') - expect(await btn.textContent()).toBe('Counter 0') - - await btn.click() - expect(await btn.textContent()).toBe('Counter 1') - - // #7561 - // `dep.ts` defines `import.module.hot.accept` and has not been loaded. - // Therefore, modifying it has no effect (doesn't trigger a page reload). - // (Note that, a dynamic import that is never loaded and that does not - // define `accept.module.hot.accept` may wrongfully trigger a full page - // reload, see discussion at #7561.) - const depTsLoadPromise = page.waitForEvent('load', { timeout: 1000 }) - editFile('counter/dep.ts', (code) => code) - await expect(depTsLoadPromise).rejects.toThrow( - /page\.waitForEvent: Timeout \d+ms exceeded while waiting for event "load"/, - ) + const elprev = await page.$('.css-prev') + const elpost = await page.$('.css-post') + await untilUpdated(() => elprev.textContent(), 'param=required') + await untilUpdated(() => elpost.textContent(), 'param=required') + const textprev = await elprev.textContent() + const textpost = await elpost.textContent() + expect(textprev).not.toBe(textpost) + expect(textprev).not.toMatch('direct') + expect(textpost).not.toMatch('direct') + }) - btn = await page.$('button') - expect(await btn.textContent()).toBe('Counter 1') - }) + test('it swaps out link tags', async () => { + await page.goto(viteTestUrl) - // #2255 - test('importing reloaded', async () => { - await page.goto(viteTestUrl) - const outputEle = await page.$('.importing-reloaded') - const getOutput = () => { - return outputEle.innerHTML() - } + editFile('global.css', (code) => code.replace('white', 'tomato')) - await untilUpdated(getOutput, ['a.js: a0', 'b.js: b0,a0'].join('
')) + let el = await page.$('.link-tag-added') + await untilUpdated(() => el.textContent(), 'yes') - editFile('importing-updated/a.js', (code) => code.replace("'a0'", "'a1'")) - await untilUpdated( - getOutput, - ['a.js: a0', 'b.js: b0,a0', 'a.js: a1'].join('
'), - ) + el = await page.$('.link-tag-removed') + await untilUpdated(() => el.textContent(), 'yes') - editFile('importing-updated/b.js', (code) => - code.replace('`b0,${a}`', '`b1,${a}`'), - ) - // note that "a.js: a1" should not happen twice after "b.js: b0,a0'" - await untilUpdated( - getOutput, - ['a.js: a0', 'b.js: b0,a0', 'a.js: a1', 'b.js: b1,a1'].join('
'), - ) - }) + expect((await page.$$('link')).length).toBe(1) + }) - describe('acceptExports', () => { - const HOT_UPDATED = /hot updated/ - const CONNECTED = /connected/ + test('not loaded dynamic import', async () => { + await page.goto(viteTestUrl + '/counter/index.html', { + waitUntil: 'load', + }) - const baseDir = 'accept-exports' + let btn = await page.$('button') + expect(await btn.textContent()).toBe('Counter 0') + await btn.click() + expect(await btn.textContent()).toBe('Counter 1') + + // Modifying `index.ts` triggers a page reload, as expected + const indexTsLoadPromise = page.waitForEvent('load') + editFile('counter/index.ts', (code) => code) + await indexTsLoadPromise + btn = await page.$('button') + expect(await btn.textContent()).toBe('Counter 0') + + await btn.click() + expect(await btn.textContent()).toBe('Counter 1') + + // #7561 + // `dep.ts` defines `import.module.hot.accept` and has not been loaded. + // Therefore, modifying it has no effect (doesn't trigger a page reload). + // (Note that, a dynamic import that is never loaded and that does not + // define `accept.module.hot.accept` may wrongfully trigger a full page + // reload, see discussion at #7561.) + const depTsLoadPromise = page.waitForEvent('load', { timeout: 1000 }) + editFile('counter/dep.ts', (code) => code) + await expect(depTsLoadPromise).rejects.toThrow( + /page\.waitForEvent: Timeout \d+ms exceeded while waiting for event "load"/, + ) - describe('when all used exports are accepted', () => { - const testDir = baseDir + '/main-accepted' + btn = await page.$('button') + expect(await btn.textContent()).toBe('Counter 1') + }) - const fileName = 'target.ts' - const file = `${testDir}/${fileName}` - const url = '/' + file + // #2255 + test('importing reloaded', async () => { + await page.goto(viteTestUrl) + const outputEle = await page.$('.importing-reloaded') + const getOutput = () => { + return outputEle.innerHTML() + } - let dep = 'dep0' + await untilUpdated(getOutput, ['a.js: a0', 'b.js: b0,a0'].join('
')) - beforeAll(async () => { - await untilBrowserLogAfter( - () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, />>>>>>/], - (logs) => { - expect(logs).toContain(`<<<<<< A0 B0 D0 ; ${dep}`) - expect(logs).toContain('>>>>>> A0 D0') - }, - ) - }) + editFile('importing-updated/a.js', (code) => code.replace("'a0'", "'a1'")) + await untilUpdated( + getOutput, + ['a.js: a0', 'b.js: b0,a0', 'a.js: a1'].join('
'), + ) - it('the callback is called with the new version the module', async () => { - const callbackFile = `${testDir}/callback.ts` - const callbackUrl = '/' + callbackFile + editFile('importing-updated/b.js', (code) => + code.replace('`b0,${a}`', '`b1,${a}`'), + ) + // note that "a.js: a1" should not happen twice after "b.js: b0,a0'" + await untilUpdated( + getOutput, + ['a.js: a0', 'b.js: b0,a0', 'a.js: a1', 'b.js: b1,a1'].join('
'), + ) + }) - await untilBrowserLogAfter( - () => { - editFile(callbackFile, (code) => - code - .replace("x = 'X'", "x = 'Y'") - .replace('reloaded >>>', 'reloaded (2) >>>'), - ) - }, - HOT_UPDATED, - (logs) => { - expect(logs).toEqual([ - 'reloaded >>> Y', - `[vite] hot updated: ${callbackUrl}`, - ]) - }, - ) + describe('acceptExports', () => { + const HOT_UPDATED = /hot updated/ + const CONNECTED = /connected/ - await untilBrowserLogAfter( - () => { - editFile(callbackFile, (code) => code.replace("x = 'Y'", "x = 'Z'")) - }, - HOT_UPDATED, - (logs) => { - expect(logs).toEqual([ - 'reloaded (2) >>> Z', - `[vite] hot updated: ${callbackUrl}`, - ]) - }, - ) - }) + const baseDir = 'accept-exports' - it('stops HMR bubble on dependency change', async () => { - const depFileName = 'dep.ts' - const depFile = `${testDir}/${depFileName}` + describe('when all used exports are accepted', () => { + const testDir = baseDir + '/main-accepted' - await untilBrowserLogAfter( - () => { - editFile(depFile, (code) => code.replace('dep0', (dep = 'dep1'))) - }, - HOT_UPDATED, - (logs) => { - expect(logs).toEqual([ - `<<<<<< A0 B0 D0 ; ${dep}`, - `[vite] hot updated: ${url}`, - ]) - }, - ) - }) + const fileName = 'target.ts' + const file = `${testDir}/${fileName}` + const url = '/' + file - it('accepts itself and refreshes on change', async () => { - await untilBrowserLogAfter( - () => { - editFile(file, (code) => code.replace(/(\b[A-Z])0/g, '$11')) - }, - HOT_UPDATED, - (logs) => { - expect(logs).toEqual([ - `<<<<<< A1 B1 D1 ; ${dep}`, - `[vite] hot updated: ${url}`, - ]) - }, - ) - }) + let dep = 'dep0' - it('accepts itself and refreshes on 2nd change', async () => { - await untilBrowserLogAfter( - () => { - editFile(file, (code) => - code - .replace(/(\b[A-Z])1/g, '$12') - .replace( - "acceptExports(['a', 'default']", - "acceptExports(['b', 'default']", - ), - ) - }, - HOT_UPDATED, - (logs) => { - expect(logs).toEqual([ - `<<<<<< A2 B2 D2 ; ${dep}`, - `[vite] hot updated: ${url}`, - ]) - }, - ) - }) + beforeAll(async () => { + await untilBrowserLogAfter( + () => page.goto(`${viteTestUrl}/${testDir}/`), + [CONNECTED, />>>>>>/], + (logs) => { + expect(logs).toContain(`<<<<<< A0 B0 D0 ; ${dep}`) + expect(logs).toContain('>>>>>> A0 D0') + }, + ) + }) - it('does not accept itself anymore after acceptedExports change', async () => { - await untilBrowserLogAfter( - async () => { - editFile(file, (code) => code.replace(/(\b[A-Z])2/g, '$13')) - await page.waitForEvent('load') - }, - [CONNECTED, />>>>>>/], - (logs) => { - expect(logs).toContain(`<<<<<< A3 B3 D3 ; ${dep}`) - expect(logs).toContain('>>>>>> A3 D3') - }, - ) - }) - }) + it('the callback is called with the new version the module', async () => { + const callbackFile = `${testDir}/callback.ts` + const callbackUrl = '/' + callbackFile - describe('when some used exports are not accepted', () => { - const testDir = baseDir + '/main-non-accepted' + await untilBrowserLogAfter( + () => { + editFile(callbackFile, (code) => + code + .replace("x = 'X'", "x = 'Y'") + .replace('reloaded >>>', 'reloaded (2) >>>'), + ) + }, + HOT_UPDATED, + (logs) => { + expect(logs).toEqual([ + 'reloaded >>> Y', + `[vite] hot updated: ${callbackUrl}`, + ]) + }, + ) - const namedFileName = 'named.ts' - const namedFile = `${testDir}/${namedFileName}` - const defaultFileName = 'default.ts' - const defaultFile = `${testDir}/${defaultFileName}` - const depFileName = 'dep.ts' - const depFile = `${testDir}/${depFileName}` + await untilBrowserLogAfter( + () => { + editFile(callbackFile, (code) => + code.replace("x = 'Y'", "x = 'Z'"), + ) + }, + HOT_UPDATED, + (logs) => { + expect(logs).toEqual([ + 'reloaded (2) >>> Z', + `[vite] hot updated: ${callbackUrl}`, + ]) + }, + ) + }) - const a = 'A0' - let dep = 'dep0' + it('stops HMR bubble on dependency change', async () => { + const depFileName = 'dep.ts' + const depFile = `${testDir}/${depFileName}` - beforeAll(async () => { - await untilBrowserLogAfter( - () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, />>>>>>/], - (logs) => { - expect(logs).toContain(`<<< named: ${a} ; ${dep}`) - expect(logs).toContain(`<<< default: def0`) - expect(logs).toContain(`>>>>>> ${a} def0`) - }, - ) - }) + await untilBrowserLogAfter( + () => { + editFile(depFile, (code) => code.replace('dep0', (dep = 'dep1'))) + }, + HOT_UPDATED, + (logs) => { + expect(logs).toEqual([ + `<<<<<< A0 B0 D0 ; ${dep}`, + `[vite] hot updated: ${url}`, + ]) + }, + ) + }) - it('does not stop the HMR bubble on change to dep', async () => { - await untilBrowserLogAfter( - async () => { - editFile(depFile, (code) => code.replace('dep0', (dep = 'dep1'))) - await page.waitForEvent('load') - }, - [CONNECTED, />>>>>>/], - (logs) => { - expect(logs).toContain(`<<< named: ${a} ; ${dep}`) - }, - ) - }) + it('accepts itself and refreshes on change', async () => { + await untilBrowserLogAfter( + () => { + editFile(file, (code) => code.replace(/(\b[A-Z])0/g, '$11')) + }, + HOT_UPDATED, + (logs) => { + expect(logs).toEqual([ + `<<<<<< A1 B1 D1 ; ${dep}`, + `[vite] hot updated: ${url}`, + ]) + }, + ) + }) - describe('does not stop the HMR bubble on change to self', () => { - it('with named exports', async () => { + it('accepts itself and refreshes on 2nd change', async () => { await untilBrowserLogAfter( - async () => { - editFile(namedFile, (code) => code.replace(a, 'A1')) - await page.waitForEvent('load') + () => { + editFile(file, (code) => + code + .replace(/(\b[A-Z])1/g, '$12') + .replace( + "acceptExports(['a', 'default']", + "acceptExports(['b', 'default']", + ), + ) }, - [CONNECTED, />>>>>>/], + HOT_UPDATED, (logs) => { - expect(logs).toContain(`<<< named: A1 ; ${dep}`) + expect(logs).toEqual([ + `<<<<<< A2 B2 D2 ; ${dep}`, + `[vite] hot updated: ${url}`, + ]) }, ) }) - it('with default export', async () => { + it('does not accept itself anymore after acceptedExports change', async () => { await untilBrowserLogAfter( async () => { - editFile(defaultFile, (code) => code.replace('def0', 'def1')) + editFile(file, (code) => code.replace(/(\b[A-Z])2/g, '$13')) await page.waitForEvent('load') }, [CONNECTED, />>>>>>/], (logs) => { - expect(logs).toContain(`<<< default: def1`) + expect(logs).toContain(`<<<<<< A3 B3 D3 ; ${dep}`) + expect(logs).toContain('>>>>>> A3 D3') }, ) }) }) - }) - test('accepts itself when imported for side effects only (no bindings imported)', async () => { - const testDir = baseDir + '/side-effects' - const file = 'side-effects.ts' - - await untilBrowserLogAfter( - () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, />>>/], - (logs) => { - expect(logs).toContain('>>> side FX') - }, - ) + describe('when some used exports are not accepted', () => { + const testDir = baseDir + '/main-non-accepted' - await untilBrowserLogAfter( - () => { - editFile(`${testDir}/${file}`, (code) => - code.replace('>>> side FX', '>>> side FX !!'), - ) - }, - HOT_UPDATED, - (logs) => { - expect(logs).toEqual([ - '>>> side FX !!', - `[vite] hot updated: /${testDir}/${file}`, - ]) - }, - ) - }) + const namedFileName = 'named.ts' + const namedFile = `${testDir}/${namedFileName}` + const defaultFileName = 'default.ts' + const defaultFile = `${testDir}/${defaultFileName}` + const depFileName = 'dep.ts' + const depFile = `${testDir}/${depFileName}` - describe('acceptExports([])', () => { - const testDir = baseDir + '/unused-exports' + const a = 'A0' + let dep = 'dep0' - test('accepts itself if no exports are imported', async () => { - const fileName = 'unused.ts' - const file = `${testDir}/${fileName}` - const url = '/' + file + beforeAll(async () => { + await untilBrowserLogAfter( + () => page.goto(`${viteTestUrl}/${testDir}/`), + [CONNECTED, />>>>>>/], + (logs) => { + expect(logs).toContain(`<<< named: ${a} ; ${dep}`) + expect(logs).toContain(`<<< default: def0`) + expect(logs).toContain(`>>>>>> ${a} def0`) + }, + ) + }) - await untilBrowserLogAfter( - () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, '-- unused --'], - (logs) => { - expect(logs).toContain('-- unused --') - }, - ) + it('does not stop the HMR bubble on change to dep', async () => { + await untilBrowserLogAfter( + async () => { + editFile(depFile, (code) => code.replace('dep0', (dep = 'dep1'))) + await page.waitForEvent('load') + }, + [CONNECTED, />>>>>>/], + (logs) => { + expect(logs).toContain(`<<< named: ${a} ; ${dep}`) + }, + ) + }) - await untilBrowserLogAfter( - () => { - editFile(file, (code) => - code.replace('-- unused --', '-> unused <-'), + describe('does not stop the HMR bubble on change to self', () => { + it('with named exports', async () => { + await untilBrowserLogAfter( + async () => { + editFile(namedFile, (code) => code.replace(a, 'A1')) + await page.waitForEvent('load') + }, + [CONNECTED, />>>>>>/], + (logs) => { + expect(logs).toContain(`<<< named: A1 ; ${dep}`) + }, ) - }, - HOT_UPDATED, - (logs) => { - expect(logs).toEqual(['-> unused <-', `[vite] hot updated: ${url}`]) - }, - ) + }) + + it('with default export', async () => { + await untilBrowserLogAfter( + async () => { + editFile(defaultFile, (code) => code.replace('def0', 'def1')) + await page.waitForEvent('load') + }, + [CONNECTED, />>>>>>/], + (logs) => { + expect(logs).toContain(`<<< default: def1`) + }, + ) + }) + }) }) - test("doesn't accept itself if any of its exports is imported", async () => { - const fileName = 'used.ts' - const file = `${testDir}/${fileName}` + test('accepts itself when imported for side effects only (no bindings imported)', async () => { + const testDir = baseDir + '/side-effects' + const file = 'side-effects.ts' await untilBrowserLogAfter( () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, '-- used --'], + [CONNECTED, />>>/], (logs) => { - expect(logs).toContain('-- used --') - expect(logs).toContain('used:foo0') + expect(logs).toContain('>>> side FX') }, ) await untilBrowserLogAfter( - async () => { - editFile(file, (code) => - code.replace('foo0', 'foo1').replace('-- used --', '-> used <-'), + () => { + editFile(`${testDir}/${file}`, (code) => + code.replace('>>> side FX', '>>> side FX !!'), ) - await page.waitForEvent('load') }, - [CONNECTED, /used:foo/], + HOT_UPDATED, (logs) => { - expect(logs).toContain('-> used <-') - expect(logs).toContain('used:foo1') + expect(logs).toEqual([ + '>>> side FX !!', + `[vite] hot updated: /${testDir}/${file}`, + ]) }, ) }) - }) - describe('indiscriminate imports: import *', () => { - const testStarExports = (testDirName: string) => { - const testDir = `${baseDir}/${testDirName}` + describe('acceptExports([])', () => { + const testDir = baseDir + '/unused-exports' - it('accepts itself if all its exports are accepted', async () => { - const fileName = 'deps-all-accepted.ts' + test('accepts itself if no exports are imported', async () => { + const fileName = 'unused.ts' const file = `${testDir}/${fileName}` const url = '/' + file await untilBrowserLogAfter( () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, '>>> ready <<<'], - (logs) => { - expect(logs).toContain('loaded:all:a0b0c0default0') - expect(logs).toContain('all >>>>>> a0, b0, c0') - }, - ) - - await untilBrowserLogAfter( - () => { - editFile(file, (code) => code.replace(/([abc])0/g, '$11')) - }, - HOT_UPDATED, + [CONNECTED, '-- unused --'], (logs) => { - expect(logs).toEqual([ - 'all >>>>>> a1, b1, c1', - `[vite] hot updated: ${url}`, - ]) + expect(logs).toContain('-- unused --') }, ) await untilBrowserLogAfter( () => { - editFile(file, (code) => code.replace(/([abc])1/g, '$12')) + editFile(file, (code) => + code.replace('-- unused --', '-> unused <-'), + ) }, HOT_UPDATED, (logs) => { expect(logs).toEqual([ - 'all >>>>>> a2, b2, c2', + '-> unused <-', `[vite] hot updated: ${url}`, ]) }, ) }) - it("doesn't accept itself if one export is not accepted", async () => { - const fileName = 'deps-some-accepted.ts' + test("doesn't accept itself if any of its exports is imported", async () => { + const fileName = 'used.ts' const file = `${testDir}/${fileName}` await untilBrowserLogAfter( () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, '>>> ready <<<'], + [CONNECTED, '-- used --'], (logs) => { - expect(logs).toContain('loaded:some:a0b0c0default0') - expect(logs).toContain('some >>>>>> a0, b0, c0') + expect(logs).toContain('-- used --') + expect(logs).toContain('used:foo0') }, ) await untilBrowserLogAfter( async () => { - const loadPromise = page.waitForEvent('load') - editFile(file, (code) => code.replace(/([abc])0/g, '$11')) - await loadPromise + editFile(file, (code) => + code + .replace('foo0', 'foo1') + .replace('-- used --', '-> used <-'), + ) + await page.waitForEvent('load') }, - [CONNECTED, '>>> ready <<<'], + [CONNECTED, /used:foo/], (logs) => { - expect(logs).toContain('loaded:some:a1b1c1default0') - expect(logs).toContain('some >>>>>> a1, b1, c1') + expect(logs).toContain('-> used <-') + expect(logs).toContain('used:foo1') }, ) }) - } + }) + + describe('indiscriminate imports: import *', () => { + const testStarExports = (testDirName: string) => { + const testDir = `${baseDir}/${testDirName}` + + it('accepts itself if all its exports are accepted', async () => { + const fileName = 'deps-all-accepted.ts' + const file = `${testDir}/${fileName}` + const url = '/' + file + + await untilBrowserLogAfter( + () => page.goto(`${viteTestUrl}/${testDir}/`), + [CONNECTED, '>>> ready <<<'], + (logs) => { + expect(logs).toContain('loaded:all:a0b0c0default0') + expect(logs).toContain('all >>>>>> a0, b0, c0') + }, + ) + + await untilBrowserLogAfter( + () => { + editFile(file, (code) => code.replace(/([abc])0/g, '$11')) + }, + HOT_UPDATED, + (logs) => { + expect(logs).toEqual([ + 'all >>>>>> a1, b1, c1', + `[vite] hot updated: ${url}`, + ]) + }, + ) + + await untilBrowserLogAfter( + () => { + editFile(file, (code) => code.replace(/([abc])1/g, '$12')) + }, + HOT_UPDATED, + (logs) => { + expect(logs).toEqual([ + 'all >>>>>> a2, b2, c2', + `[vite] hot updated: ${url}`, + ]) + }, + ) + }) + + it("doesn't accept itself if one export is not accepted", async () => { + const fileName = 'deps-some-accepted.ts' + const file = `${testDir}/${fileName}` + + await untilBrowserLogAfter( + () => page.goto(`${viteTestUrl}/${testDir}/`), + [CONNECTED, '>>> ready <<<'], + (logs) => { + expect(logs).toContain('loaded:some:a0b0c0default0') + expect(logs).toContain('some >>>>>> a0, b0, c0') + }, + ) + + await untilBrowserLogAfter( + async () => { + const loadPromise = page.waitForEvent('load') + editFile(file, (code) => code.replace(/([abc])0/g, '$11')) + await loadPromise + }, + [CONNECTED, '>>> ready <<<'], + (logs) => { + expect(logs).toContain('loaded:some:a1b1c1default0') + expect(logs).toContain('some >>>>>> a1, b1, c1') + }, + ) + }) + } - describe('import * from ...', () => testStarExports('star-imports')) + describe('import * from ...', () => testStarExports('star-imports')) - describe('dynamic import(...)', () => testStarExports('dynamic-imports')) + describe('dynamic import(...)', () => + testStarExports('dynamic-imports')) + }) }) - }) - test('css in html hmr', async () => { - await page.goto(viteTestUrl) - expect(await getBg('.import-image')).toMatch('icon') - await page.goto(viteTestUrl + '/foo/', { waitUntil: 'load' }) - expect(await getBg('.import-image')).toMatch('icon') + test('css in html hmr', async () => { + await page.goto(viteTestUrl) + expect(await getBg('.import-image')).toMatch('icon') + await page.goto(viteTestUrl + '/foo/', { waitUntil: 'load' }) + expect(await getBg('.import-image')).toMatch('icon') - const loadPromise = page.waitForEvent('load') - editFile('index.html', (code) => code.replace('url("./icon.png")', '')) - await loadPromise - expect(await getBg('.import-image')).toMatch('') - }) + const loadPromise = page.waitForEvent('load') + editFile('index.html', (code) => code.replace('url("./icon.png")', '')) + await loadPromise + expect(await getBg('.import-image')).toMatch('') + }) - test('HTML', async () => { - await page.goto(viteTestUrl + '/counter/index.html') - let btn = await page.$('button') - expect(await btn.textContent()).toBe('Counter 0') + test('HTML', async () => { + await page.goto(viteTestUrl + '/counter/index.html') + let btn = await page.$('button') + expect(await btn.textContent()).toBe('Counter 0') - const loadPromise = page.waitForEvent('load') - editFile('counter/index.html', (code) => - code.replace('Counter', 'Compteur'), - ) - await loadPromise - btn = await page.$('button') - expect(await btn.textContent()).toBe('Compteur 0') - }) + const loadPromise = page.waitForEvent('load') + editFile('counter/index.html', (code) => + code.replace('Counter', 'Compteur'), + ) + await loadPromise + btn = await page.$('button') + expect(await btn.textContent()).toBe('Compteur 0') + }) - test('handle virtual module updates', async () => { - await page.goto(viteTestUrl) - const el = await page.$('.virtual') - expect(await el.textContent()).toBe('[success]0') - editFile('importedVirtual.js', (code) => code.replace('[success]', '[wow]')) - await untilUpdated(async () => { + test('handle virtual module updates', async () => { + await page.goto(viteTestUrl) const el = await page.$('.virtual') - return await el.textContent() - }, '[wow]') - }) + expect(await el.textContent()).toBe('[success]0') + editFile('importedVirtual.js', (code) => + code.replace('[success]', '[wow]'), + ) + await untilUpdated(async () => { + const el = await page.$('.virtual') + return await el.textContent() + }, '[wow]') + }) - test('invalidate virtual module', async () => { - await page.goto(viteTestUrl) - const el = await page.$('.virtual') - expect(await el.textContent()).toBe('[wow]0') - const btn = await page.$('.virtual-update') - btn.click() - await untilUpdated(async () => { + test('invalidate virtual module', async () => { + await page.goto(viteTestUrl) const el = await page.$('.virtual') - return await el.textContent() - }, '[wow]1') - }) + expect(await el.textContent()).toBe('[wow]0') + const btn = await page.$('.virtual-update') + btn.click() + await untilUpdated(async () => { + const el = await page.$('.virtual') + return await el.textContent() + }, '[wow]1') + }) - test('handle virtual module accept updates', async () => { - await page.goto(viteTestUrl) - const el = await page.$('.virtual-dep') - expect(await el.textContent()).toBe('0') - editFile('importedVirtual.js', (code) => code.replace('[success]', '[wow]')) - await untilUpdated(async () => { + test('handle virtual module accept updates', async () => { + await page.goto(viteTestUrl) const el = await page.$('.virtual-dep') - return await el.textContent() - }, '[wow]') - }) + expect(await el.textContent()).toBe('0') + editFile('importedVirtual.js', (code) => + code.replace('[success]', '[wow]'), + ) + await untilUpdated(async () => { + const el = await page.$('.virtual-dep') + return await el.textContent() + }, '[wow]') + }) - test('invalidate virtual module and accept', async () => { - await page.goto(viteTestUrl) - const el = await page.$('.virtual-dep') - expect(await el.textContent()).toBe('0') - const btn = await page.$('.virtual-update-dep') - btn.click() - await untilUpdated(async () => { + test('invalidate virtual module and accept', async () => { + await page.goto(viteTestUrl) const el = await page.$('.virtual-dep') - return await el.textContent() - }, '[wow]2') - }) - - test('keep hmr reload after missing import on server startup', async () => { - const file = 'missing-import/a.js' - const importCode = "import 'missing-modules'" - const unImportCode = `// ${importCode}` - - await untilBrowserLogAfter( - () => - page.goto(viteTestUrl + '/missing-import/index.html', { - waitUntil: 'load', - }), - /connected/, // wait for HMR connection - ) + expect(await el.textContent()).toBe('0') + const btn = await page.$('.virtual-update-dep') + btn.click() + await untilUpdated(async () => { + const el = await page.$('.virtual-dep') + return await el.textContent() + }, '[wow]2') + }) - await untilBrowserLogAfter(async () => { - const loadPromise = page.waitForEvent('load') - editFile(file, (code) => code.replace(importCode, unImportCode)) - await loadPromise - }, ['missing test', /connected/]) + test('keep hmr reload after missing import on server startup', async () => { + const file = 'missing-import/a.js' + const importCode = "import 'missing-modules'" + const unImportCode = `// ${importCode}` - await untilBrowserLogAfter(async () => { - const loadPromise = page.waitForEvent('load') - editFile(file, (code) => code.replace(unImportCode, importCode)) - await loadPromise - }, [/500/, /connected/]) - }) + await untilBrowserLogAfter( + () => + page.goto(viteTestUrl + '/missing-import/index.html', { + waitUntil: 'load', + }), + /connected/, // wait for HMR connection + ) - test('should hmr when file is deleted and restored', async () => { - await page.goto(viteTestUrl) + await untilBrowserLogAfter(async () => { + const loadPromise = page.waitForEvent('load') + editFile(file, (code) => code.replace(importCode, unImportCode)) + await loadPromise + }, ['missing test', /connected/]) + + await untilBrowserLogAfter(async () => { + const loadPromise = page.waitForEvent('load') + editFile(file, (code) => code.replace(unImportCode, importCode)) + await loadPromise + }, [/500/, /connected/]) + }) - const parentFile = 'file-delete-restore/parent.js' - const childFile = 'file-delete-restore/child.js' + test('should hmr when file is deleted and restored', async () => { + await page.goto(viteTestUrl) - await untilUpdated( - () => page.textContent('.file-delete-restore'), - 'parent:child', - ) + const parentFile = 'file-delete-restore/parent.js' + const childFile = 'file-delete-restore/child.js' - editFile(childFile, (code) => - code.replace("value = 'child'", "value = 'child1'"), - ) - await untilUpdated( - () => page.textContent('.file-delete-restore'), - 'parent:child1', - ) + await untilUpdated( + () => page.textContent('.file-delete-restore'), + 'parent:child', + ) - // delete the file - editFile(parentFile, (code) => - code.replace( - "export { value as childValue } from './child'", - "export const childValue = 'not-child'", - ), - ) - const originalChildFileCode = readFile(childFile) - await Promise.all([ - untilBrowserLogAfter( - () => removeFile(childFile), - `${childFile} is disposed`, - ), - untilUpdated( + editFile(childFile, (code) => + code.replace("value = 'child'", "value = 'child1'"), + ) + await untilUpdated( () => page.textContent('.file-delete-restore'), - 'parent:not-child', - ), - ]) + 'parent:child1', + ) - await untilBrowserLogAfter(async () => { - const loadPromise = page.waitForEvent('load') - addFile(childFile, originalChildFileCode) + // delete the file editFile(parentFile, (code) => code.replace( - "export const childValue = 'not-child'", "export { value as childValue } from './child'", + "export const childValue = 'not-child'", ), ) - await loadPromise - }, [/connected/]) - await untilUpdated( - () => page.textContent('.file-delete-restore'), - 'parent:child', - ) - }) - - test('delete file should not break hmr', async () => { - await page.goto(viteTestUrl) + const originalChildFileCode = readFile(childFile) + await Promise.all([ + untilBrowserLogAfter( + () => removeFile(childFile), + `${childFile} is disposed`, + ), + untilUpdated( + () => page.textContent('.file-delete-restore'), + 'parent:not-child', + ), + ]) - await untilUpdated( - () => page.textContent('.intermediate-file-delete-display'), - 'count is 1', - ) + await untilBrowserLogAfter(async () => { + const loadPromise = page.waitForEvent('load') + addFile(childFile, originalChildFileCode) + editFile(parentFile, (code) => + code.replace( + "export const childValue = 'not-child'", + "export { value as childValue } from './child'", + ), + ) + await loadPromise + }, [/connected/]) + await untilUpdated( + () => page.textContent('.file-delete-restore'), + 'parent:child', + ) + }) - // add state - await page.click('.intermediate-file-delete-increment') - await untilUpdated( - () => page.textContent('.intermediate-file-delete-display'), - 'count is 2', - ) + test('delete file should not break hmr', async () => { + await page.goto(viteTestUrl) - // update import, hmr works - editFile('intermediate-file-delete/index.js', (code) => - code.replace("from './re-export.js'", "from './display.js'"), - ) - editFile('intermediate-file-delete/display.js', (code) => - code.replace('count is ${count}', 'count is ${count}!'), - ) - await untilUpdated( - () => page.textContent('.intermediate-file-delete-display'), - 'count is 2!', - ) + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 1', + ) - // remove unused file, page reload because it's considered entry point now - removeFile('intermediate-file-delete/re-export.js') - await untilUpdated( - () => page.textContent('.intermediate-file-delete-display'), - 'count is 1!', - ) + // add state + await page.click('.intermediate-file-delete-increment') + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2', + ) - // re-add state - await page.click('.intermediate-file-delete-increment') - await untilUpdated( - () => page.textContent('.intermediate-file-delete-display'), - 'count is 2!', - ) + // update import, hmr works + editFile('intermediate-file-delete/index.js', (code) => + code.replace("from './re-export.js'", "from './display.js'"), + ) + editFile('intermediate-file-delete/display.js', (code) => + code.replace('count is ${count}', 'count is ${count}!'), + ) + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2!', + ) - // hmr works after file deletion - editFile('intermediate-file-delete/display.js', (code) => - code.replace('count is ${count}!', 'count is ${count}'), - ) - await untilUpdated( - () => page.textContent('.intermediate-file-delete-display'), - 'count is 2', - ) - }) + // remove unused file, page reload because it's considered entry point now + removeFile('intermediate-file-delete/re-export.js') + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 1!', + ) - test('deleted file should trigger dispose and prune callbacks', async () => { - await page.goto(viteTestUrl) + // re-add state + await page.click('.intermediate-file-delete-increment') + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2!', + ) - const parentFile = 'file-delete-restore/parent.js' - const childFile = 'file-delete-restore/child.js' - const originalChildFileCode = readFile(childFile) + // hmr works after file deletion + editFile('intermediate-file-delete/display.js', (code) => + code.replace('count is ${count}!', 'count is ${count}'), + ) + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2', + ) + }) - await untilBrowserLogAfter( - () => { - // delete the file - editFile(parentFile, (code) => - code.replace( - "export { value as childValue } from './child'", - "export const childValue = 'not-child'", - ), - ) - removeFile(childFile) - }, - [ - 'file-delete-restore/child.js is disposed', - 'file-delete-restore/child.js is pruned', - ], - false, - ) - await untilUpdated( - () => page.textContent('.file-delete-restore'), - 'parent:not-child', - ) + test('deleted file should trigger dispose and prune callbacks', async () => { + await page.goto(viteTestUrl) - // restore the file - addFile(childFile, originalChildFileCode) - editFile(parentFile, (code) => - code.replace( - "export const childValue = 'not-child'", - "export { value as childValue } from './child'", - ), - ) - await untilUpdated( - () => page.textContent('.file-delete-restore'), - 'parent:child', - ) - }) + const parentFile = 'file-delete-restore/parent.js' + const childFile = 'file-delete-restore/child.js' + const originalChildFileCode = readFile(childFile) - test('import.meta.hot?.accept', async () => { - await page.goto(viteTestUrl) + await untilBrowserLogAfter( + () => { + // delete the file + editFile(parentFile, (code) => + code.replace( + "export { value as childValue } from './child'", + "export const childValue = 'not-child'", + ), + ) + removeFile(childFile) + }, + [ + 'file-delete-restore/child.js is disposed', + 'file-delete-restore/child.js is pruned', + ], + false, + ) + await untilUpdated( + () => page.textContent('.file-delete-restore'), + 'parent:not-child', + ) - const el = await page.$('.optional-chaining') - await untilBrowserLogAfter( - () => - editFile('optional-chaining/child.js', (code) => - code.replace('const foo = 1', 'const foo = 2'), + // restore the file + addFile(childFile, originalChildFileCode) + editFile(parentFile, (code) => + code.replace( + "export const childValue = 'not-child'", + "export { value as childValue } from './child'", ), - '(optional-chaining) child update', - ) - await untilUpdated(() => el.textContent(), '2') - }) + ) + await untilUpdated( + () => page.textContent('.file-delete-restore'), + 'parent:child', + ) + }) - test('hmr works for self-accepted module within circular imported files', async () => { - await page.goto(viteTestUrl + '/self-accept-within-circular/index.html') - const el = await page.$('.self-accept-within-circular') - expect(await el.textContent()).toBe('c') - editFile('self-accept-within-circular/c.js', (code) => - code.replace(`export const c = 'c'`, `export const c = 'cc'`), - ) - await untilUpdated( - () => page.textContent('.self-accept-within-circular'), - 'cc', - ) - expect(serverLogs.length).greaterThanOrEqual(1) - // Should still keep hmr update, but it'll error on the browser-side and will refresh itself. - // Match on full log not possible because of color markers - expect(serverLogs.at(-1)!).toContain('hmr update') - }) + test('import.meta.hot?.accept', async () => { + await page.goto(viteTestUrl) - test('hmr should not reload if no accepted within circular imported files', async () => { - await page.goto(viteTestUrl + '/circular/index.html') - const el = await page.$('.circular') - expect(await el.textContent()).toBe( - 'mod-a -> mod-b -> mod-c -> mod-a (expected error)', - ) - editFile('circular/mod-b.js', (code) => - code.replace(`mod-b ->`, `mod-b (edited) ->`), - ) - await untilUpdated( - () => el.textContent(), - 'mod-a -> mod-b (edited) -> mod-c -> mod-a (expected error)', - ) - }) + const el = await page.$('.optional-chaining') + await untilBrowserLogAfter( + () => + editFile('optional-chaining/child.js', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + '(optional-chaining) child update', + ) + await untilUpdated(() => el.textContent(), '2') + }) - test('not inlined assets HMR', async () => { - await page.goto(viteTestUrl) - const el = await page.$('#logo-no-inline') - await untilBrowserLogAfter( - () => - editFile('logo-no-inline.svg', (code) => - code.replace('height="30px"', 'height="40px"'), - ), - /Logo-no-inline updated/, - ) - await untilUpdated(() => el.evaluate((it) => `${it.clientHeight}`), '40') - }) + test('hmr works for self-accepted module within circular imported files', async () => { + await page.goto(viteTestUrl + '/self-accept-within-circular/index.html') + const el = await page.$('.self-accept-within-circular') + expect(await el.textContent()).toBe('c') + editFile('self-accept-within-circular/c.js', (code) => + code.replace(`export const c = 'c'`, `export const c = 'cc'`), + ) + await untilUpdated( + () => page.textContent('.self-accept-within-circular'), + 'cc', + ) + expect(serverLogs.length).greaterThanOrEqual(1) + // Should still keep hmr update, but it'll error on the browser-side and will refresh itself. + // Match on full log not possible because of color markers + expect(serverLogs.at(-1)!).toContain('hmr update') + }) - test('inlined assets HMR', async () => { - await page.goto(viteTestUrl) - const el = await page.$('#logo') - await untilBrowserLogAfter( - () => - editFile('logo.svg', (code) => - code.replace('height="30px"', 'height="40px"'), - ), - /Logo updated/, - ) - await untilUpdated(() => el.evaluate((it) => `${it.clientHeight}`), '40') - }) + test('hmr should not reload if no accepted within circular imported files', async () => { + await page.goto(viteTestUrl + '/circular/index.html') + const el = await page.$('.circular') + expect(await el.textContent()).toBe( + 'mod-a -> mod-b -> mod-c -> mod-a (expected error)', + ) + editFile('circular/mod-b.js', (code) => + code.replace(`mod-b ->`, `mod-b (edited) ->`), + ) + await untilUpdated( + () => el.textContent(), + 'mod-a -> mod-b (edited) -> mod-c -> mod-a (expected error)', + ) + }) - test('CSS HMR with this.addWatchFile', async () => { - await page.goto(viteTestUrl + '/css-deps/index.html') - expect(await getColor('.css-deps')).toMatch('red') - editFile('css-deps/dep.js', (code) => code.replace(`red`, `green`)) - await untilUpdated(() => getColor('.css-deps'), 'green') - }) + test('not inlined assets HMR', async () => { + await page.goto(viteTestUrl) + const el = await page.$('#logo-no-inline') + await untilBrowserLogAfter( + () => + editFile('logo-no-inline.svg', (code) => + code.replace('height="30px"', 'height="40px"'), + ), + /Logo-no-inline updated/, + ) + await untilUpdated(() => el.evaluate((it) => `${it.clientHeight}`), '40') + }) + + test('inlined assets HMR', async () => { + await page.goto(viteTestUrl) + const el = await page.$('#logo') + await untilBrowserLogAfter( + () => + editFile('logo.svg', (code) => + code.replace('height="30px"', 'height="40px"'), + ), + /Logo updated/, + ) + await untilUpdated(() => el.evaluate((it) => `${it.clientHeight}`), '40') + }) - test('hmr should happen after missing file is created', async () => { - const file = 'missing-file/a.js' - const code = 'console.log("a.js")' + test('CSS HMR with this.addWatchFile', async () => { + await page.goto(viteTestUrl + '/css-deps/index.html') + expect(await getColor('.css-deps')).toMatch('red') + editFile('css-deps/dep.js', (code) => code.replace(`red`, `green`)) + await untilUpdated(() => getColor('.css-deps'), 'green') + }) - await untilBrowserLogAfter( - () => - page.goto(viteTestUrl + '/missing-file/index.html', { - waitUntil: 'load', - }), - /connected/, // wait for HMR connection - ) + test('hmr should happen after missing file is created', async () => { + const file = 'missing-file/a.js' + const code = 'console.log("a.js")' - await untilBrowserLogAfter(async () => { - const loadPromise = page.waitForEvent('load') - addFile(file, code) - await loadPromise - }, [/connected/, 'a.js']) - }) + await untilBrowserLogAfter( + () => + page.goto(viteTestUrl + '/missing-file/index.html', { + waitUntil: 'load', + }), + /connected/, // wait for HMR connection + ) + + await untilBrowserLogAfter(async () => { + const loadPromise = page.waitForEvent('load') + addFile(file, code) + await loadPromise + }, [/connected/, 'a.js']) + }) + } } -} \ No newline at end of file From c1d2a74f029dc0a29bcea925f763563099a9e072 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 25 Apr 2025 10:43:02 +0800 Subject: [PATCH 49/76] chore: disable dynamic import warning at dev build --- packages/vite/src/node/plugins/reporter.ts | 54 +++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index a3a40ddc979494..0a6b4a1fb40805 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -298,37 +298,37 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { }, renderChunk(_, chunk, options) { - if (!options.inlineDynamicImports) { - for (const id of chunk.moduleIds) { - const module = this.getModuleInfo(id) - if (!module) continue - // When a dynamic importer shares a chunk with the imported module, - // warn that the dynamic imported module will not be moved to another chunk (#12850). - if (module.importers.length && module.dynamicImporters.length) { - // Filter out the intersection of dynamic importers and sibling modules in - // the same chunk. The intersecting dynamic importers' dynamic import is not - // expected to work. Note we're only detecting the direct ineffective - // dynamic import here. - const detectedIneffectiveDynamicImport = - module.dynamicImporters.some( - (id) => !isInNodeModules(id) && chunk.moduleIds.includes(id), - ) - if (detectedIneffectiveDynamicImport) { - this.warn( - `\n(!) ${ - module.id - } is dynamically imported by ${module.dynamicImporters.join( - ', ', - )} but also statically imported by ${module.importers.join( - ', ', - )}, dynamic import will not move module into another chunk.\n`, - ) + if (config.command === 'build' && !config.experimental.fullBundleMode) { + if (!options.inlineDynamicImports) { + for (const id of chunk.moduleIds) { + const module = this.getModuleInfo(id) + if (!module) continue + // When a dynamic importer shares a chunk with the imported module, + // warn that the dynamic imported module will not be moved to another chunk (#12850). + if (module.importers.length && module.dynamicImporters.length) { + // Filter out the intersection of dynamic importers and sibling modules in + // the same chunk. The intersecting dynamic importers' dynamic import is not + // expected to work. Note we're only detecting the direct ineffective + // dynamic import here. + const detectedIneffectiveDynamicImport = + module.dynamicImporters.some( + (id) => !isInNodeModules(id) && chunk.moduleIds.includes(id), + ) + if (detectedIneffectiveDynamicImport) { + this.warn( + `\n(!) ${ + module.id + } is dynamically imported by ${module.dynamicImporters.join( + ', ', + )} but also statically imported by ${module.importers.join( + ', ', + )}, dynamic import will not move module into another chunk.\n`, + ) + } } } } - } - if (config.command === 'build' && !config.experimental.fullBundleMode) { chunksReporter(this).register() } }, From bbb1def331eacc210b22f1b1b4405ab5dde3cde6 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 25 Apr 2025 10:52:04 +0800 Subject: [PATCH 50/76] chore: remove watch changed file log --- packages/vite/src/node/build.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 25ac410be4605e..6bc69e0e2934de 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -946,7 +946,6 @@ async function buildEnvironment( if (server) { server.watcher.on('change', async (file) => { - logger.info(`${colors.green(`${path.relative(root, file)} changed.`)}`) const startTime = Date.now() const hmrOutput = (await bundle!.generateHmrPatch([file]))! // @ts-expect-error Need to upgrade rolldown From 662fbb7082acc27a3b2616e1901b3bf128d20ca6 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 25 Apr 2025 16:55:31 +0800 Subject: [PATCH 51/76] chore: pass deps related tests --- playground/hmr/__tests__/hmr.spec.ts | 166 +++++++++++++-------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index c2441decafad3c..71c70612f51311 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -67,93 +67,93 @@ if (!isBuild) { await untilUpdated(() => el.textContent(), '3') }) - if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { - test('accept dep', async () => { - const el = await page.$('.dep') - await untilBrowserLogAfter( - () => - editFile('hmrDep.js', (code) => - code.replace('const foo = 1', 'const foo = 2'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 1', - '(dep) foo from dispose: 1', - '(single dep) foo is now: 2', - '(single dep) nested foo is now: 1', - '(multi deps) foo is now: 2', - '(multi deps) nested foo is now: 1', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '2') + test('accept dep', async () => { + const el = await page.$('.dep') + await untilBrowserLogAfter( + () => + editFile('hmrDep.js', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 1', + '(dep) foo from dispose: 1', + '(single dep) foo is now: 2', + '(single dep) nested foo is now: 1', + '(multi deps) foo is now: 2', + '(multi deps) nested foo is now: 1', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '2') - await untilBrowserLogAfter( - () => - editFile('hmrDep.js', (code) => - code.replace('const foo = 2', 'const foo = 3'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 2', - '(dep) foo from dispose: 2', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 1', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 1', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '3') - }) + await untilBrowserLogAfter( + () => + editFile('hmrDep.js', (code) => + code.replace('const foo = 2', 'const foo = 3'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 2', + '(dep) foo from dispose: 2', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 1', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 1', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '3') + }) - test('nested dep propagation', async () => { - const el = await page.$('.nested') - await untilBrowserLogAfter( - () => - editFile('hmrNestedDep.js', (code) => - code.replace('const foo = 1', 'const foo = 2'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 3', - '(dep) foo from dispose: 3', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 2', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 2', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '2') + test('nested dep propagation', async () => { + const el = await page.$('.nested') + await untilBrowserLogAfter( + () => + editFile('hmrNestedDep.js', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 3', + '(dep) foo from dispose: 3', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 2', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 2', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '2') - await untilBrowserLogAfter( - () => - editFile('hmrNestedDep.js', (code) => - code.replace('const foo = 2', 'const foo = 3'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 3', - '(dep) foo from dispose: 3', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 3', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 3', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '3') - }) + await untilBrowserLogAfter( + () => + editFile('hmrNestedDep.js', (code) => + code.replace('const foo = 2', 'const foo = 3'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 3', + '(dep) foo from dispose: 3', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 3', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 3', + '[vite] hot updated: /hmrDep.js via /hmr.ts', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), '3') + }) + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('invalidate', async () => { const el = await page.$('.invalidation-parent') await untilBrowserLogAfter( From 064c2683169a3a83b8d09589548eab9785db0f19 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 29 Apr 2025 16:55:08 +0800 Subject: [PATCH 52/76] fix: pass some hmr invalidate tests --- packages/vite/src/client/client.ts | 2 +- packages/vite/src/module-runner/runner.ts | 3 +- packages/vite/src/node/build.ts | 49 +++++++++-- packages/vite/src/node/server/environment.ts | 25 +++--- packages/vite/src/shared/hmr.ts | 10 ++- playground/hmr/__tests__/hmr.spec.ts | 88 ++++++++++---------- 6 files changed, 107 insertions(+), 70 deletions(-) diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index a16facfd399f09..aa5f9d6e4f43a8 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -455,7 +455,7 @@ export function removeStyle(id: string): void { } export function createHotContext(ownerPath: string): ViteHotContext { - return new HMRContext(hmrClient, ownerPath) + return new HMRContext(hmrClient, ownerPath, __FULL_BUNDLE_MODE__) } /** diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index 339b6a6a8908e6..de74899548b306 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -77,6 +77,7 @@ export class ModuleRunner { resolvedHmrLogger, this.transport, ({ acceptedPath }) => this.import(acceptedPath), + false, ) if (!this.transport.connect) { throw new Error( @@ -391,7 +392,7 @@ export class ModuleRunner { throw new Error(`[module runner] HMR client was closed.`) } this.debug?.('[module runner] creating hmr context for', mod.url) - hotContext ||= new HMRContext(this.hmrClient, mod.url) + hotContext ||= new HMRContext(this.hmrClient, mod.url, false) return hotContext }, set: (value) => { diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 6bc69e0e2934de..09d4ee8f449d3c 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -945,24 +945,24 @@ async function buildEnvironment( } if (server) { - server.watcher.on('change', async (file) => { - const startTime = Date.now() - const hmrOutput = (await bundle!.generateHmrPatch([file]))! + async function handleHmrOutput(hmrOutput: any, file: string) { // @ts-expect-error Need to upgrade rolldown if (hmrOutput.fullReload) { - await build() - server.ws.send({ + if (!hmrOutput.firstInvalidatedBy) { + await build() + } + server!.ws.send({ type: 'full-reload', }) logger.info(colors.green(`page reload `) + colors.dim(file), { - clear: true, + clear: !hmrOutput.firstInvalidatedBy, timestamp: true, }) } if (hmrOutput.patch) { const url = `${Date.now()}.js` - server.memoryFiles[url] = hmrOutput.patch + server!.memoryFiles[url] = hmrOutput.patch const updates = hmrOutput.hmrBoundaries.map((boundary) => { return { type: 'js-update', @@ -972,22 +972,53 @@ async function buildEnvironment( timestamp: 0, } }) as Update[] - server.ws.send({ + server!.ws.send({ type: 'update', updates, }) logger.info( colors.green(`hmr update `) + colors.dim([...new Set(updates.map((u) => u.path))].join(', ')), - { clear: true, timestamp: true }, + { clear: !hmrOutput.firstInvalidatedBy, timestamp: true }, ) + } + } + server.watcher.on('change', async (file) => { + const startTime = Date.now() + const hmrOutput = (await bundle!.generateHmrPatch([file]))! + // TODO(underfin): rebuild at first could be work. + if (hmrOutput.patch) { await build() logger.info( `${colors.green(`✓ rebuilt in ${displayTime(Date.now() - startTime)}`)}`, ) } + await handleHmrOutput(hmrOutput, file) + + // TODO(underfin): The invalidate case is failed because the hmrInvalidate is hang after rebuild at here . }) + server.hot.on( + 'vite:invalidate', + async ({ path: file, message, firstInvalidatedBy }) => { + file = path.join(root, file) + const hmrOutput = (await bundle!.hmrInvalidate( + file, + firstInvalidatedBy, + ))! + if (hmrOutput) { + if (hmrOutput.isSelfAccepting) { + logger.info( + colors.yellow(`hmr invalidate `) + + colors.dim(file) + + (message ? ` ${message}` : ''), + { timestamp: true }, + ) + await handleHmrOutput(hmrOutput, file) + } + } + }, + ) } return Array.isArray(outputs) ? res : res[0] diff --git a/packages/vite/src/node/server/environment.ts b/packages/vite/src/node/server/environment.ts index 7da15747f62673..9c075d8900402b 100644 --- a/packages/vite/src/node/server/environment.ts +++ b/packages/vite/src/node/server/environment.ts @@ -134,18 +134,21 @@ export class DevEnvironment extends BaseEnvironment { }, }) - this.hot.on( - 'vite:invalidate', - async ({ path, message, firstInvalidatedBy }) => { - invalidateModule(this, { - path, - message, - firstInvalidatedBy, - }) - }, - ) - const { optimizeDeps, experimental } = this.config + + if (!experimental.fullBundleMode) { + this.hot.on( + 'vite:invalidate', + async ({ path, message, firstInvalidatedBy }) => { + invalidateModule(this, { + path, + message, + firstInvalidatedBy, + }) + }, + ) + } + if (context.depsOptimizer && !experimental.fullBundleMode) { this.depsOptimizer = context.depsOptimizer } else if (isDepOptimizationDisabled(optimizeDeps)) { diff --git a/packages/vite/src/shared/hmr.ts b/packages/vite/src/shared/hmr.ts index 81e9efe39b9304..7c1255e85e16fe 100644 --- a/packages/vite/src/shared/hmr.ts +++ b/packages/vite/src/shared/hmr.ts @@ -27,6 +27,7 @@ export class HMRContext implements ViteHotContext { constructor( private hmrClient: HMRClient, private ownerPath: string, + private fullBundleMode: boolean, ) { if (!hmrClient.dataMap.has(ownerPath)) { hmrClient.dataMap.set(ownerPath, {}) @@ -99,18 +100,21 @@ export class HMRContext implements ViteHotContext { invalidate(message: string): void { const firstInvalidatedBy = this.hmrClient.currentFirstInvalidatedBy ?? this.ownerPath + const ownerPath = this.fullBundleMode + ? `/${this.ownerPath}` + : this.ownerPath this.hmrClient.notifyListeners('vite:invalidate', { - path: this.ownerPath, + path: ownerPath, message, firstInvalidatedBy, }) this.send('vite:invalidate', { - path: this.ownerPath, + path: ownerPath, message, firstInvalidatedBy, }) this.hmrClient.logger.debug( - `invalidate ${this.ownerPath}${message ? `: ${message}` : ''}`, + `invalidate ${ownerPath}${message ? `: ${message}` : ''}`, ) } diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 71c70612f51311..5b27550646313c 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -153,8 +153,35 @@ if (!isBuild) { await untilUpdated(() => el.textContent(), '3') }) - if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { - test('invalidate', async () => { + test('invalidate', async () => { + const el = await page.$('.invalidation-parent') + await untilBrowserLogAfter( + () => + editFile('invalidation/child.js', (code) => + code.replace('child', 'child updated'), + ), + [ + '>>> vite:beforeUpdate -- update', + '>>> vite:invalidate -- /invalidation/child.js', + '[vite] invalidate /invalidation/child.js', + '[vite] hot updated: /invalidation/child.js', + '>>> vite:afterUpdate -- update', + '>>> vite:beforeUpdate -- update', + '(invalidation) parent is executing', + '[vite] hot updated: /invalidation/parent.js', + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el.textContent(), 'child updated') + }) + + test('invalidate works with multiple tabs', async () => { + let page2: Page + try { + page2 = await browser.newPage() + await page2.goto(viteTestUrl) + const el = await page.$('.invalidation-parent') await untilBrowserLogAfter( () => @@ -167,6 +194,7 @@ if (!isBuild) { '[vite] invalidate /invalidation/child.js', '[vite] hot updated: /invalidation/child.js', '>>> vite:afterUpdate -- update', + // if invalidate dedupe doesn't work correctly, this beforeUpdate will be called twice '>>> vite:beforeUpdate -- update', '(invalidation) parent is executing', '[vite] hot updated: /invalidation/parent.js', @@ -175,51 +203,21 @@ if (!isBuild) { true, ) await untilUpdated(() => el.textContent(), 'child updated') - }) - - test('invalidate works with multiple tabs', async () => { - let page2: Page - try { - page2 = await browser.newPage() - await page2.goto(viteTestUrl) - - const el = await page.$('.invalidation-parent') - await untilBrowserLogAfter( - () => - editFile('invalidation/child.js', (code) => - code.replace('child', 'child updated'), - ), - [ - '>>> vite:beforeUpdate -- update', - '>>> vite:invalidate -- /invalidation/child.js', - '[vite] invalidate /invalidation/child.js', - '[vite] hot updated: /invalidation/child.js', - '>>> vite:afterUpdate -- update', - // if invalidate dedupe doesn't work correctly, this beforeUpdate will be called twice - '>>> vite:beforeUpdate -- update', - '(invalidation) parent is executing', - '[vite] hot updated: /invalidation/parent.js', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), 'child updated') - } finally { - await page2.close() - } - }) + } finally { + await page2.close() + } + }) - test('invalidate on root triggers page reload', async () => { - editFile('invalidation/root.js', (code) => - code.replace('Init', 'Updated'), - ) - await page.waitForEvent('load') - await untilUpdated( - async () => (await page.$('.invalidation-root')).textContent(), - 'Updated', - ) - }) + test('invalidate on root triggers page reload', async () => { + editFile('invalidation/root.js', (code) => code.replace('Init', 'Updated')) + await page.waitForEvent('load') + await untilUpdated( + async () => (await page.$('.invalidation-root')).textContent(), + 'Updated', + ) + }) + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('soft invalidate', async () => { const el = await page.$('.soft-invalidation') expect(await el.textContent()).toBe( From 28ad636fe74a1368a41ae7103faab079698beb73 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 29 Apr 2025 17:34:39 +0800 Subject: [PATCH 53/76] fix: ignored hmr soft invalidate --- playground/hmr/__tests__/hmr.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 5b27550646313c..54c53880e2a56c 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -217,6 +217,9 @@ if (!isBuild) { ) }) + // The file will be transformed twice at rolldown hmr and rebuild. + // It is a performance improvement at vite, it should be ignored at rolldown-vite full bundle mode. + // Other, not sure why the times is 10, but using the client to check the times is 2. if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('soft invalidate', async () => { const el = await page.$('.soft-invalidation') @@ -242,7 +245,9 @@ if (!isBuild) { 'soft-invalidation/index.js is transformed 2 times. child is now updated?', ) }) + } + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('invalidate in circular dep should not trigger infinite HMR', async () => { const el = await page.$('.invalidation-circular-deps') await untilUpdated(() => el.textContent(), 'child') From c4043321e0c04b4c0f174c10e79bbc3de7d17895 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 30 Apr 2025 11:01:32 +0800 Subject: [PATCH 54/76] fix: invalidate in circular dep --- packages/vite/src/node/build.ts | 19 ++++++++---- playground/hmr/__tests__/hmr.spec.ts | 46 ++++++++++++++-------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 09d4ee8f449d3c..bec8cffe54ba8c 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -954,10 +954,17 @@ async function buildEnvironment( server!.ws.send({ type: 'full-reload', }) - logger.info(colors.green(`page reload `) + colors.dim(file), { - clear: !hmrOutput.firstInvalidatedBy, - timestamp: true, - }) + const reason = hmrOutput.fullReloadReason + ? colors.dim(` (${hmrOutput.fullReloadReason})`) + : '' + logger.info( + colors.green(`page reload `) + colors.dim(file) + reason, + { + clear: !hmrOutput.firstInvalidatedBy, + timestamp: true, + }, + ) + return } if (hmrOutput.patch) { @@ -969,6 +976,7 @@ async function buildEnvironment( url, path: boundary.boundary, acceptedPath: boundary.acceptedVia, + firstInvalidatedBy: hmrOutput.firstInvalidatedBy, timestamp: 0, } }) as Update[] @@ -1001,9 +1009,8 @@ async function buildEnvironment( server.hot.on( 'vite:invalidate', async ({ path: file, message, firstInvalidatedBy }) => { - file = path.join(root, file) const hmrOutput = (await bundle!.hmrInvalidate( - file, + path.join(root, file), firstInvalidatedBy, ))! if (hmrOutput) { diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 54c53880e2a56c..0175fb1ea08873 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -247,31 +247,31 @@ if (!isBuild) { }) } - if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { - test('invalidate in circular dep should not trigger infinite HMR', async () => { - const el = await page.$('.invalidation-circular-deps') - await untilUpdated(() => el.textContent(), 'child') - editFile( - 'invalidation-circular-deps/circular-invalidate/child.js', - (code) => code.replace('child', 'child updated'), - ) - await page.waitForEvent('load') - await untilUpdated( - () => page.textContent('.invalidation-circular-deps'), - 'child updated', - ) - }) + test('invalidate in circular dep should not trigger infinite HMR', async () => { + const el = await page.$('.invalidation-circular-deps') + await untilUpdated(() => el.textContent(), 'child') + editFile( + 'invalidation-circular-deps/circular-invalidate/child.js', + (code) => code.replace('child', 'child updated'), + ) + await page.waitForEvent('load') + await untilUpdated( + () => page.textContent('.invalidation-circular-deps'), + 'child updated', + ) + }) - test('invalidate in circular dep should be hot updated if possible', async () => { - const el = await page.$('.invalidation-circular-deps-handled') - await untilUpdated(() => el.textContent(), 'child') - editFile( - 'invalidation-circular-deps/invalidate-handled-in-circle/child.js', - (code) => code.replace('child', 'child updated'), - ) - await untilUpdated(() => el.textContent(), 'child updated') - }) + test('invalidate in circular dep should be hot updated if possible', async () => { + const el = await page.$('.invalidation-circular-deps-handled') + await untilUpdated(() => el.textContent(), 'child') + editFile( + 'invalidation-circular-deps/invalidate-handled-in-circle/child.js', + (code) => code.replace('child', 'child updated'), + ) + await untilUpdated(() => el.textContent(), 'child updated') + }) + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('plugin hmr handler + custom event', async () => { const el = await page.$('.custom') editFile('customFile.js', (code) => code.replace('custom', 'edited')) From 6d4a11546cacd0ecfc84f38b064e80705e0d1c82 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 30 Apr 2025 11:09:39 +0800 Subject: [PATCH 55/76] chore: disable not entry html hmr test and css hmr test --- playground/hmr/__tests__/hmr.spec.ts | 40 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 0175fb1ea08873..21e1290c853f53 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -271,26 +271,27 @@ if (!isBuild) { await untilUpdated(() => el.textContent(), 'child updated') }) - if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { - test('plugin hmr handler + custom event', async () => { - const el = await page.$('.custom') - editFile('customFile.js', (code) => code.replace('custom', 'edited')) - await untilUpdated(() => el.textContent(), 'edited') - }) + test('plugin hmr handler + custom event', async () => { + const el = await page.$('.custom') + editFile('customFile.js', (code) => code.replace('custom', 'edited')) + await untilUpdated(() => el.textContent(), 'edited') + }) - test('plugin hmr remove custom events', async () => { - const el = await page.$('.toRemove') - editFile('customFile.js', (code) => code.replace('custom', 'edited')) - await untilUpdated(() => el.textContent(), 'edited') - editFile('customFile.js', (code) => code.replace('edited', 'custom')) - await untilUpdated(() => el.textContent(), 'edited') - }) + test('plugin hmr remove custom events', async () => { + const el = await page.$('.toRemove') + editFile('customFile.js', (code) => code.replace('custom', 'edited')) + await untilUpdated(() => el.textContent(), 'edited') + editFile('customFile.js', (code) => code.replace('edited', 'custom')) + await untilUpdated(() => el.textContent(), 'edited') + }) - test('plugin client-server communication', async () => { - const el = await page.$('.custom-communication') - await untilUpdated(() => el.textContent(), '3') - }) + test('plugin client-server communication', async () => { + const el = await page.$('.custom-communication') + await untilUpdated(() => el.textContent(), '3') + }) + // BreakChange: The html is not a entry, it couldn't be seen also hasn't hmr. + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('full-reload encodeURI path', async () => { await page.goto( viteTestUrl + '/unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', @@ -306,7 +307,10 @@ if (!isBuild) { 'title2', ) }) + } + // TODO css hmr + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('CSS update preserves query params', async () => { await page.goto(viteTestUrl) @@ -336,7 +340,9 @@ if (!isBuild) { expect((await page.$$('link')).length).toBe(1) }) + } + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('not loaded dynamic import', async () => { await page.goto(viteTestUrl + '/counter/index.html', { waitUntil: 'load', From 4d51f1d723c12a6146843fee1738adeb7859b9d8 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 30 Apr 2025 16:54:39 +0800 Subject: [PATCH 56/76] chore: skip rolldown hmr for outside module graph --- packages/vite/src/node/build.ts | 5 +++ playground/hmr/__tests__/hmr.spec.ts | 50 ++++++++++++++++------------ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index bec8cffe54ba8c..c03af87d628d79 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -993,6 +993,11 @@ async function buildEnvironment( } server.watcher.on('change', async (file) => { + // The playground/hmr test `plugin hmr remove custom events` need to skip the change of unused files. + if (!bundle!.watchFiles.includes(file)) { + return + } + const startTime = Date.now() const hmrOutput = (await bundle!.generateHmrPatch([file]))! // TODO(underfin): rebuild at first could be work. diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 21e1290c853f53..88c9b06e5bac57 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -277,6 +277,9 @@ if (!isBuild) { await untilUpdated(() => el.textContent(), 'edited') }) + // TODO(underfin): the customFile tests maybe make test hang. + // The `customFile.js` is not inside module graph, it couldn't be seen also hasn't hmr. + // Here only trigger hmr events. test('plugin hmr remove custom events', async () => { const el = await page.$('.toRemove') editFile('customFile.js', (code) => code.replace('custom', 'edited')) @@ -342,6 +345,7 @@ if (!isBuild) { }) } + // TODO(underfin): recheck it if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { test('not loaded dynamic import', async () => { await page.goto(viteTestUrl + '/counter/index.html', { @@ -378,33 +382,35 @@ if (!isBuild) { btn = await page.$('button') expect(await btn.textContent()).toBe('Counter 1') }) + } - // #2255 - test('importing reloaded', async () => { - await page.goto(viteTestUrl) - const outputEle = await page.$('.importing-reloaded') - const getOutput = () => { - return outputEle.innerHTML() - } + // #2255 + test('importing reloaded', async () => { + await page.goto(viteTestUrl) + const outputEle = await page.$('.importing-reloaded') + const getOutput = () => { + return outputEle.innerHTML() + } - await untilUpdated(getOutput, ['a.js: a0', 'b.js: b0,a0'].join('
')) + await untilUpdated(getOutput, ['a.js: a0', 'b.js: b0,a0'].join('
')) - editFile('importing-updated/a.js', (code) => code.replace("'a0'", "'a1'")) - await untilUpdated( - getOutput, - ['a.js: a0', 'b.js: b0,a0', 'a.js: a1'].join('
'), - ) + editFile('importing-updated/a.js', (code) => code.replace("'a0'", "'a1'")) + await untilUpdated( + getOutput, + ['a.js: a0', 'b.js: b0,a0', 'a.js: a1'].join('
'), + ) - editFile('importing-updated/b.js', (code) => - code.replace('`b0,${a}`', '`b1,${a}`'), - ) - // note that "a.js: a1" should not happen twice after "b.js: b0,a0'" - await untilUpdated( - getOutput, - ['a.js: a0', 'b.js: b0,a0', 'a.js: a1', 'b.js: b1,a1'].join('
'), - ) - }) + editFile('importing-updated/b.js', (code) => + code.replace('`b0,${a}`', '`b1,${a}`'), + ) + // note that "a.js: a1" should not happen twice after "b.js: b0,a0'" + await untilUpdated( + getOutput, + ['a.js: a0', 'b.js: b0,a0', 'a.js: a1', 'b.js: b1,a1'].join('
'), + ) + }) + if (!process.env.VITE_TEST_FULL_BUNDLE_MODE) { describe('acceptExports', () => { const HOT_UPDATED = /hot updated/ const CONNECTED = /connected/ From 955a7ab86faaa766bbce3fe2f4f7c267f9217127 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 7 May 2025 15:35:42 +0800 Subject: [PATCH 57/76] chore: fix lint --- packages/vite/src/node/build.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index c03af87d628d79..390a5b4cec5ef8 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -587,7 +587,7 @@ function resolveConfigToBuild( **/ async function buildEnvironment( environment: BuildEnvironment, - server?: ViteDevServer + server?: ViteDevServer, ): Promise { const { root, packageCache, experimental, command } = environment.config const options = environment.config.build @@ -874,6 +874,7 @@ async function buildEnvironment( resolvedOutDirs, emptyOutDir, environment.config.cacheDir, + !!experimental.fullBundleMode, ) const { watch } = await import('rolldown') @@ -946,7 +947,6 @@ async function buildEnvironment( if (server) { async function handleHmrOutput(hmrOutput: any, file: string) { - // @ts-expect-error Need to upgrade rolldown if (hmrOutput.fullReload) { if (!hmrOutput.firstInvalidatedBy) { await build() @@ -970,7 +970,7 @@ async function buildEnvironment( if (hmrOutput.patch) { const url = `${Date.now()}.js` server!.memoryFiles[url] = hmrOutput.patch - const updates = hmrOutput.hmrBoundaries.map((boundary) => { + const updates = hmrOutput.hmrBoundaries.map((boundary: any) => { return { type: 'js-update', url, @@ -1775,7 +1775,7 @@ export interface ViteBuilder { buildApp(server?: ViteDevServer): Promise build( environment: BuildEnvironment, - server?: ViteDevServer + server?: ViteDevServer, ): Promise } From 930092e8d600a2da8fb23daab7edce606a1dd739 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 7 May 2025 15:46:53 +0800 Subject: [PATCH 58/76] fix: disable treeshake becasue the minify is disabled --- packages/vite/src/node/build.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 390a5b4cec5ef8..9c408930b81701 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -682,6 +682,9 @@ async function buildEnvironment( } : false, }, + treeshake: experimental.fullBundleMode + ? false + : options.rollupOptions.treeshake, } /** From 797676834f4f88ab6cdf0ed7da7191e7d267605f Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 8 May 2025 10:45:38 +0800 Subject: [PATCH 59/76] fix: htmlFallbackMiddleware should check fullBundleMode memoryFiles --- packages/vite/src/node/preview.ts | 4 +++- packages/vite/src/node/server/index.ts | 9 ++++++++- .../src/node/server/middlewares/htmlFallback.ts | 14 ++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index 184d96bc53c7b4..e774c94c718283 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -246,7 +246,9 @@ export async function preview( // html fallback if (config.appType === 'spa' || config.appType === 'mpa') { - app.use(htmlFallbackMiddleware(distDir, config.appType === 'spa')) + app.use( + htmlFallbackMiddleware(distDir, config.appType === 'spa', false, {}), + ) } // apply post server hooks from plugins diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 0370566f832ad1..72c2b1c3eeb1c6 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -939,7 +939,14 @@ export async function _createServer( // html fallback if (config.appType === 'spa' || config.appType === 'mpa') { - middlewares.use(htmlFallbackMiddleware(root, config.appType === 'spa')) + middlewares.use( + htmlFallbackMiddleware( + root, + config.appType === 'spa', + !!config.experimental.fullBundleMode, + server.memoryFiles, + ), + ) } // run post config hooks diff --git a/packages/vite/src/node/server/middlewares/htmlFallback.ts b/packages/vite/src/node/server/middlewares/htmlFallback.ts index b61b44bf061180..9796f06d4f51b9 100644 --- a/packages/vite/src/node/server/middlewares/htmlFallback.ts +++ b/packages/vite/src/node/server/middlewares/htmlFallback.ts @@ -9,6 +9,8 @@ const debug = createDebugger('vite:html-fallback') export function htmlFallbackMiddleware( root: string, spaFallback: boolean, + fullBundleMode: boolean, + memoryFiles: Record, ): Connect.NextHandleFunction { // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...` return function viteHtmlFallbackMiddleware(req, _res, next) { @@ -31,6 +33,12 @@ export function htmlFallbackMiddleware( const url = cleanUrl(req.url!) const pathname = decodeURIComponent(url) + function checkFileExists(htmlPathName: string) { + return fullBundleMode + ? memoryFiles[htmlPathName] + : fs.existsSync(path.join(root, htmlPathName)) + } + // .html files are not handled by serveStaticMiddleware // so we need to check if the file exists if (pathname.endsWith('.html')) { @@ -43,8 +51,7 @@ export function htmlFallbackMiddleware( } // trailing slash should check for fallback index.html else if (pathname.endsWith('/')) { - const filePath = path.join(root, pathname, 'index.html') - if (fs.existsSync(filePath)) { + if (checkFileExists(path.join(pathname, 'index.html'))) { const newUrl = url + 'index.html' debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`) req.url = newUrl @@ -53,8 +60,7 @@ export function htmlFallbackMiddleware( } // non-trailing slash should check for fallback .html else { - const filePath = path.join(root, pathname + '.html') - if (fs.existsSync(filePath)) { + if (checkFileExists(pathname + '.html')) { const newUrl = url + '.html' debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`) req.url = newUrl From 09e498e9c7c34bc57bf9428438f70a0a456dc9d9 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 9 May 2025 16:31:07 +0800 Subject: [PATCH 60/76] fix: patch rolldown module runner changes --- packages/vite/src/client/hmrModuleRunner.ts | 44 +++++---------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 500a9be140e3bb..927060376db245 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -25,41 +25,9 @@ if (__FULL_BUNDLE_MODE__) { // } - registerModule( - id: string, - esmExportGettersOrCjsExports: Record, - meta: { cjs?: boolean } = {}, - ) { - const exports = {} - Object.keys(esmExportGettersOrCjsExports).forEach((key) => { - if ( - Object.prototype.hasOwnProperty.call( - esmExportGettersOrCjsExports, - key, - ) - ) { - if (meta.cjs) { - Object.defineProperty(exports, key, { - enumerable: true, - get: () => esmExportGettersOrCjsExports[key], - }) - } else { - Object.defineProperty(exports, key, { - enumerable: true, - get: esmExportGettersOrCjsExports[key], - }) - } - } - }) - if (this.modules[id]) { - this.modules[id] = { - exports, - } - } else { - // If the module is not in the cache, we need to register it. - this.modules[id] = { - exports, - } + registerModule(id: string, exports: Record unknown>) { + this.modules[id] = { + exports, } } @@ -81,6 +49,12 @@ if (__FULL_BUNDLE_MODE__) { createCjsInitializer = (cb, mod) => () => ( mod || cb((mod = { exports: {} }).exports, mod), mod.exports ) + // @ts-expect-error it is exits + __toESM = __toESM + // @ts-expect-error it is exits + __toCommonJS = __toCommonJS + // @ts-expect-error it is exits + __export = __export } // @ts-expect-error __rolldown_runtime__ From bac444e3e06a3dbd679ad04e94bef49d3d9ef4eb Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 9 May 2025 16:31:38 +0800 Subject: [PATCH 61/76] feat: add hmr hot getExports --- packages/vite/src/shared/hmr.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/vite/src/shared/hmr.ts b/packages/vite/src/shared/hmr.ts index 7c1255e85e16fe..17f7dbc12ba1cf 100644 --- a/packages/vite/src/shared/hmr.ts +++ b/packages/vite/src/shared/hmr.ts @@ -155,6 +155,15 @@ export class HMRContext implements ViteHotContext { this.hmrClient.send({ type: 'custom', event, data }) } + async getExports(): Promise { + return this.fullBundleMode + ? Promise.resolve().then(() => + // @ts-expect-error __rolldown_runtime__ + __rolldown_runtime__.loadExports(this.ownerPath), + ) + : import(this.ownerPath) + } + private acceptDeps( deps: string[], callback: HotCallback['fn'] = () => {}, From 7ade2bc00e545d92228efcb44000130434e940c2 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 12 May 2025 15:23:58 +0800 Subject: [PATCH 62/76] chore: patch rolldown module runner changes --- packages/vite/src/client/hmrModuleRunner.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/client/hmrModuleRunner.ts b/packages/vite/src/client/hmrModuleRunner.ts index 927060376db245..2c9f95ec736a5c 100644 --- a/packages/vite/src/client/hmrModuleRunner.ts +++ b/packages/vite/src/client/hmrModuleRunner.ts @@ -25,10 +25,11 @@ if (__FULL_BUNDLE_MODE__) { // } - registerModule(id: string, exports: Record unknown>) { - this.modules[id] = { - exports, - } + registerModule( + id: string, + module: { exports: Record unknown> }, + ) { + this.modules[id] = module } loadExports(id: string) { From eb361caa1f8c102229dc8697f6285936003daa1d Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 12 May 2025 16:37:08 +0800 Subject: [PATCH 63/76] chore: run hmr at first --- packages/vite/src/node/build.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 9c408930b81701..ce5f139da9085f 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -1003,16 +1003,15 @@ async function buildEnvironment( const startTime = Date.now() const hmrOutput = (await bundle!.generateHmrPatch([file]))! - // TODO(underfin): rebuild at first could be work. + + await handleHmrOutput(hmrOutput, file) + if (hmrOutput.patch) { await build() logger.info( `${colors.green(`✓ rebuilt in ${displayTime(Date.now() - startTime)}`)}`, ) } - await handleHmrOutput(hmrOutput, file) - - // TODO(underfin): The invalidate case is failed because the hmrInvalidate is hang after rebuild at here . }) server.hot.on( 'vite:invalidate', From 467b118934bb1432d8c8c790a98ae4215490a0e4 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 13 May 2025 11:23:25 +0800 Subject: [PATCH 64/76] feat: debounce reBuild --- packages/vite/src/node/build.ts | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index ce5f139da9085f..e1349241992c96 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -995,27 +995,43 @@ async function buildEnvironment( } } + let debouncedBuild: NodeJS.Timeout | undefined + + function debounceBuild() { + cancelBuild() + debouncedBuild = setTimeout(async () => { + const startTime = Date.now() + await build() + logger.info( + `${colors.green(`✓ rebuilt in ${displayTime(Date.now() - startTime)}`)}`, + ) + }, 20) + } + + function cancelBuild() { + if (debouncedBuild) clearTimeout(debouncedBuild) + } + server.watcher.on('change', async (file) => { // The playground/hmr test `plugin hmr remove custom events` need to skip the change of unused files. if (!bundle!.watchFiles.includes(file)) { return } - const startTime = Date.now() const hmrOutput = (await bundle!.generateHmrPatch([file]))! await handleHmrOutput(hmrOutput, file) if (hmrOutput.patch) { - await build() - logger.info( - `${colors.green(`✓ rebuilt in ${displayTime(Date.now() - startTime)}`)}`, - ) + debounceBuild() } }) + server.hot.on( 'vite:invalidate', async ({ path: file, message, firstInvalidatedBy }) => { + // cancel the debounce build util the hmr invalidate is done. + cancelBuild() const hmrOutput = (await bundle!.hmrInvalidate( path.join(root, file), firstInvalidatedBy, @@ -1029,6 +1045,10 @@ async function buildEnvironment( { timestamp: true }, ) await handleHmrOutput(hmrOutput, file) + + if (hmrOutput.patch) { + debounceBuild() + } } } }, From bd44ee276e2ed924469c261c4a41f64106124a50 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 13 May 2025 11:24:50 +0800 Subject: [PATCH 65/76] chore: using await bundle.watchFiles --- packages/vite/src/node/build.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index e1349241992c96..7b2745cf577e43 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -931,7 +931,7 @@ async function buildEnvironment( if (server) { // watching the files - for (const file of bundle!.watchFiles) { + for (const file of await bundle!.watchFiles) { if (path.isAbsolute(file) && fs.existsSync(file)) { server.watcher.add(file) } @@ -1014,7 +1014,7 @@ async function buildEnvironment( server.watcher.on('change', async (file) => { // The playground/hmr test `plugin hmr remove custom events` need to skip the change of unused files. - if (!bundle!.watchFiles.includes(file)) { + if (!(await bundle!.watchFiles).includes(file)) { return } From 950428b631a09d363620cf92792d1c6d90c08741 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 13 May 2025 11:26:53 +0800 Subject: [PATCH 66/76] chore: add hmr html files to rollupOptions.input --- playground/hmr/vite.config.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/playground/hmr/vite.config.ts b/playground/hmr/vite.config.ts index 9ee8024ee2bf44..0e7522d4567c0c 100644 --- a/playground/hmr/vite.config.ts +++ b/playground/hmr/vite.config.ts @@ -13,6 +13,18 @@ export default defineConfig({ return false } }, + rollupOptions: { + input: [ + path.resolve(import.meta.dirname, 'accept-exports/dynamic-imports/index.html'), + path.resolve(import.meta.dirname, 'accept-exports/export-from/index.html'), + path.resolve(import.meta.dirname, 'accept-exports/main-accepted/index.html'), + path.resolve(import.meta.dirname, 'accept-exports/main-non-accepted/index.html'), + path.resolve(import.meta.dirname, 'accept-exports/side-effects/index.html'), + path.resolve(import.meta.dirname, 'accept-exports/star-imports/index.html'), + path.resolve(import.meta.dirname, 'accept-exports/unused-exports/index.html'), + path.resolve(import.meta.dirname, 'index.html'), + ] + } }, plugins: [ { From 9c7f704de91cbc0d62e867795170a94802ec0046 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 13 May 2025 16:14:23 +0800 Subject: [PATCH 67/76] fix: react plugin get module exports --- packages/vite/src/shared/hmr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/shared/hmr.ts b/packages/vite/src/shared/hmr.ts index 17f7dbc12ba1cf..5a054124e31a4a 100644 --- a/packages/vite/src/shared/hmr.ts +++ b/packages/vite/src/shared/hmr.ts @@ -155,13 +155,13 @@ export class HMRContext implements ViteHotContext { this.hmrClient.send({ type: 'custom', event, data }) } - async getExports(): Promise { + async getExports(url: string): Promise { return this.fullBundleMode ? Promise.resolve().then(() => // @ts-expect-error __rolldown_runtime__ __rolldown_runtime__.loadExports(this.ownerPath), ) - : import(this.ownerPath) + : import(url) } private acceptDeps( From 80f53dbe67d1fde78fe51fd4cc416d4085b4c386 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 15 May 2025 16:03:55 +0800 Subject: [PATCH 68/76] feat: add build moduleGraph --- packages/vite/src/node/build.ts | 5 + .../vite/src/node/server/buildModuleGraph.ts | 237 ++++++++++++++++++ packages/vite/src/node/server/hmr.ts | 7 +- packages/vite/src/node/server/index.ts | 14 +- 4 files changed, 256 insertions(+), 7 deletions(-) create mode 100644 packages/vite/src/node/server/buildModuleGraph.ts diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 7b2745cf577e43..ace69373593ed3 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -93,6 +93,7 @@ import type { RollupPluginHooks } from './typeUtils' import { buildOxcPlugin } from './plugins/oxc' import type { ViteDevServer } from './server' import { getHmrImplement } from './plugins/clientInjections' +import { buildModuleGraphPlugin } from './server/buildModuleGraph' export interface BuildEnvironmentOptions { /** @@ -648,6 +649,10 @@ async function buildEnvironment( injectEnvironmentToHooks(environment, chunkMetadataMap, p), ) + if (server) { + plugins.push(buildModuleGraphPlugin(server)) + } + const rollupOptions: RolldownOptions = { // preserveEntrySignatures: ssr // ? 'allow-extension' diff --git a/packages/vite/src/node/server/buildModuleGraph.ts b/packages/vite/src/node/server/buildModuleGraph.ts new file mode 100644 index 00000000000000..9a51cdb7f1f71a --- /dev/null +++ b/packages/vite/src/node/server/buildModuleGraph.ts @@ -0,0 +1,237 @@ +import type { ModuleInfo } from 'rolldown' +import type { ViteDevServer } from ".."; +import type { Plugin } from '../plugin' +import { cleanUrl } from "../../shared/utils"; +import type { TransformResult } from './transformRequest' +import type { EnvironmentModuleNode, ResolvedUrl } from "./moduleGraph" + +export class BuildModuleNode { + _id: string + _file: string + _importers = new Set + constructor(id: string, file: string) { + this._id = id + this._file = file + } + + // TODO using id also could be work + get url(): string { + return this._id + } + set url(_value: string) { + throw new Error("BuildModuleNode set url is not support"); + } + + get id(): string | null { + return this._id + } + set id(_value: string | null) { + throw new Error("BuildModuleNode set id is not support"); + } + get file(): string | null { + return this._file + } + set file(_value: string | null) { + throw new Error("BuildModuleNode set file is not support"); + } + // eslint-disable-next-line @typescript-eslint/class-literal-property-style + get type(): 'js' | 'css' { + return 'js' + } + // `info` needs special care as it's defined as a proxy in `pluginContainer`, + // so we also merge it as a proxy too + get info(): ModuleInfo | undefined { + throw new Error("BuildModuleNode get info is not support"); + } + get meta(): Record | undefined { + throw new Error("BuildModuleNode get meta is not support"); + } + get importers(): Set { + throw this._importers; + } + set importers(importers: Set) { + this._importers = importers; + } + get clientImportedModules(): Set { + throw new Error("BuildModuleNode get clientImportedModules is not support"); + } + get ssrImportedModules(): Set { + throw new Error("BuildModuleNode get ssrImportedModules is not support"); + } + get importedModules(): Set { + throw new Error("BuildModuleNode get importedModules is not support"); + } + get acceptedHmrDeps(): Set { + throw new Error("BuildModuleNode get acceptedHmrDeps is not support"); + } + get acceptedHmrExports(): Set | null { + throw new Error("BuildModuleNode get acceptedHmrExports is not support"); + } + get importedBindings(): Map> | null { + throw new Error("BuildModuleNode get importedBindings is not support"); + } + get isSelfAccepting(): boolean | undefined { + throw new Error("BuildModuleNode get isSelfAccepting is not support"); + } + get transformResult(): TransformResult | null { + throw new Error("BuildModuleNode get transformResult is not support"); + } + set transformResult(_value: TransformResult | null) { + throw new Error("BuildModuleNode set transformResult is not support"); + } + get ssrTransformResult(): TransformResult | null { + throw new Error("BuildModuleNode get ssrTransformResult is not support"); + } + set ssrTransformResult(_value: TransformResult | null) { + throw new Error("BuildModuleNode set ssrTransformResult is not support"); + } + get ssrModule(): Record | null { + throw new Error("BuildModuleNode get ssrModule is not support"); + } + get ssrError(): Error | null { + throw new Error("BuildModuleNode get ssrError is not support"); + } + get lastHMRTimestamp(): number { + throw new Error("BuildModuleNode get lastHMRTimestamp is not support"); + } + set lastHMRTimestamp(_value: number) { + throw new Error("BuildModuleNode set lastHMRTimestamp is not support"); + } + get lastInvalidationTimestamp(): number { + throw new Error("BuildModuleNode get lastInvalidationTimestamp is not support"); + } + get invalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined { + throw new Error("BuildModuleNode get invalidationState is not support"); + } + get ssrInvalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined { + throw new Error("BuildModuleNode get ssrInvalidationState is not support"); + } +} + + +export class BuildModuleGraph { + idToModuleMap = new Map() + fileToModulesMap = new Map>() + + // eslint-disable-next-line @typescript-eslint/no-empty-function + constructor() {} + + getModuleById(id: string): BuildModuleNode | undefined { + return this.idToModuleMap.get(id); + } + + async getModuleByUrl( + _url: string, + _ssr?: boolean, + ): Promise { + throw new Error("BuildModuleGraph getModuleByUrl is not support"); + } + + getModulesByFile(file: string): Set | undefined { + return this.fileToModulesMap.get(file); + } + + onFileChange(_file: string): void { + throw new Error("BuildModuleGraph onFileChange is not support"); + } + + onFileDelete(_file: string): void { + throw new Error("BuildModuleGraph onFileDelete is not support"); + } + + + invalidateModule( + _mod: BuildModuleNode, + _seen = new Set(), + _timestamp: number = Date.now(), + _isHmr: boolean = false, + /** @internal */ + _softInvalidate = false, + ): void { + throw new Error("BuildModuleGraph invalidateModule is not support"); + } + + invalidateAll(): void { + throw new Error("BuildModuleGraph invalidateAll is not support"); + } + + + async ensureEntryFromUrl( + _rawUrl: string, + _ssr?: boolean, + _setIsSelfAccepting = true, + ): Promise { + throw new Error("BuildModuleGraph invalidateAll is not support"); + } + + createFileOnlyEntry(_file: string): BuildModuleNode { + throw new Error("BuildModuleGraph createFileOnlyEntry is not support"); + } + + async resolveUrl(_url: string, _ssr?: boolean): Promise { + throw new Error("BuildModuleGraph resolveUrl is not support"); + } + + updateModuleTransformResult( + _mod: BuildModuleNode, + _result: TransformResult | null, + _ssr?: boolean, + ): void { + throw new Error("BuildModuleGraph updateModuleTransformResult is not support"); + } + + getModuleByEtag(_etag: string): BuildModuleNode | undefined { + throw new Error("BuildModuleGraph getModuleByEtag is not support"); + } + + getBackwardCompatibleBrowserModuleNode( + _clientModule: EnvironmentModuleNode, + ): BuildModuleNode { + throw new Error("BuildModuleGraph getBackwardCompatibleBrowserModuleNode is not support"); + } + + getBackwardCompatibleServerModuleNode( + _ssrModule: EnvironmentModuleNode, + ): BuildModuleNode { + throw new Error("BuildModuleGraph getBackwardCompatibleServerModuleNode is not support"); + } + + getBackwardCompatibleModuleNode(_mod: EnvironmentModuleNode): BuildModuleNode { + throw new Error("BuildModuleGraph getBackwardCompatibleModuleNode is not support"); + } + + getBackwardCompatibleModuleNodeDual( + _clientModule?: EnvironmentModuleNode, + _ssrModule?: EnvironmentModuleNode, + ): BuildModuleNode { + throw new Error("BuildModuleGraph getBackwardCompatibleModuleNodeDual is not support"); + } +} + +export function buildModuleGraphPlugin(server: ViteDevServer): Plugin { + return { + name: 'build-module-graph-plugin', + renderStart() { + const moduleGraph = server.moduleGraph as BuildModuleGraph + for (const id of this.getModuleIds()) { + // const moduleInfo = this.getModuleInfo(id)! + const file = cleanUrl(id) + const moduleNode = new BuildModuleNode(id, file); + moduleGraph.idToModuleMap.set(id, moduleNode) + + moduleGraph.fileToModulesMap.set(file, new Set([moduleNode])) + let fileMappedModules = moduleGraph.fileToModulesMap.get(file) + if (!fileMappedModules) { + fileMappedModules = new Set() + moduleGraph.fileToModulesMap.set(file, fileMappedModules) + } + fileMappedModules.add(moduleNode) + } + for (const id of this.getModuleIds()) { + const moduleInfo = this.getModuleInfo(id)! + const moduleNode = moduleGraph.idToModuleMap.get(id); + moduleNode!.importers = new Set(moduleInfo.importers.map((importerId) => moduleGraph.idToModuleMap.get(importerId)!)) + } + } + } +} \ No newline at end of file diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 4f3154ad615547..03e2232b65461b 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -27,6 +27,7 @@ import type { EnvironmentModuleNode } from './moduleGraph' import type { ModuleNode } from './mixedModuleGraph' import type { DevEnvironment } from './environment' import { prepareError } from './middlewares/error' +import type { BuildModuleNode } from './buildModuleGraph' import type { HttpServer } from '.' import { restartServerWithUrls } from '.' @@ -64,7 +65,7 @@ export interface HotUpdateOptions { export interface HmrContext { file: string timestamp: number - modules: Array + modules: Array read: () => string | Promise server: ViteDevServer } @@ -453,7 +454,9 @@ export async function handleHMRUpdate( hotMap.set(environment, { options }) } - const mixedMods = new Set(mixedModuleGraph.getModulesByFile(file)) + const mixedMods = new Set( + mixedModuleGraph.getModulesByFile(file), + ) const mixedHmrContext: HmrContext = { ...contextMeta, diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 72c2b1c3eeb1c6..610be403f4ce88 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -101,6 +101,7 @@ import type { DevEnvironment } from './environment' import { hostCheckMiddleware } from './middlewares/hostCheck' import { memoryFilesMiddleware } from './middlewares/memoryFiles' import { rejectInvalidRequestMiddleware } from './middlewares/rejectInvalidRequest' +import { BuildModuleGraph } from './buildModuleGraph' export interface ServerOptions extends CommonServerOptions { /** @@ -295,7 +296,7 @@ export interface ViteDevServer { * Module graph that tracks the import relationships, url to file mapping * and hmr state. */ - moduleGraph: ModuleGraph + moduleGraph: ModuleGraph | BuildModuleGraph /** * The resolved urls Vite prints on the CLI (URL-encoded). Returns `null` * in middleware mode or if the server is not listening on any port. @@ -531,10 +532,12 @@ export async function _createServer( // Backward compatibility - let moduleGraph = new ModuleGraph({ - client: () => environments.client.moduleGraph, - ssr: () => environments.ssr.moduleGraph, - }) + let moduleGraph = config.experimental.fullBundleMode + ? new BuildModuleGraph() + : new ModuleGraph({ + client: () => environments.client.moduleGraph, + ssr: () => environments.ssr.moduleGraph, + }) const pluginContainer = createPluginContainer(environments) const closeHttpServer = createServerCloseFn(httpServer) @@ -797,6 +800,7 @@ export async function _createServer( } } + // TODO(underfin): handle this const onFileAddUnlink = async (file: string, isUnlink: boolean) => { file = normalizePath(file) reloadOnTsconfigChange(server, file) From acd2c411329b3c0599f566e80c3b556aeed6a69a Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 16 May 2025 17:56:57 +0800 Subject: [PATCH 69/76] feat: make sourcemap work at full bundle mode dev --- packages/vite/src/node/build.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index ace69373593ed3..36e961f67bc56a 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -975,13 +975,16 @@ async function buildEnvironment( return } - if (hmrOutput.patch) { - const url = `${Date.now()}.js` - server!.memoryFiles[url] = hmrOutput.patch + if (hmrOutput.code) { + server!.memoryFiles[hmrOutput.filename] = hmrOutput.code + if (hmrOutput.sourcemap) { + server!.memoryFiles[hmrOutput.sourcemapFilename] = + hmrOutput.sourcemap + } const updates = hmrOutput.hmrBoundaries.map((boundary: any) => { return { type: 'js-update', - url, + url: hmrOutput.filename, path: boundary.boundary, acceptedPath: boundary.acceptedVia, firstInvalidatedBy: hmrOutput.firstInvalidatedBy, @@ -1027,7 +1030,7 @@ async function buildEnvironment( await handleHmrOutput(hmrOutput, file) - if (hmrOutput.patch) { + if (hmrOutput.code) { debounceBuild() } }) @@ -1051,7 +1054,7 @@ async function buildEnvironment( ) await handleHmrOutput(hmrOutput, file) - if (hmrOutput.patch) { + if (hmrOutput.code) { debounceBuild() } } From f03bc472f4e1624c94c7184eb404e29251f33eaf Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 19 May 2025 16:36:27 +0800 Subject: [PATCH 70/76] chore: disable BuildModuleGraph --- packages/vite/src/node/build.ts | 8 ++++---- packages/vite/src/node/server/index.ts | 16 +++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 36e961f67bc56a..a3b0460e89347d 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -93,7 +93,7 @@ import type { RollupPluginHooks } from './typeUtils' import { buildOxcPlugin } from './plugins/oxc' import type { ViteDevServer } from './server' import { getHmrImplement } from './plugins/clientInjections' -import { buildModuleGraphPlugin } from './server/buildModuleGraph' +// import { buildModuleGraphPlugin } from './server/buildModuleGraph' export interface BuildEnvironmentOptions { /** @@ -649,9 +649,9 @@ async function buildEnvironment( injectEnvironmentToHooks(environment, chunkMetadataMap, p), ) - if (server) { - plugins.push(buildModuleGraphPlugin(server)) - } + // if (server) { + // plugins.push(buildModuleGraphPlugin(server)) + // } const rollupOptions: RolldownOptions = { // preserveEntrySignatures: ssr diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 610be403f4ce88..d86f591eed47d4 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -101,7 +101,7 @@ import type { DevEnvironment } from './environment' import { hostCheckMiddleware } from './middlewares/hostCheck' import { memoryFilesMiddleware } from './middlewares/memoryFiles' import { rejectInvalidRequestMiddleware } from './middlewares/rejectInvalidRequest' -import { BuildModuleGraph } from './buildModuleGraph' +import type { BuildModuleGraph } from './buildModuleGraph' export interface ServerOptions extends CommonServerOptions { /** @@ -532,12 +532,14 @@ export async function _createServer( // Backward compatibility - let moduleGraph = config.experimental.fullBundleMode - ? new BuildModuleGraph() - : new ModuleGraph({ - client: () => environments.client.moduleGraph, - ssr: () => environments.ssr.moduleGraph, - }) + let moduleGraph = + // config.experimental.fullBundleMode + // ? new BuildModuleGraph() + // : + new ModuleGraph({ + client: () => environments.client.moduleGraph, + ssr: () => environments.ssr.moduleGraph, + }) const pluginContainer = createPluginContainer(environments) const closeHttpServer = createServerCloseFn(httpServer) From bdb70a966cb530d924fcbd3b8b46d5c755a4fb08 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 19 May 2025 16:40:54 +0800 Subject: [PATCH 71/76] chore: rebase --- pnpm-lock.yaml | 89 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50e29989b42601..4a7000f4afc406 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -138,7 +138,7 @@ importers: version: link:packages/vite vitest: specifier: ^3.1.3 - version: 3.1.3(@types/debug@4.1.12)(@types/node@22.15.18) + version: 3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass-embedded@1.89.0(source-map-js@1.2.1))(sass@1.89.0)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) docs: devDependencies: @@ -7534,11 +7534,51 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite@6.2.6: + resolution: {integrity: sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitepress-plugin-group-icons@1.5.2: resolution: {integrity: sha512-zen07KxZ83y3eecou4EraaEgwIriwHaB5Q0cHAmS4yO1UZEQvbljTylHPqiJ7LNkV39U8VehfcyquAJXg/26LA==} - vitepress-plugin-llms@1.1.0: - resolution: {integrity: sha512-nb7bG/lBDihlcFTzqxRxQIyzeBWQW9F6OwuUWQ7PFUNK5kVbybxXGISU4wvAV8osQmfrD9xNIGJQfuOLj5CzHg==} + vitepress-plugin-llms@1.1.4: + resolution: {integrity: sha512-/+xrVWpd03B5bGsWN+R+TN0P3kqM/jaDWiddmpFxPuw7vJdoE9mAqAZx1ywzhHEeBCepDMt069l1VRyitmEU6A==} vitepress@1.6.3: resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} @@ -9927,17 +9967,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 -<<<<<<< HEAD - '@vitest/mocker@3.1.1(vite@packages+vite)': -======= - '@vitest/mocker@3.0.8(vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.29.3)(sass-embedded@1.85.1(source-map-js@1.2.1))(sass@1.85.1)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': ->>>>>>> a2d094f19 (chore: make test work, avoid overrides vitest vite) + '@vitest/mocker@3.1.3(vite@6.2.6(@types/node@22.15.18)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass-embedded@1.89.0(source-map-js@1.2.1))(sass@1.89.0)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.1.3 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + vite: 6.2.6(@types/node@22.15.18)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass-embedded@1.89.0(source-map-js@1.2.1))(sass@1.89.0)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) '@vitest/pretty-format@3.1.3': dependencies: @@ -13849,24 +13885,24 @@ snapshots: transitivePeerDependencies: - supports-color - vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): + vite@6.2.6(@types/node@22.15.18)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass-embedded@1.89.0(source-map-js@1.2.1))(sass@1.89.0)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: esbuild: 0.25.0 postcss: 8.5.3 rollup: 4.34.9 optionalDependencies: - '@types/node': 22.13.6 + '@types/node': 22.15.18 fsevents: 2.3.3 jiti: 2.4.2 less: 4.3.0 - lightningcss: 1.29.3 - sass: 1.86.3 - sass-embedded: 1.86.3(source-map-js@1.2.1) + lightningcss: 1.30.1 + sass: 1.89.0 + sass-embedded: 1.89.0(source-map-js@1.2.1) stylus: 0.64.0 sugarss: 5.0.0(postcss@8.5.3) terser: 5.39.0 - tsx: 4.19.3 - yaml: 2.7.0 + tsx: 4.19.4 + yaml: 2.8.0 vitepress-plugin-group-icons@1.5.2: dependencies: @@ -13933,16 +13969,15 @@ snapshots: - typescript - universal-cookie -<<<<<<< HEAD - vitest@3.1.1(@types/debug@4.1.12)(@types/node@22.13.6): + vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass-embedded@1.89.0(source-map-js@1.2.1))(sass@1.89.0)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: - '@vitest/expect': 3.1.1 - '@vitest/mocker': 3.1.1(vite@6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - '@vitest/pretty-format': 3.1.1 - '@vitest/runner': 3.1.1 - '@vitest/snapshot': 3.1.1 - '@vitest/spy': 3.1.1 - '@vitest/utils': 3.1.1 + '@vitest/expect': 3.1.3 + '@vitest/mocker': 3.1.3(vite@6.2.6(@types/node@22.15.18)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass-embedded@1.89.0(source-map-js@1.2.1))(sass@1.89.0)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@vitest/pretty-format': 3.1.3 + '@vitest/runner': 3.1.3 + '@vitest/snapshot': 3.1.3 + '@vitest/spy': 3.1.3 + '@vitest/utils': 3.1.3 chai: 5.2.0 debug: 4.4.0 expect-type: 1.2.1 @@ -13954,8 +13989,8 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.6(@types/node@22.13.6)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.3)(sass-embedded@1.86.3(source-map-js@1.2.1))(sass@1.86.3)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-node: 3.1.1 + vite: 6.2.6(@types/node@22.15.18)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass-embedded@1.89.0(source-map-js@1.2.1))(sass@1.89.0)(stylus@0.64.0)(sugarss@5.0.0(postcss@8.5.3))(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite-node: 3.1.3 why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 From f7dc0b7981d3944ffff35cf9f282f1499b77b7b9 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 20 May 2025 14:33:06 +0800 Subject: [PATCH 72/76] fix: aovid build oxc pluin enable jsx option --- packages/vite/src/node/plugins/oxc.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vite/src/node/plugins/oxc.ts b/packages/vite/src/node/plugins/oxc.ts index e5ee70155d90e5..f3bb0c2d47b9f5 100644 --- a/packages/vite/src/node/plugins/oxc.ts +++ b/packages/vite/src/node/plugins/oxc.ts @@ -487,6 +487,7 @@ export function resolveOxcTranspileOptions( sourceType: format === 'es' ? 'module' : 'script', target: target || undefined, sourcemap: !!config.build.sourcemap, + jsx: undefined, } } From 9f56cb894658b054b2ff97be40084cfd95ed2d18 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 21 May 2025 18:04:57 +0800 Subject: [PATCH 73/76] fix: eanble native nativeModulePreloadPolyfillPlugin --- packages/vite/src/node/plugins/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 4f7b9416928a28..7ceb1a2035fde9 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -88,10 +88,7 @@ export async function resolvePlugins( ? perEnvironmentPlugin( 'native:modulepreload-polyfill', (environment) => { - if ( - config.command !== 'build' || - environment.config.consumer !== 'client' - ) + if (!isBuild || environment.config.consumer !== 'client') return false return nativeModulePreloadPolyfillPlugin() }, From 01090e054b1a143e41b5e944553211cc35cf9141 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 21 May 2025 18:06:28 +0800 Subject: [PATCH 74/76] chore: fix fmt --- packages/vite/src/node/cli.ts | 16 +- .../vite/src/node/server/buildModuleGraph.ts | 311 +++++++++--------- 2 files changed, 171 insertions(+), 156 deletions(-) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 1f739b5713d113..0fd1288500c95d 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -270,14 +270,14 @@ cli const hasExistingLogs = process.stdout.bytesWritten > 0 || process.stderr.bytesWritten > 0 - info( - `\n ${colors.green( - `${colors.bold('ROLLDOWN-VITE')} v${VERSION}`, - )}${modeString} ${startupDurationString}\n`, - { - clear: !hasExistingLogs, - }, - ) + info( + `\n ${colors.green( + `${colors.bold('ROLLDOWN-VITE')} v${VERSION}`, + )}${modeString} ${startupDurationString}\n`, + { + clear: !hasExistingLogs, + }, + ) server.printUrls() const customShortcuts: CLIShortcut[] = [] diff --git a/packages/vite/src/node/server/buildModuleGraph.ts b/packages/vite/src/node/server/buildModuleGraph.ts index 9a51cdb7f1f71a..82af0de6053911 100644 --- a/packages/vite/src/node/server/buildModuleGraph.ts +++ b/packages/vite/src/node/server/buildModuleGraph.ts @@ -1,113 +1,114 @@ import type { ModuleInfo } from 'rolldown' -import type { ViteDevServer } from ".."; +import type { ViteDevServer } from '..' import type { Plugin } from '../plugin' -import { cleanUrl } from "../../shared/utils"; +import { cleanUrl } from '../../shared/utils' import type { TransformResult } from './transformRequest' -import type { EnvironmentModuleNode, ResolvedUrl } from "./moduleGraph" +import type { EnvironmentModuleNode, ResolvedUrl } from './moduleGraph' export class BuildModuleNode { - _id: string - _file: string - _importers = new Set - constructor(id: string, file: string) { - this._id = id - this._file = file - } - - // TODO using id also could be work - get url(): string { - return this._id - } - set url(_value: string) { - throw new Error("BuildModuleNode set url is not support"); - } - - get id(): string | null { - return this._id - } - set id(_value: string | null) { - throw new Error("BuildModuleNode set id is not support"); - } - get file(): string | null { - return this._file - } - set file(_value: string | null) { - throw new Error("BuildModuleNode set file is not support"); - } - // eslint-disable-next-line @typescript-eslint/class-literal-property-style - get type(): 'js' | 'css' { - return 'js' - } - // `info` needs special care as it's defined as a proxy in `pluginContainer`, - // so we also merge it as a proxy too - get info(): ModuleInfo | undefined { - throw new Error("BuildModuleNode get info is not support"); - } - get meta(): Record | undefined { - throw new Error("BuildModuleNode get meta is not support"); - } - get importers(): Set { - throw this._importers; - } - set importers(importers: Set) { - this._importers = importers; - } - get clientImportedModules(): Set { - throw new Error("BuildModuleNode get clientImportedModules is not support"); - } - get ssrImportedModules(): Set { - throw new Error("BuildModuleNode get ssrImportedModules is not support"); - } - get importedModules(): Set { - throw new Error("BuildModuleNode get importedModules is not support"); - } - get acceptedHmrDeps(): Set { - throw new Error("BuildModuleNode get acceptedHmrDeps is not support"); - } - get acceptedHmrExports(): Set | null { - throw new Error("BuildModuleNode get acceptedHmrExports is not support"); - } - get importedBindings(): Map> | null { - throw new Error("BuildModuleNode get importedBindings is not support"); - } - get isSelfAccepting(): boolean | undefined { - throw new Error("BuildModuleNode get isSelfAccepting is not support"); - } - get transformResult(): TransformResult | null { - throw new Error("BuildModuleNode get transformResult is not support"); - } - set transformResult(_value: TransformResult | null) { - throw new Error("BuildModuleNode set transformResult is not support"); - } - get ssrTransformResult(): TransformResult | null { - throw new Error("BuildModuleNode get ssrTransformResult is not support"); - } - set ssrTransformResult(_value: TransformResult | null) { - throw new Error("BuildModuleNode set ssrTransformResult is not support"); - } - get ssrModule(): Record | null { - throw new Error("BuildModuleNode get ssrModule is not support"); - } - get ssrError(): Error | null { - throw new Error("BuildModuleNode get ssrError is not support"); - } - get lastHMRTimestamp(): number { - throw new Error("BuildModuleNode get lastHMRTimestamp is not support"); - } - set lastHMRTimestamp(_value: number) { - throw new Error("BuildModuleNode set lastHMRTimestamp is not support"); - } - get lastInvalidationTimestamp(): number { - throw new Error("BuildModuleNode get lastInvalidationTimestamp is not support"); - } - get invalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined { - throw new Error("BuildModuleNode get invalidationState is not support"); - } - get ssrInvalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined { - throw new Error("BuildModuleNode get ssrInvalidationState is not support"); - } + _id: string + _file: string + _importers = new Set() + constructor(id: string, file: string) { + this._id = id + this._file = file + } + + // TODO using id also could be work + get url(): string { + return this._id + } + set url(_value: string) { + throw new Error('BuildModuleNode set url is not support') + } + + get id(): string | null { + return this._id + } + set id(_value: string | null) { + throw new Error('BuildModuleNode set id is not support') + } + get file(): string | null { + return this._file + } + set file(_value: string | null) { + throw new Error('BuildModuleNode set file is not support') + } + // eslint-disable-next-line @typescript-eslint/class-literal-property-style + get type(): 'js' | 'css' { + return 'js' + } + // `info` needs special care as it's defined as a proxy in `pluginContainer`, + // so we also merge it as a proxy too + get info(): ModuleInfo | undefined { + throw new Error('BuildModuleNode get info is not support') + } + get meta(): Record | undefined { + throw new Error('BuildModuleNode get meta is not support') + } + get importers(): Set { + throw this._importers + } + set importers(importers: Set) { + this._importers = importers + } + get clientImportedModules(): Set { + throw new Error('BuildModuleNode get clientImportedModules is not support') + } + get ssrImportedModules(): Set { + throw new Error('BuildModuleNode get ssrImportedModules is not support') + } + get importedModules(): Set { + throw new Error('BuildModuleNode get importedModules is not support') + } + get acceptedHmrDeps(): Set { + throw new Error('BuildModuleNode get acceptedHmrDeps is not support') + } + get acceptedHmrExports(): Set | null { + throw new Error('BuildModuleNode get acceptedHmrExports is not support') + } + get importedBindings(): Map> | null { + throw new Error('BuildModuleNode get importedBindings is not support') + } + get isSelfAccepting(): boolean | undefined { + throw new Error('BuildModuleNode get isSelfAccepting is not support') + } + get transformResult(): TransformResult | null { + throw new Error('BuildModuleNode get transformResult is not support') + } + set transformResult(_value: TransformResult | null) { + throw new Error('BuildModuleNode set transformResult is not support') + } + get ssrTransformResult(): TransformResult | null { + throw new Error('BuildModuleNode get ssrTransformResult is not support') + } + set ssrTransformResult(_value: TransformResult | null) { + throw new Error('BuildModuleNode set ssrTransformResult is not support') + } + get ssrModule(): Record | null { + throw new Error('BuildModuleNode get ssrModule is not support') + } + get ssrError(): Error | null { + throw new Error('BuildModuleNode get ssrError is not support') + } + get lastHMRTimestamp(): number { + throw new Error('BuildModuleNode get lastHMRTimestamp is not support') + } + set lastHMRTimestamp(_value: number) { + throw new Error('BuildModuleNode set lastHMRTimestamp is not support') + } + get lastInvalidationTimestamp(): number { + throw new Error( + 'BuildModuleNode get lastInvalidationTimestamp is not support', + ) + } + get invalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined { + throw new Error('BuildModuleNode get invalidationState is not support') + } + get ssrInvalidationState(): TransformResult | 'HARD_INVALIDATED' | undefined { + throw new Error('BuildModuleNode get ssrInvalidationState is not support') + } } - export class BuildModuleGraph { idToModuleMap = new Map() @@ -117,29 +118,28 @@ export class BuildModuleGraph { constructor() {} getModuleById(id: string): BuildModuleNode | undefined { - return this.idToModuleMap.get(id); + return this.idToModuleMap.get(id) } async getModuleByUrl( _url: string, _ssr?: boolean, ): Promise { - throw new Error("BuildModuleGraph getModuleByUrl is not support"); + throw new Error('BuildModuleGraph getModuleByUrl is not support') } getModulesByFile(file: string): Set | undefined { - return this.fileToModulesMap.get(file); + return this.fileToModulesMap.get(file) } onFileChange(_file: string): void { - throw new Error("BuildModuleGraph onFileChange is not support"); + throw new Error('BuildModuleGraph onFileChange is not support') } onFileDelete(_file: string): void { - throw new Error("BuildModuleGraph onFileDelete is not support"); + throw new Error('BuildModuleGraph onFileDelete is not support') } - invalidateModule( _mod: BuildModuleNode, _seen = new Set(), @@ -148,28 +148,27 @@ export class BuildModuleGraph { /** @internal */ _softInvalidate = false, ): void { - throw new Error("BuildModuleGraph invalidateModule is not support"); + throw new Error('BuildModuleGraph invalidateModule is not support') } invalidateAll(): void { - throw new Error("BuildModuleGraph invalidateAll is not support"); + throw new Error('BuildModuleGraph invalidateAll is not support') } - async ensureEntryFromUrl( _rawUrl: string, _ssr?: boolean, _setIsSelfAccepting = true, ): Promise { - throw new Error("BuildModuleGraph invalidateAll is not support"); + throw new Error('BuildModuleGraph invalidateAll is not support') } createFileOnlyEntry(_file: string): BuildModuleNode { - throw new Error("BuildModuleGraph createFileOnlyEntry is not support"); + throw new Error('BuildModuleGraph createFileOnlyEntry is not support') } async resolveUrl(_url: string, _ssr?: boolean): Promise { - throw new Error("BuildModuleGraph resolveUrl is not support"); + throw new Error('BuildModuleGraph resolveUrl is not support') } updateModuleTransformResult( @@ -177,61 +176,77 @@ export class BuildModuleGraph { _result: TransformResult | null, _ssr?: boolean, ): void { - throw new Error("BuildModuleGraph updateModuleTransformResult is not support"); + throw new Error( + 'BuildModuleGraph updateModuleTransformResult is not support', + ) } getModuleByEtag(_etag: string): BuildModuleNode | undefined { - throw new Error("BuildModuleGraph getModuleByEtag is not support"); + throw new Error('BuildModuleGraph getModuleByEtag is not support') } getBackwardCompatibleBrowserModuleNode( _clientModule: EnvironmentModuleNode, ): BuildModuleNode { - throw new Error("BuildModuleGraph getBackwardCompatibleBrowserModuleNode is not support"); + throw new Error( + 'BuildModuleGraph getBackwardCompatibleBrowserModuleNode is not support', + ) } getBackwardCompatibleServerModuleNode( _ssrModule: EnvironmentModuleNode, ): BuildModuleNode { - throw new Error("BuildModuleGraph getBackwardCompatibleServerModuleNode is not support"); + throw new Error( + 'BuildModuleGraph getBackwardCompatibleServerModuleNode is not support', + ) } - getBackwardCompatibleModuleNode(_mod: EnvironmentModuleNode): BuildModuleNode { - throw new Error("BuildModuleGraph getBackwardCompatibleModuleNode is not support"); + getBackwardCompatibleModuleNode( + _mod: EnvironmentModuleNode, + ): BuildModuleNode { + throw new Error( + 'BuildModuleGraph getBackwardCompatibleModuleNode is not support', + ) } getBackwardCompatibleModuleNodeDual( _clientModule?: EnvironmentModuleNode, _ssrModule?: EnvironmentModuleNode, ): BuildModuleNode { - throw new Error("BuildModuleGraph getBackwardCompatibleModuleNodeDual is not support"); + throw new Error( + 'BuildModuleGraph getBackwardCompatibleModuleNodeDual is not support', + ) } } export function buildModuleGraphPlugin(server: ViteDevServer): Plugin { - return { - name: 'build-module-graph-plugin', - renderStart() { - const moduleGraph = server.moduleGraph as BuildModuleGraph - for (const id of this.getModuleIds()) { - // const moduleInfo = this.getModuleInfo(id)! - const file = cleanUrl(id) - const moduleNode = new BuildModuleNode(id, file); - moduleGraph.idToModuleMap.set(id, moduleNode) - - moduleGraph.fileToModulesMap.set(file, new Set([moduleNode])) - let fileMappedModules = moduleGraph.fileToModulesMap.get(file) - if (!fileMappedModules) { - fileMappedModules = new Set() - moduleGraph.fileToModulesMap.set(file, fileMappedModules) - } - fileMappedModules.add(moduleNode) - } - for (const id of this.getModuleIds()) { - const moduleInfo = this.getModuleInfo(id)! - const moduleNode = moduleGraph.idToModuleMap.get(id); - moduleNode!.importers = new Set(moduleInfo.importers.map((importerId) => moduleGraph.idToModuleMap.get(importerId)!)) - } + return { + name: 'build-module-graph-plugin', + renderStart() { + const moduleGraph = server.moduleGraph as BuildModuleGraph + for (const id of this.getModuleIds()) { + // const moduleInfo = this.getModuleInfo(id)! + const file = cleanUrl(id) + const moduleNode = new BuildModuleNode(id, file) + moduleGraph.idToModuleMap.set(id, moduleNode) + + moduleGraph.fileToModulesMap.set(file, new Set([moduleNode])) + let fileMappedModules = moduleGraph.fileToModulesMap.get(file) + if (!fileMappedModules) { + fileMappedModules = new Set() + moduleGraph.fileToModulesMap.set(file, fileMappedModules) } - } -} \ No newline at end of file + fileMappedModules.add(moduleNode) + } + for (const id of this.getModuleIds()) { + const moduleInfo = this.getModuleInfo(id)! + const moduleNode = moduleGraph.idToModuleMap.get(id) + moduleNode!.importers = new Set( + moduleInfo.importers.map( + (importerId) => moduleGraph.idToModuleMap.get(importerId)!, + ), + ) + } + }, + } +} From a28f8157a9d792f62d1c5370248154429ea4e1f5 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 22 May 2025 10:33:01 +0800 Subject: [PATCH 75/76] fix: add cwd to hmr test --- package.json | 2 +- packages/vite/src/node/build.ts | 2 +- playground/hmr/vite.config.ts | 43 ++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index e22addcdc322a9..bf7ace197b04e0 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "format": "prettier --write --cache .", "lint": "eslint --cache .", "typecheck": "tsc -p scripts --noEmit && pnpm -r --parallel run typecheck", - "test": "pnpm test-unit && pnpm test-serve && pnpm test-build", + "test": "pnpm test-unit && pnpm test-serve && pnpm run test-full-bundle-mode && pnpm test-build", "test-full-bundle-mode": "VITE_TEST_FULL_BUNDLE_MODE=1 vitest run -c vitest.config.e2e.ts", "test-serve": "vitest run -c vitest.config.e2e.ts", "test-build": "VITE_TEST_BUILD=1 vitest run -c vitest.config.e2e.ts", diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index a3b0460e89347d..17f17feb24af5d 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -660,7 +660,7 @@ async function buildEnvironment( // ? 'strict' // : false, // cache: options.watch ? undefined : false, - cwd: root, + // cwd: root, ...options.rollupOptions, output: options.rollupOptions.output, input, diff --git a/playground/hmr/vite.config.ts b/playground/hmr/vite.config.ts index 0e7522d4567c0c..dbf6776bd84edd 100644 --- a/playground/hmr/vite.config.ts +++ b/playground/hmr/vite.config.ts @@ -15,16 +15,41 @@ export default defineConfig({ }, rollupOptions: { input: [ - path.resolve(import.meta.dirname, 'accept-exports/dynamic-imports/index.html'), - path.resolve(import.meta.dirname, 'accept-exports/export-from/index.html'), - path.resolve(import.meta.dirname, 'accept-exports/main-accepted/index.html'), - path.resolve(import.meta.dirname, 'accept-exports/main-non-accepted/index.html'), - path.resolve(import.meta.dirname, 'accept-exports/side-effects/index.html'), - path.resolve(import.meta.dirname, 'accept-exports/star-imports/index.html'), - path.resolve(import.meta.dirname, 'accept-exports/unused-exports/index.html'), + path.resolve( + import.meta.dirname, + 'accept-exports/dynamic-imports/index.html', + ), + path.resolve( + import.meta.dirname, + 'accept-exports/export-from/index.html', + ), + path.resolve( + import.meta.dirname, + 'accept-exports/main-accepted/index.html', + ), + path.resolve( + import.meta.dirname, + 'accept-exports/main-non-accepted/index.html', + ), + path.resolve( + import.meta.dirname, + 'accept-exports/side-effects/index.html', + ), + path.resolve( + import.meta.dirname, + 'accept-exports/star-imports/index.html', + ), + path.resolve( + import.meta.dirname, + 'accept-exports/unused-exports/index.html', + ), path.resolve(import.meta.dirname, 'index.html'), - ] - } + ], + // TODO(underfin): find a nice way + // The vite root not using `cwd` option call rolldown, the rolldown register module is base on process.cwd to calculate path. + // make full bundle mode print the hmr path base on the dirname + cwd: import.meta.dirname, + }, }, plugins: [ { From bfb953f85db37f9ef7858c2f8e9ca7aaaa92ea66 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 22 May 2025 10:49:24 +0800 Subject: [PATCH 76/76] chore: add test-full-bundle-mode in ci --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8528988ae8ad54..3e4e45686d1b61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,6 +124,9 @@ jobs: - name: Test serve run: pnpm run test-serve + - name: Test full bundle mode serve + run: pnpm run test-full-bundle-mode + - name: Test build run: pnpm run test-build