Skip to content

Commit 1f355b6

Browse files
committed
Merge branch 'main' into patik123/main
2 parents f6c2e8d + 9e473bd commit 1f355b6

File tree

4 files changed

+86
-18
lines changed

4 files changed

+86
-18
lines changed

packages/docusaurus-mdx-loader/src/loader.ts

+64-17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
compileToJSX,
1818
createAssetsExportCode,
1919
extractContentTitleData,
20+
promiseWithResolvers,
2021
} from './utils';
2122
import type {WebpackCompilerName} from '@docusaurus/utils';
2223
import type {Options} from './options';
@@ -138,30 +139,76 @@ async function loadMDXWithCaching({
138139
options: Options;
139140
compilerName: WebpackCompilerName;
140141
}): Promise<string> {
142+
const {crossCompilerCache} = options;
143+
if (!crossCompilerCache) {
144+
return loadMDX({
145+
fileContent,
146+
filePath,
147+
options,
148+
compilerName,
149+
});
150+
}
151+
141152
// Note we "resource" as cache key, not "filePath" nor "fileContent"
142153
// This is because:
143154
// - the same file can be compiled in different variants (blog.mdx?truncated)
144155
// - the same content can be processed differently (versioned docs links)
145156
const cacheKey = resource;
146157

147-
const cachedPromise = options.crossCompilerCache?.get(cacheKey);
148-
if (cachedPromise) {
149-
// We can clean up the cache and free memory here
150-
// We know there are only 2 compilations for the same file
151-
// Note: once we introduce RSCs we'll probably have 3 compilations
152-
// Note: we can't use string keys in WeakMap
153-
// But we could eventually use WeakRef for the values
154-
options.crossCompilerCache?.delete(cacheKey);
155-
return cachedPromise;
158+
// We can clean up the cache and free memory after cache entry consumption
159+
// We know there are only 2 compilations for the same file
160+
// Note: once we introduce RSCs we'll probably have 3 compilations
161+
// Note: we can't use string keys in WeakMap
162+
// But we could eventually use WeakRef for the values
163+
const deleteCacheEntry = () => crossCompilerCache.delete(cacheKey);
164+
165+
const cacheEntry = crossCompilerCache?.get(cacheKey);
166+
167+
// When deduplicating client/server compilations, we always use the client
168+
// compilation and not the server compilation
169+
// This is important because the server compilation usually skips some steps
170+
// Notably: the server compilation does not emit file-loader assets
171+
// Using the server compilation otherwise leads to broken images
172+
// See https://github.com/facebook/docusaurus/issues/10544#issuecomment-2390943794
173+
// See https://github.com/facebook/docusaurus/pull/10553
174+
// TODO a problem with this: server bundle will use client inline loaders
175+
// This means server bundle will use ?emit=true for assets
176+
// We should try to get rid of inline loaders to cleanup this caching logic
177+
if (compilerName === 'client') {
178+
const promise = loadMDX({
179+
fileContent,
180+
filePath,
181+
options,
182+
compilerName,
183+
});
184+
if (cacheEntry) {
185+
promise.then(cacheEntry.resolve, cacheEntry.reject);
186+
deleteCacheEntry();
187+
} else {
188+
const noop = () => {
189+
throw new Error('this should never be called');
190+
};
191+
crossCompilerCache.set(cacheKey, {
192+
promise,
193+
resolve: noop,
194+
reject: noop,
195+
});
196+
}
197+
return promise;
198+
}
199+
// Server compilation always uses the result of the client compilation above
200+
else if (compilerName === 'server') {
201+
if (cacheEntry) {
202+
deleteCacheEntry();
203+
return cacheEntry.promise;
204+
} else {
205+
const {promise, resolve, reject} = promiseWithResolvers<string>();
206+
crossCompilerCache.set(cacheKey, {promise, resolve, reject});
207+
return promise;
208+
}
209+
} else {
210+
throw new Error(`Unexpected compilerName=${compilerName}`);
156211
}
157-
const promise = loadMDX({
158-
fileContent,
159-
filePath,
160-
options,
161-
compilerName,
162-
});
163-
options.crossCompilerCache?.set(cacheKey, promise);
164-
return promise;
165212
}
166213

167214
export async function mdxLoader(

packages/docusaurus-mdx-loader/src/options.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import type {MDXOptions, SimpleProcessors} from './processor';
99
import type {MarkdownConfig} from '@docusaurus/types';
1010
import type {ResolveMarkdownLink} from './remark/resolveMarkdownLinks';
11+
import type {PromiseWithResolvers} from './utils';
1112

1213
export type Options = Partial<MDXOptions> & {
1314
markdownConfig: MarkdownConfig;
@@ -25,5 +26,7 @@ export type Options = Partial<MDXOptions> & {
2526

2627
// Will usually be created by "createMDXLoaderItem"
2728
processors?: SimpleProcessors;
28-
crossCompilerCache?: Map<string, Promise<string>>; // MDX => Promise<JSX> cache
29+
crossCompilerCache?: Map<string, CrossCompilerCacheEntry>; // MDX => Promise<JSX> cache
2930
};
31+
32+
type CrossCompilerCacheEntry = PromiseWithResolvers<string>;

packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ const plugin: Plugin = function plugin(this: Processor): Transformer {
155155
// We only enable these warnings for the client compiler
156156
// This avoids emitting duplicate warnings in prod mode
157157
// Note: the client compiler is used in both dev/prod modes
158+
// Also: the client compiler is what gets used when using crossCompilerCache
158159
if (file.data.compilerName === 'client') {
159160
logUnusedDirectivesWarning({
160161
directives: unusedDirectives,

packages/docusaurus-mdx-loader/src/utils.ts

+17
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,20 @@ export async function compileToJSX({
132132
);
133133
}
134134
}
135+
136+
// TODO Docusaurus v4, remove temporary polyfill when upgrading to Node 22+
137+
export interface PromiseWithResolvers<T> {
138+
promise: Promise<T>;
139+
resolve: (value: T | PromiseLike<T>) => void;
140+
reject: (reason?: any) => void;
141+
}
142+
// TODO Docusaurus v4, remove temporary polyfill when upgrading to Node 22+
143+
export function promiseWithResolvers<T>(): PromiseWithResolvers<T> {
144+
// @ts-expect-error: it's fine
145+
const out: PromiseWithResolvers<T> = {};
146+
out.promise = new Promise((resolve, reject) => {
147+
out.resolve = resolve;
148+
out.reject = reject;
149+
});
150+
return out;
151+
}

0 commit comments

Comments
 (0)