From d1a8061eb438c730ccc62ce2d7158dbe89cc5292 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:24:39 +0530 Subject: [PATCH] fix(hmr): don't load config twice on server restart --- src/node/cli.ts | 74 ++++++++++++++++++++++++++++--------------- src/node/plugin.ts | 44 ++++++++----------------- src/node/server.ts | 19 ++++++----- src/node/shortcuts.ts | 20 ++++-------- 4 files changed, 79 insertions(+), 78 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index cf8ce305..dc35f14e 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -1,9 +1,16 @@ import minimist from 'minimist' import c from 'picocolors' import { createLogger, type Logger } from 'vite' -import { build, createServer, serve } from '.' +import { + build, + createServer, + disposeMdItInstance, + resolveConfig, + serve +} from '.' import { version } from '../../package.json' import { init } from './init/init' +import { clearCache } from './markdownToVue' import { bindShortcuts } from './shortcuts' if (process.env.DEBUG) { @@ -40,30 +47,38 @@ if (!command || command === 'dev') { argv.optimizeDeps = { force: true } } + let config = await resolveConfig(root, argv).catch( + logErrorAndExit.bind(null, `failed to resolve config. error:`) + ) const createDevServer = async (isRestart = true) => { - const server = await createServer(root, argv, async () => { + const server = await createServer(root, argv, restartServer, config) + function restartServer() { if (!restartPromise) { restartPromise = (async () => { + try { + config = await resolveConfig(root, argv) + } catch (err: any) { + logError(`failed to resolve config. error:`, err) + return + } + disposeMdItInstance() + clearCache() await server.close() await createDevServer() })().finally(() => { restartPromise = undefined }) } - return restartPromise - }) + } await server.listen(undefined, isRestart) logVersion(server.config.logger) server.printUrls() - bindShortcuts(server, createDevServer) + bindShortcuts(server, restartServer) } - createDevServer(false).catch((err) => { - createLogger().error( - `${c.red(`failed to start server. error:`)}\n${err.message}\n${err.stack}` - ) - process.exit(1) - }) + createDevServer(false).catch( + logErrorAndExit.bind(null, `failed to start server. error:`) + ) } else if (command === 'init') { createLogger().info('', { clear: true }) init(argv.root) @@ -74,21 +89,30 @@ if (!command || command === 'dev') { onAfterConfigResolve(siteConfig) { logVersion(siteConfig.logger) } - }).catch((err) => { - createLogger().error( - `${c.red(`build error:`)}\n${err.message}\n${err.stack}` - ) - process.exit(1) - }) + }).catch(logErrorAndExit.bind(null, `build error:`)) } else if (command === 'serve' || command === 'preview') { - serve(argv).catch((err) => { - createLogger().error( - `${c.red(`failed to start server. error:`)}\n${err.message}\n${err.stack}` - ) - process.exit(1) - }) + serve(argv).catch( + logErrorAndExit.bind(null, `failed to start server. error:`) + ) } else { - createLogger().error(c.red(`unknown command "${command}".`)) - process.exit(1) + logErrorAndExit(`unknown command "${command}".`) } } + +function logErrorAndExit(message: string, err?: any): never { + logError(message, err) + process.exit(1) +} + +function logError(message: string, err?: any) { + const logger = createLogger() + logger.error( + [ + c.red(message), + err && 'message' in err && err.message, + err && 'stack' in err && err.stack + ] + .filter(Boolean) + .join('\n') + ) +} diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 34e02d4f..18eebcac 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -18,13 +18,7 @@ import { SITE_DATA_REQUEST_PATH, resolveAliases } from './alias' -import { - isAdditionalConfigFile, - resolvePages, - resolveUserConfig, - type SiteConfig -} from './config' -import { disposeMdItInstance } from './markdown/markdown' +import { isAdditionalConfigFile, resolvePages, type SiteConfig } from './config' import { clearCache, createMarkdownToVueRenderFn, @@ -76,7 +70,7 @@ export async function createVitePressPlugin( ssr = false, pageToHashMap?: Record, clientJSMap?: Record, - recreateServer?: () => Promise + restartServer?: () => Promise ) { const { srcDir, @@ -354,18 +348,6 @@ export async function createVitePressPlugin( if (this.environment.name !== 'client') return const relativePath = path.posix.relative(srcDir, file) - if (themeRE.test(relativePath) && type !== 'update') { - siteConfig.themeDir = - type === 'create' ? path.posix.dirname(file) : DEFAULT_THEME_PATH - siteConfig.logger.info(c.green('page reload ') + c.dim(relativePath), { - clear: true, - timestamp: true - }) - this.environment.moduleGraph.invalidateAll() - this.environment.hot.send({ type: 'full-reload' }) - return [] - } - // update pages, dynamicRoutes and rewrites on md file creation / deletion if (file.endsWith('.md') && type !== 'update') { await resolvePages(siteConfig) @@ -387,17 +369,19 @@ export async function createVitePressPlugin( { clear: true, timestamp: true } ) - try { - await resolveUserConfig(siteConfig.root, 'serve', 'development') - } catch (err: any) { - siteConfig.logger.error(err) - return - } + return restartServer?.() + } - disposeMdItInstance() - clearCache() - await recreateServer?.() - return + if (themeRE.test(relativePath) && type !== 'update') { + siteConfig.themeDir = + type === 'create' ? path.posix.dirname(file) : DEFAULT_THEME_PATH + siteConfig.logger.info(c.green('page reload ') + c.dim(relativePath), { + clear: true, + timestamp: true + }) + this.environment.moduleGraph.invalidateAll() + this.environment.hot.send({ type: 'full-reload' }) + return [] } } } diff --git a/src/node/server.ts b/src/node/server.ts index 4105edfe..f452c228 100644 --- a/src/node/server.ts +++ b/src/node/server.ts @@ -1,25 +1,24 @@ import { createServer as createViteServer, type ServerOptions } from 'vite' -import { resolveConfig } from './config' +import { resolveConfig, type SiteConfig } from './config' import { createVitePressPlugin } from './plugin' export async function createServer( - root: string = process.cwd(), + root: string = process.cwd(), // for backwards compatibility serverOptions: ServerOptions & { base?: string } = {}, - recreateServer?: () => Promise + restartServer?: () => Promise, + config?: SiteConfig // new code should pass config directly ) { - const config = await resolveConfig(root) + config ??= await resolveConfig(root) - if (serverOptions.base) { - config.site.base = serverOptions.base - delete serverOptions.base - } + const { base, ...server } = serverOptions + config.site.base = base ?? config.site.base return createViteServer({ root: config.srcDir, base: config.site.base, cacheDir: config.cacheDir, - plugins: await createVitePressPlugin(config, false, {}, {}, recreateServer), - server: serverOptions, + plugins: await createVitePressPlugin(config, false, {}, {}, restartServer), + server, customLogger: config.logger, configFile: config.vite?.configFile }) diff --git a/src/node/shortcuts.ts b/src/node/shortcuts.ts index 108857c5..56dd00c0 100644 --- a/src/node/shortcuts.ts +++ b/src/node/shortcuts.ts @@ -1,22 +1,19 @@ import c from 'picocolors' import type { ViteDevServer } from 'vite' -import { disposeMdItInstance } from './markdown/markdown' -import { clearCache } from './markdownToVue' - -type CreateDevServer = () => Promise +import type { Awaitable } from './shared' export type CLIShortcut = { key: string description: string action( server: ViteDevServer, - createDevServer: CreateDevServer - ): void | Promise + restartServer: () => Promise + ): Awaitable } export function bindShortcuts( server: ViteDevServer, - createDevServer: CreateDevServer + restartServer: () => Promise ): void { if (!server.httpServer || !process.stdin.isTTY || process.env.CI) { return @@ -59,7 +56,7 @@ export function bindShortcuts( if (!shortcut) return actionRunning = true - await shortcut.action(server, createDevServer) + await shortcut.action(server, restartServer) actionRunning = false } @@ -77,15 +74,12 @@ const SHORTCUTS: CLIShortcut[] = [ { key: 'r', description: 'restart the server', - async action(server, createDevServer) { + async action(server, restartServer) { server.config.logger.info(c.green(`restarting server...\n`), { clear: true, timestamp: true }) - disposeMdItInstance() - clearCache() - await server.close() - await createDevServer() + await restartServer() } }, {