From 9d24263ca302402d5f22262b66efe8fd6046e4d4 Mon Sep 17 00:00:00 2001 From: sebastien Date: Thu, 12 Nov 2026 22:49:16 +0100 Subject: [PATCH] fix(core): evict the client and server persistent caches together The MDX cross-compiler cache makes the server compilation of an MDX file wait for its client compilation. When only the server cache was evicted (a Node.js upgrade, Bun, DOCUSAURUS_SERVER_NODE_TARGET), the client restored the MDX modules from its cache and the build hung forever. Both cache versions now hash the client and server targets, and both caches depend on the client and server config files. Co-Authored-By: Claude Opus 5.5 --- .../src/webpack/__tests__/base.test.ts | 46 +++++++++++++++---- packages/docusaurus/src/webpack/base.ts | 10 +++- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/packages/docusaurus/src/webpack/__tests__/base.test.ts b/packages/docusaurus/src/webpack/__tests__/base.test.ts index 279a387deda9..7cab135a22c2 100644 --- a/packages/docusaurus/src/webpack/__tests__/base.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/base.test.ts @@ -161,7 +161,7 @@ describe('base webpack config', () => { vi.unstubAllEnvs(); }); - async function getCacheVersion({ + async function getCache({ isServer = false, siteMetadata = props.siteMetadata, }: { @@ -174,7 +174,15 @@ describe('base webpack config', () => { minify: true, configureWebpackUtils: await createTestConfigureWebpackUtils(), }); - return (config.cache as {version: string}).version; + // Docusaurus bundler configs are typed with Webpack types + return config.cache as unknown as { + version: string; + buildDependencies: string[]; + }; + } + + async function getCacheVersion(params?: Parameters[0]) { + return (await getCache(params)).version; } // Browserslist reads this env variable before any config file @@ -232,14 +240,32 @@ describe('base webpack config', () => { expect(oldNodeVersion).not.toBe(newNodeVersion); }); - // TODO a warm client cache with a cold server cache hangs the build - // See https://github.com/meta/docusaurus/pull/13034 - it('keeps the client cache when the server Node.js target changes', async () => { - stubServerNodeTarget('24.11.1'); - const oldNodeVersion = await getCacheVersion({isServer: false}); - stubServerNodeTarget('24.11.2'); - const newNodeVersion = await getCacheVersion({isServer: false}); - expect(oldNodeVersion).toBe(newNodeVersion); + // A warm client cache with a cold server cache hangs the build + // See https://github.com/meta/docusaurus/pull/13035 + describe('evicts the client and server caches together', () => { + it('evicts the client cache when the server Node.js target changes', async () => { + stubServerNodeTarget('24.11.1'); + const oldNodeVersion = await getCacheVersion({isServer: false}); + stubServerNodeTarget('24.11.2'); + const newNodeVersion = await getCacheVersion({isServer: false}); + expect(oldNodeVersion).not.toBe(newNodeVersion); + }); + + it('evicts the server cache when the client browsers change', async () => { + stubBrowserslistQuery('chrome 90'); + const oldBrowsersVersion = await getCacheVersion({isServer: true}); + stubBrowserslistQuery('baseline widely available'); + const baselineVersion = await getCacheVersion({isServer: true}); + expect(oldBrowsersVersion).not.toBe(baselineVersion); + }); + + it('uses the same build dependencies for the client and server caches', async () => { + const clientCache = await getCache({isServer: false}); + const serverCache = await getCache({isServer: true}); + expect(clientCache.buildDependencies).toEqual( + serverCache.buildDependencies, + ); + }); }); it('is evicted when a plugin package version changes', async () => { diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts index 66b17b448784..782bdc695a7d 100644 --- a/packages/docusaurus/src/webpack/base.ts +++ b/packages/docusaurus/src/webpack/base.ts @@ -118,7 +118,11 @@ export async function createBaseConfig({ // Changing the target browsers or Node.js version must evict the cache // The same Browserslist queries can match other browsers over time and // after data upgrades, for example "baseline widely available" - getTargets({isServer}), + // The client and server caches must be evicted together, or the MDX + // cross-compiler cache hangs the build + // See https://github.com/meta/docusaurus/pull/13035 + getTargets({isServer: false}), + getTargets({isServer: true}), // The plugin code computing loader options is not a build dependency // Upgrading a third-party plugin must evict the cache siteMetadata.pluginVersions, @@ -134,7 +138,9 @@ export async function createBaseConfig({ function getCacheBuildDependencies(): string[] { return [ __filename, - path.join(__dirname, isServer ? 'server.js' : 'client.js'), + // Both: the client and server caches must be evicted together + path.join(__dirname, 'client.js'), + path.join(__dirname, 'server.js'), // Docusaurus config changes can affect MDX/JSX compilation, so we'd // rather evict the cache. // See https://github.com/questdb/questdb.io/issues/493