@@ -17,6 +17,7 @@ import {
17
17
compileToJSX ,
18
18
createAssetsExportCode ,
19
19
extractContentTitleData ,
20
+ promiseWithResolvers ,
20
21
} from './utils' ;
21
22
import type { WebpackCompilerName } from '@docusaurus/utils' ;
22
23
import type { Options } from './options' ;
@@ -138,30 +139,76 @@ async function loadMDXWithCaching({
138
139
options : Options ;
139
140
compilerName : WebpackCompilerName ;
140
141
} ) : Promise < string > {
142
+ const { crossCompilerCache} = options ;
143
+ if ( ! crossCompilerCache ) {
144
+ return loadMDX ( {
145
+ fileContent,
146
+ filePath,
147
+ options,
148
+ compilerName,
149
+ } ) ;
150
+ }
151
+
141
152
// Note we "resource" as cache key, not "filePath" nor "fileContent"
142
153
// This is because:
143
154
// - the same file can be compiled in different variants (blog.mdx?truncated)
144
155
// - the same content can be processed differently (versioned docs links)
145
156
const cacheKey = resource ;
146
157
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 } ` ) ;
156
211
}
157
- const promise = loadMDX ( {
158
- fileContent,
159
- filePath,
160
- options,
161
- compilerName,
162
- } ) ;
163
- options . crossCompilerCache ?. set ( cacheKey , promise ) ;
164
- return promise ;
165
212
}
166
213
167
214
export async function mdxLoader (
0 commit comments