@docusaurus/mdx-loader 3.5.2 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/lib/createMDXLoader.d.ts +18 -0
- package/lib/createMDXLoader.d.ts.map +1 -0
- package/lib/createMDXLoader.js +48 -0
- package/lib/createMDXLoader.js.map +1 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +1 -23
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +120 -139
- package/lib/loader.js.map +1 -1
- package/lib/options.d.ts +33 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +9 -0
- package/lib/options.js.map +1 -0
- package/lib/preprocessor.d.ts +1 -1
- package/lib/preprocessor.d.ts.map +1 -1
- package/lib/processor.d.ts +12 -6
- package/lib/processor.d.ts.map +1 -1
- package/lib/processor.js +19 -15
- package/lib/processor.js.map +1 -1
- package/lib/remark/transformImage/index.d.ts.map +1 -1
- package/lib/remark/transformImage/index.js +11 -9
- package/lib/remark/transformImage/index.js.map +1 -1
- package/lib/remark/transformLinks/index.d.ts.map +1 -1
- package/lib/remark/transformLinks/index.js +11 -9
- package/lib/remark/transformLinks/index.js.map +1 -1
- package/lib/remark/unusedDirectives/index.d.ts.map +1 -1
- package/lib/remark/unusedDirectives/index.js +1 -0
- package/lib/remark/unusedDirectives/index.js.map +1 -1
- package/lib/utils.d.ts +41 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +108 -0
- package/lib/utils.js.map +1 -0
- package/package.json +6 -6
- package/src/createMDXLoader.ts +67 -0
- package/src/index.ts +4 -1
- package/src/loader.ts +163 -198
- package/src/options.ts +32 -0
- package/src/preprocessor.ts +1 -1
- package/src/processor.ts +35 -31
- package/src/remark/transformImage/index.ts +17 -15
- package/src/remark/transformLinks/index.ts +19 -15
- package/src/remark/unusedDirectives/index.ts +1 -0
- package/src/types.d.mts +3 -1
- package/src/utils.ts +151 -0
package/src/loader.ts
CHANGED
|
@@ -5,22 +5,22 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import fs from 'fs-extra';
|
|
9
8
|
import logger from '@docusaurus/logger';
|
|
10
9
|
import {
|
|
10
|
+
aliasedSitePath,
|
|
11
11
|
DEFAULT_PARSE_FRONT_MATTER,
|
|
12
|
-
escapePath,
|
|
13
12
|
getFileLoaderUtils,
|
|
14
13
|
getWebpackLoaderCompilerName,
|
|
15
14
|
} from '@docusaurus/utils';
|
|
16
15
|
import stringifyObject from 'stringify-object';
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import type {
|
|
16
|
+
import {
|
|
17
|
+
compileToJSX,
|
|
18
|
+
createAssetsExportCode,
|
|
19
|
+
extractContentTitleData,
|
|
20
|
+
promiseWithResolvers,
|
|
21
|
+
} from './utils';
|
|
22
|
+
import type {WebpackCompilerName} from '@docusaurus/utils';
|
|
23
|
+
import type {Options} from './options';
|
|
24
24
|
import type {LoaderContext} from 'webpack';
|
|
25
25
|
|
|
26
26
|
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
|
@@ -28,235 +28,91 @@ import type {LoaderContext} from 'webpack';
|
|
|
28
28
|
// See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
|
|
29
29
|
type Pluggable = any; // TODO fix this asap
|
|
30
30
|
|
|
31
|
-
const {
|
|
32
|
-
loaders: {inlineMarkdownAssetImageFileLoader},
|
|
33
|
-
} = getFileLoaderUtils();
|
|
34
|
-
|
|
35
31
|
export type MDXPlugin = Pluggable;
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
resolveMarkdownLink?: ResolveMarkdownLink;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* When this throws, it generally means that there's no metadata file associated
|
|
54
|
-
* with this MDX document. It can happen when using MDX partials (usually
|
|
55
|
-
* starting with _). That's why it's important to provide the `isMDXPartial`
|
|
56
|
-
* function in config
|
|
57
|
-
*/
|
|
58
|
-
async function readMetadataPath(metadataPath: string) {
|
|
59
|
-
try {
|
|
60
|
-
return await fs.readFile(metadataPath, 'utf8');
|
|
61
|
-
} catch (err) {
|
|
62
|
-
logger.error`MDX loader can't read MDX metadata file path=${metadataPath}. Maybe the isMDXPartial option function was not provided?`;
|
|
63
|
-
throw err;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Converts assets an object with Webpack require calls code.
|
|
69
|
-
* This is useful for mdx files to reference co-located assets using relative
|
|
70
|
-
* paths. Those assets should enter the Webpack assets pipeline and be hashed.
|
|
71
|
-
* For now, we only handle that for images and paths starting with `./`:
|
|
72
|
-
*
|
|
73
|
-
* `{image: "./myImage.png"}` => `{image: require("./myImage.png")}`
|
|
74
|
-
*/
|
|
75
|
-
function createAssetsExportCode(assets: unknown) {
|
|
76
|
-
if (
|
|
77
|
-
typeof assets !== 'object' ||
|
|
78
|
-
!assets ||
|
|
79
|
-
Object.keys(assets).length === 0
|
|
80
|
-
) {
|
|
81
|
-
return 'undefined';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// TODO implementation can be completed/enhanced
|
|
85
|
-
function createAssetValueCode(assetValue: unknown): string | undefined {
|
|
86
|
-
if (Array.isArray(assetValue)) {
|
|
87
|
-
const arrayItemCodes = assetValue.map(
|
|
88
|
-
(item: unknown) => createAssetValueCode(item) ?? 'undefined',
|
|
89
|
-
);
|
|
90
|
-
return `[${arrayItemCodes.join(', ')}]`;
|
|
91
|
-
}
|
|
92
|
-
// Only process string values starting with ./
|
|
93
|
-
// We could enhance this logic and check if file exists on disc?
|
|
94
|
-
if (typeof assetValue === 'string' && assetValue.startsWith('./')) {
|
|
95
|
-
// TODO do we have other use-cases than image assets?
|
|
96
|
-
// Probably not worth adding more support, as we want to move to Webpack 5 new asset system (https://github.com/facebook/docusaurus/pull/4708)
|
|
97
|
-
return `require("${inlineMarkdownAssetImageFileLoader}${escapePath(
|
|
98
|
-
assetValue,
|
|
99
|
-
)}").default`;
|
|
100
|
-
}
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const assetEntries = Object.entries(assets);
|
|
105
|
-
|
|
106
|
-
const codeLines = assetEntries
|
|
107
|
-
.map(([key, value]: [string, unknown]) => {
|
|
108
|
-
const assetRequireCode = createAssetValueCode(value);
|
|
109
|
-
return assetRequireCode ? `"${key}": ${assetRequireCode},` : undefined;
|
|
110
|
-
})
|
|
111
|
-
.filter(Boolean);
|
|
112
|
-
|
|
113
|
-
return `{\n${codeLines.join('\n')}\n}`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// TODO temporary, remove this after v3.1?
|
|
117
|
-
// Some plugin authors use our mdx-loader, despite it not being public API
|
|
118
|
-
// see https://github.com/facebook/docusaurus/issues/8298
|
|
119
|
-
function ensureMarkdownConfig(reqOptions: Options) {
|
|
120
|
-
if (!reqOptions.markdownConfig) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
'Docusaurus v3+ requires MDX loader options.markdownConfig - plugin authors using the MDX loader should make sure to provide that option',
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* data.contentTitle is set by the remark contentTitle plugin
|
|
129
|
-
*/
|
|
130
|
-
function extractContentTitleData(data: {
|
|
131
|
-
[key: string]: unknown;
|
|
132
|
-
}): string | undefined {
|
|
133
|
-
return data.contentTitle as string | undefined;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export async function mdxLoader(
|
|
137
|
-
this: LoaderContext<Options>,
|
|
138
|
-
fileContent: string,
|
|
139
|
-
): Promise<void> {
|
|
140
|
-
const compilerName = getWebpackLoaderCompilerName(this);
|
|
141
|
-
const callback = this.async();
|
|
142
|
-
const filePath = this.resourcePath;
|
|
143
|
-
const reqOptions: Options = this.getOptions();
|
|
144
|
-
const {query} = this;
|
|
145
|
-
|
|
146
|
-
ensureMarkdownConfig(reqOptions);
|
|
147
|
-
|
|
148
|
-
const {frontMatter} = await reqOptions.markdownConfig.parseFrontMatter({
|
|
33
|
+
async function loadMDX({
|
|
34
|
+
fileContent,
|
|
35
|
+
filePath,
|
|
36
|
+
options,
|
|
37
|
+
compilerName,
|
|
38
|
+
}: {
|
|
39
|
+
fileContent: string;
|
|
40
|
+
filePath: string;
|
|
41
|
+
options: Options;
|
|
42
|
+
compilerName: WebpackCompilerName;
|
|
43
|
+
}): Promise<string> {
|
|
44
|
+
const {frontMatter} = await options.markdownConfig.parseFrontMatter({
|
|
149
45
|
filePath,
|
|
150
46
|
fileContent,
|
|
151
47
|
defaultParseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
|
|
152
48
|
});
|
|
153
|
-
const mdxFrontMatter = validateMDXFrontMatter(frontMatter.mdx);
|
|
154
|
-
|
|
155
|
-
const preprocessedContent = preprocessor({
|
|
156
|
-
fileContent,
|
|
157
|
-
filePath,
|
|
158
|
-
admonitions: reqOptions.admonitions,
|
|
159
|
-
markdownConfig: reqOptions.markdownConfig,
|
|
160
|
-
});
|
|
161
49
|
|
|
162
50
|
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
|
163
51
|
|
|
164
|
-
const
|
|
52
|
+
const result = await compileToJSX({
|
|
53
|
+
fileContent,
|
|
165
54
|
filePath,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
55
|
+
frontMatter,
|
|
56
|
+
options,
|
|
57
|
+
compilerName,
|
|
169
58
|
});
|
|
170
59
|
|
|
171
|
-
let result: {content: string; data: {[key: string]: unknown}};
|
|
172
|
-
try {
|
|
173
|
-
result = await processor.process({
|
|
174
|
-
content: preprocessedContent,
|
|
175
|
-
filePath,
|
|
176
|
-
frontMatter,
|
|
177
|
-
compilerName,
|
|
178
|
-
});
|
|
179
|
-
} catch (errorUnknown) {
|
|
180
|
-
const error = errorUnknown as Error;
|
|
181
|
-
|
|
182
|
-
// MDX can emit errors that have useful extra attributes
|
|
183
|
-
const errorJSON = JSON.stringify(error, null, 2);
|
|
184
|
-
const errorDetails =
|
|
185
|
-
errorJSON === '{}'
|
|
186
|
-
? // regular JS error case: print stacktrace
|
|
187
|
-
error.stack ?? 'N/A'
|
|
188
|
-
: // MDX error: print extra attributes + stacktrace
|
|
189
|
-
`${errorJSON}\n${error.stack}`;
|
|
190
|
-
|
|
191
|
-
return callback(
|
|
192
|
-
new Error(
|
|
193
|
-
`MDX compilation failed for file ${logger.path(filePath)}\nCause: ${
|
|
194
|
-
error.message
|
|
195
|
-
}\nDetails:\n${errorDetails}`,
|
|
196
|
-
// TODO error cause doesn't seem to be used by Webpack stats.errors :s
|
|
197
|
-
{cause: error},
|
|
198
|
-
),
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
60
|
const contentTitle = extractContentTitleData(result.data);
|
|
203
61
|
|
|
204
62
|
// MDX partials are MDX files starting with _ or in a folder starting with _
|
|
205
63
|
// Partial are not expected to have associated metadata files or front matter
|
|
206
|
-
const isMDXPartial =
|
|
64
|
+
const isMDXPartial = options.isMDXPartial?.(filePath);
|
|
207
65
|
if (isMDXPartial && hasFrontMatter) {
|
|
208
66
|
const errorMessage = `Docusaurus MDX partial files should not contain front matter.
|
|
209
67
|
Those partial files use the _ prefix as a convention by default, but this is configurable.
|
|
210
68
|
File at ${filePath} contains front matter that will be ignored:
|
|
211
69
|
${JSON.stringify(frontMatter, null, 2)}`;
|
|
212
70
|
|
|
213
|
-
if (!
|
|
71
|
+
if (!options.isMDXPartialFrontMatterWarningDisabled) {
|
|
214
72
|
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
|
|
215
73
|
if (shouldError) {
|
|
216
|
-
|
|
74
|
+
throw new Error(errorMessage);
|
|
217
75
|
}
|
|
218
76
|
logger.warn(errorMessage);
|
|
219
77
|
}
|
|
220
78
|
}
|
|
221
79
|
|
|
222
|
-
function getMetadataPath()
|
|
80
|
+
const metadataPath = (function getMetadataPath() {
|
|
223
81
|
if (!isMDXPartial) {
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
reqOptions.metadataPath &&
|
|
227
|
-
typeof reqOptions.metadataPath === 'function'
|
|
228
|
-
) {
|
|
229
|
-
return reqOptions.metadataPath(filePath);
|
|
230
|
-
}
|
|
82
|
+
return options.metadataPath?.(filePath);
|
|
231
83
|
}
|
|
232
84
|
return undefined;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const metadataPath = getMetadataPath();
|
|
236
|
-
if (metadataPath) {
|
|
237
|
-
this.addDependency(metadataPath);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const metadataJsonString = metadataPath
|
|
241
|
-
? await readMetadataPath(metadataPath)
|
|
242
|
-
: undefined;
|
|
243
|
-
|
|
244
|
-
const metadata = metadataJsonString
|
|
245
|
-
? (JSON.parse(metadataJsonString) as {[key: string]: unknown})
|
|
246
|
-
: undefined;
|
|
85
|
+
})();
|
|
247
86
|
|
|
248
87
|
const assets =
|
|
249
|
-
|
|
250
|
-
?
|
|
88
|
+
options.createAssets && !isMDXPartial
|
|
89
|
+
? options.createAssets({filePath, frontMatter})
|
|
251
90
|
: undefined;
|
|
252
91
|
|
|
92
|
+
const fileLoaderUtils = getFileLoaderUtils(compilerName === 'server');
|
|
93
|
+
|
|
253
94
|
// TODO use remark plugins to insert extra exports instead of string concat?
|
|
254
95
|
// cf how the toc is exported
|
|
255
96
|
const exportsCode = `
|
|
256
97
|
export const frontMatter = ${stringifyObject(frontMatter)};
|
|
257
98
|
export const contentTitle = ${stringifyObject(contentTitle)};
|
|
258
|
-
${
|
|
259
|
-
|
|
99
|
+
${
|
|
100
|
+
metadataPath
|
|
101
|
+
? `export {default as metadata} from '${aliasedSitePath(
|
|
102
|
+
metadataPath,
|
|
103
|
+
options.siteDir,
|
|
104
|
+
)}'`
|
|
105
|
+
: ''
|
|
106
|
+
}
|
|
107
|
+
${
|
|
108
|
+
assets
|
|
109
|
+
? `export const assets = ${createAssetsExportCode({
|
|
110
|
+
assets,
|
|
111
|
+
inlineMarkdownAssetImageFileLoader:
|
|
112
|
+
fileLoaderUtils.loaders.inlineMarkdownAssetImageFileLoader,
|
|
113
|
+
})};`
|
|
114
|
+
: ''
|
|
115
|
+
}
|
|
260
116
|
`;
|
|
261
117
|
|
|
262
118
|
const code = `
|
|
@@ -264,5 +120,114 @@ ${exportsCode}
|
|
|
264
120
|
${result.content}
|
|
265
121
|
`;
|
|
266
122
|
|
|
267
|
-
return
|
|
123
|
+
return code;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Note: we cache promises instead of strings
|
|
127
|
+
// This is because client/server compilations might be triggered in parallel
|
|
128
|
+
// When this happens for the same file, we don't want to compile it twice
|
|
129
|
+
async function loadMDXWithCaching({
|
|
130
|
+
resource,
|
|
131
|
+
fileContent,
|
|
132
|
+
filePath,
|
|
133
|
+
options,
|
|
134
|
+
compilerName,
|
|
135
|
+
}: {
|
|
136
|
+
resource: string; // path?query#hash
|
|
137
|
+
filePath: string; // path
|
|
138
|
+
fileContent: string;
|
|
139
|
+
options: Options;
|
|
140
|
+
compilerName: WebpackCompilerName;
|
|
141
|
+
}): Promise<string> {
|
|
142
|
+
const {crossCompilerCache} = options;
|
|
143
|
+
if (!crossCompilerCache) {
|
|
144
|
+
return loadMDX({
|
|
145
|
+
fileContent,
|
|
146
|
+
filePath,
|
|
147
|
+
options,
|
|
148
|
+
compilerName,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Note we "resource" as cache key, not "filePath" nor "fileContent"
|
|
153
|
+
// This is because:
|
|
154
|
+
// - the same file can be compiled in different variants (blog.mdx?truncated)
|
|
155
|
+
// - the same content can be processed differently (versioned docs links)
|
|
156
|
+
const cacheKey = resource;
|
|
157
|
+
|
|
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}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function mdxLoader(
|
|
215
|
+
this: LoaderContext<Options>,
|
|
216
|
+
fileContent: string,
|
|
217
|
+
): Promise<void> {
|
|
218
|
+
const compilerName = getWebpackLoaderCompilerName(this);
|
|
219
|
+
const callback = this.async();
|
|
220
|
+
const options: Options = this.getOptions();
|
|
221
|
+
try {
|
|
222
|
+
const result = await loadMDXWithCaching({
|
|
223
|
+
resource: this.resource,
|
|
224
|
+
filePath: this.resourcePath,
|
|
225
|
+
fileContent,
|
|
226
|
+
options,
|
|
227
|
+
compilerName,
|
|
228
|
+
});
|
|
229
|
+
return callback(null, result);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return callback(error as Error);
|
|
232
|
+
}
|
|
268
233
|
}
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {MDXOptions, SimpleProcessors} from './processor';
|
|
9
|
+
import type {MarkdownConfig} from '@docusaurus/types';
|
|
10
|
+
import type {ResolveMarkdownLink} from './remark/resolveMarkdownLinks';
|
|
11
|
+
import type {PromiseWithResolvers} from './utils';
|
|
12
|
+
|
|
13
|
+
export type Options = Partial<MDXOptions> & {
|
|
14
|
+
markdownConfig: MarkdownConfig;
|
|
15
|
+
staticDirs: string[];
|
|
16
|
+
siteDir: string;
|
|
17
|
+
isMDXPartial?: (filePath: string) => boolean;
|
|
18
|
+
isMDXPartialFrontMatterWarningDisabled?: boolean;
|
|
19
|
+
removeContentTitle?: boolean;
|
|
20
|
+
metadataPath?: (filePath: string) => string;
|
|
21
|
+
createAssets?: (metadata: {
|
|
22
|
+
filePath: string;
|
|
23
|
+
frontMatter: {[key: string]: unknown};
|
|
24
|
+
}) => {[key: string]: unknown};
|
|
25
|
+
resolveMarkdownLink?: ResolveMarkdownLink;
|
|
26
|
+
|
|
27
|
+
// Will usually be created by "createMDXLoaderItem"
|
|
28
|
+
processors?: SimpleProcessors;
|
|
29
|
+
crossCompilerCache?: Map<string, CrossCompilerCacheEntry>; // MDX => Promise<JSX> cache
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type CrossCompilerCacheEntry = PromiseWithResolvers<string>;
|
package/src/preprocessor.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
admonitionTitleToDirectiveLabel,
|
|
12
12
|
} from '@docusaurus/utils';
|
|
13
13
|
import {normalizeAdmonitionOptions} from './remark/admonitions';
|
|
14
|
-
import type {Options} from './
|
|
14
|
+
import type {Options} from './options';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Preprocess the string before passing it to MDX
|
package/src/processor.ts
CHANGED
|
@@ -20,7 +20,7 @@ import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
|
|
|
20
20
|
import {getFormat} from './format';
|
|
21
21
|
import type {WebpackCompilerName} from '@docusaurus/utils';
|
|
22
22
|
import type {MDXFrontMatter} from './frontMatter';
|
|
23
|
-
import type {Options} from './
|
|
23
|
+
import type {Options} from './options';
|
|
24
24
|
import type {AdmonitionOptions} from './remark/admonitions';
|
|
25
25
|
|
|
26
26
|
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
|
@@ -31,10 +31,13 @@ import type {ProcessorOptions} from '@mdx-js/mdx';
|
|
|
31
31
|
// See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
|
|
32
32
|
type Pluggable = any; // TODO fix this asap
|
|
33
33
|
|
|
34
|
-
type SimpleProcessorResult = {
|
|
34
|
+
export type SimpleProcessorResult = {
|
|
35
|
+
content: string;
|
|
36
|
+
data: {[key: string]: unknown};
|
|
37
|
+
};
|
|
35
38
|
|
|
36
39
|
// TODO alt interface because impossible to import type Processor (ESM + TS :/)
|
|
37
|
-
type SimpleProcessor = {
|
|
40
|
+
export type SimpleProcessor = {
|
|
38
41
|
process: ({
|
|
39
42
|
content,
|
|
40
43
|
filePath,
|
|
@@ -219,63 +222,64 @@ export async function createProcessorUncached(parameters: {
|
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
// We use different compilers depending on the file type (md vs mdx)
|
|
222
|
-
type
|
|
225
|
+
export type SimpleProcessors = {
|
|
223
226
|
mdProcessor: SimpleProcessor;
|
|
224
227
|
mdxProcessor: SimpleProcessor;
|
|
225
228
|
};
|
|
226
229
|
|
|
227
230
|
// Compilers are cached so that Remark/Rehype plugins can run
|
|
228
231
|
// expensive code during initialization
|
|
229
|
-
const ProcessorsCache = new Map<string | Options,
|
|
232
|
+
const ProcessorsCache = new Map<string | Options, SimpleProcessors>();
|
|
230
233
|
|
|
231
|
-
async function
|
|
232
|
-
|
|
233
|
-
reqOptions,
|
|
234
|
+
export async function createProcessors({
|
|
235
|
+
options,
|
|
234
236
|
}: {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}): Promise<ProcessorsCacheEntry> {
|
|
237
|
+
options: Options;
|
|
238
|
+
}): Promise<SimpleProcessors> {
|
|
238
239
|
const {createProcessorSync} = await createProcessorFactory();
|
|
239
|
-
|
|
240
|
-
const compilers = ProcessorsCache.get(query);
|
|
241
|
-
if (compilers) {
|
|
242
|
-
return compilers;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const compilerCacheEntry: ProcessorsCacheEntry = {
|
|
240
|
+
return {
|
|
246
241
|
mdProcessor: createProcessorSync({
|
|
247
|
-
options
|
|
242
|
+
options,
|
|
248
243
|
format: 'md',
|
|
249
244
|
}),
|
|
250
245
|
mdxProcessor: createProcessorSync({
|
|
251
|
-
options
|
|
246
|
+
options,
|
|
252
247
|
format: 'mdx',
|
|
253
248
|
}),
|
|
254
249
|
};
|
|
250
|
+
}
|
|
255
251
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
252
|
+
async function createProcessorsCacheEntry({
|
|
253
|
+
options,
|
|
254
|
+
}: {
|
|
255
|
+
options: Options;
|
|
256
|
+
}): Promise<SimpleProcessors> {
|
|
257
|
+
const compilers = ProcessorsCache.get(options);
|
|
258
|
+
if (compilers) {
|
|
259
|
+
return compilers;
|
|
260
|
+
}
|
|
261
|
+
const processors = await createProcessors({options});
|
|
262
|
+
ProcessorsCache.set(options, processors);
|
|
263
|
+
return processors;
|
|
259
264
|
}
|
|
260
265
|
|
|
261
|
-
export async function
|
|
266
|
+
export async function getProcessor({
|
|
262
267
|
filePath,
|
|
263
268
|
mdxFrontMatter,
|
|
264
|
-
|
|
265
|
-
reqOptions,
|
|
269
|
+
options,
|
|
266
270
|
}: {
|
|
267
271
|
filePath: string;
|
|
268
272
|
mdxFrontMatter: MDXFrontMatter;
|
|
269
|
-
|
|
270
|
-
reqOptions: Options;
|
|
273
|
+
options: Options;
|
|
271
274
|
}): Promise<SimpleProcessor> {
|
|
272
|
-
const
|
|
275
|
+
const processors =
|
|
276
|
+
options.processors ?? (await createProcessorsCacheEntry({options}));
|
|
273
277
|
|
|
274
278
|
const format = getFormat({
|
|
275
279
|
filePath,
|
|
276
280
|
frontMatterFormat: mdxFrontMatter.format,
|
|
277
|
-
markdownConfigFormat:
|
|
281
|
+
markdownConfigFormat: options.markdownConfig.format,
|
|
278
282
|
});
|
|
279
283
|
|
|
280
|
-
return format === 'md' ?
|
|
284
|
+
return format === 'md' ? processors.mdProcessor : processors.mdxProcessor;
|
|
281
285
|
}
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
toMessageRelativeFilePath,
|
|
14
14
|
posixPath,
|
|
15
15
|
escapePath,
|
|
16
|
-
getFileLoaderUtils,
|
|
17
16
|
findAsyncSequential,
|
|
17
|
+
getFileLoaderUtils,
|
|
18
18
|
} from '@docusaurus/utils';
|
|
19
19
|
import escapeHtml from 'escape-html';
|
|
20
20
|
import sizeOf from 'image-size';
|
|
@@ -27,10 +27,6 @@ import type {MdxJsxTextElement} from 'mdast-util-mdx';
|
|
|
27
27
|
import type {Image} from 'mdast';
|
|
28
28
|
import type {Parent} from 'unist';
|
|
29
29
|
|
|
30
|
-
const {
|
|
31
|
-
loaders: {inlineMarkdownImageFileLoader},
|
|
32
|
-
} = getFileLoaderUtils();
|
|
33
|
-
|
|
34
30
|
type PluginOptions = {
|
|
35
31
|
staticDirs: string[];
|
|
36
32
|
siteDir: string;
|
|
@@ -38,6 +34,7 @@ type PluginOptions = {
|
|
|
38
34
|
|
|
39
35
|
type Context = PluginOptions & {
|
|
40
36
|
filePath: string;
|
|
37
|
+
inlineMarkdownImageFileLoader: string;
|
|
41
38
|
};
|
|
42
39
|
|
|
43
40
|
type Target = [node: Image, index: number, parent: Parent];
|
|
@@ -45,21 +42,21 @@ type Target = [node: Image, index: number, parent: Parent];
|
|
|
45
42
|
async function toImageRequireNode(
|
|
46
43
|
[node]: Target,
|
|
47
44
|
imagePath: string,
|
|
48
|
-
|
|
45
|
+
context: Context,
|
|
49
46
|
) {
|
|
50
47
|
// MdxJsxTextElement => see https://github.com/facebook/docusaurus/pull/8288#discussion_r1125871405
|
|
51
48
|
const jsxNode = node as unknown as MdxJsxTextElement;
|
|
52
49
|
const attributes: MdxJsxTextElement['attributes'] = [];
|
|
53
50
|
|
|
54
51
|
let relativeImagePath = posixPath(
|
|
55
|
-
path.relative(path.dirname(filePath), imagePath),
|
|
52
|
+
path.relative(path.dirname(context.filePath), imagePath),
|
|
56
53
|
);
|
|
57
54
|
relativeImagePath = `./${relativeImagePath}`;
|
|
58
55
|
|
|
59
56
|
const parsedUrl = url.parse(node.url);
|
|
60
57
|
const hash = parsedUrl.hash ?? '';
|
|
61
58
|
const search = parsedUrl.search ?? '';
|
|
62
|
-
const requireString = `${inlineMarkdownImageFileLoader}${
|
|
59
|
+
const requireString = `${context.inlineMarkdownImageFileLoader}${
|
|
63
60
|
escapePath(relativeImagePath) + search
|
|
64
61
|
}`;
|
|
65
62
|
if (node.alt) {
|
|
@@ -186,21 +183,26 @@ async function processImageNode(target: Target, context: Context) {
|
|
|
186
183
|
// We try to convert image urls without protocol to images with require calls
|
|
187
184
|
// going through webpack ensures that image assets exist at build time
|
|
188
185
|
const imagePath = await getImageAbsolutePath(parsedUrl.pathname, context);
|
|
189
|
-
await toImageRequireNode(target, imagePath, context
|
|
186
|
+
await toImageRequireNode(target, imagePath, context);
|
|
190
187
|
}
|
|
191
188
|
|
|
192
189
|
export default function plugin(options: PluginOptions): Transformer {
|
|
193
190
|
return async (root, vfile) => {
|
|
194
191
|
const {visit} = await import('unist-util-visit');
|
|
195
192
|
|
|
193
|
+
const fileLoaderUtils = getFileLoaderUtils(
|
|
194
|
+
vfile.data.compilerName === 'server',
|
|
195
|
+
);
|
|
196
|
+
const context: Context = {
|
|
197
|
+
...options,
|
|
198
|
+
filePath: vfile.path!,
|
|
199
|
+
inlineMarkdownImageFileLoader:
|
|
200
|
+
fileLoaderUtils.loaders.inlineMarkdownImageFileLoader,
|
|
201
|
+
};
|
|
202
|
+
|
|
196
203
|
const promises: Promise<void>[] = [];
|
|
197
204
|
visit(root, 'image', (node: Image, index, parent) => {
|
|
198
|
-
promises.push(
|
|
199
|
-
processImageNode([node, index, parent!], {
|
|
200
|
-
...options,
|
|
201
|
-
filePath: vfile.path!,
|
|
202
|
-
}),
|
|
203
|
-
);
|
|
205
|
+
promises.push(processImageNode([node, index, parent!], context));
|
|
204
206
|
});
|
|
205
207
|
await Promise.all(promises);
|
|
206
208
|
};
|