@azerion/bunosaurus-mdx-loader 0.0.0-stage → 4.0.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 +57 -2
- package/lib/createMDXLoader.d.ts +14 -0
- package/lib/createMDXLoader.d.ts.map +1 -0
- package/lib/createMDXLoader.js +52 -0
- package/lib/createMDXLoader.js.map +1 -0
- package/lib/format.d.ts +14 -0
- package/lib/format.d.ts.map +1 -0
- package/lib/format.js +41 -0
- package/lib/format.js.map +1 -0
- package/lib/frontMatter.d.ts +13 -0
- package/lib/frontMatter.d.ts.map +1 -0
- package/lib/frontMatter.js +23 -0
- package/lib/frontMatter.js.map +1 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +15 -0
- package/lib/index.js.map +1 -0
- package/lib/loader.d.ts +11 -0
- package/lib/loader.d.ts.map +1 -0
- package/lib/loader.js +170 -0
- package/lib/loader.js.map +1 -0
- package/lib/options.d.ts +35 -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 +18 -0
- package/lib/preprocessor.d.ts.map +1 -0
- package/lib/preprocessor.js +34 -0
- package/lib/preprocessor.js.map +1 -0
- package/lib/processor.d.ts +49 -0
- package/lib/processor.d.ts.map +1 -0
- package/lib/processor.js +209 -0
- package/lib/processor.js.map +1 -0
- package/lib/remark/admonitions/index.d.ts +11 -0
- package/lib/remark/admonitions/index.d.ts.map +1 -0
- package/lib/remark/admonitions/index.js +107 -0
- package/lib/remark/admonitions/index.js.map +1 -0
- package/lib/remark/contentTitle/index.d.ts +13 -0
- package/lib/remark/contentTitle/index.d.ts.map +1 -0
- package/lib/remark/contentTitle/index.js +62 -0
- package/lib/remark/contentTitle/index.js.map +1 -0
- package/lib/remark/details/index.d.ts +10 -0
- package/lib/remark/details/index.d.ts.map +1 -0
- package/lib/remark/details/index.js +22 -0
- package/lib/remark/details/index.js.map +1 -0
- package/lib/remark/head/index.d.ts +10 -0
- package/lib/remark/head/index.d.ts.map +1 -0
- package/lib/remark/head/index.js +22 -0
- package/lib/remark/head/index.js.map +1 -0
- package/lib/remark/headings/index.d.ts +14 -0
- package/lib/remark/headings/index.d.ts.map +1 -0
- package/lib/remark/headings/index.js +126 -0
- package/lib/remark/headings/index.js.map +1 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts +11 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts.map +1 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.js +30 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.js.map +1 -0
- package/lib/remark/mermaid/index.d.ts +10 -0
- package/lib/remark/mermaid/index.d.ts.map +1 -0
- package/lib/remark/mermaid/index.js +34 -0
- package/lib/remark/mermaid/index.js.map +1 -0
- package/lib/remark/resolveMarkdownLinks/index.d.ts +33 -0
- package/lib/remark/resolveMarkdownLinks/index.d.ts.map +1 -0
- package/lib/remark/resolveMarkdownLinks/index.js +87 -0
- package/lib/remark/resolveMarkdownLinks/index.js.map +1 -0
- package/lib/remark/toc/index.d.ts +14 -0
- package/lib/remark/toc/index.d.ts.map +1 -0
- package/lib/remark/toc/index.js +115 -0
- package/lib/remark/toc/index.js.map +1 -0
- package/lib/remark/toc/types.d.ts +22 -0
- package/lib/remark/toc/types.d.ts.map +1 -0
- package/lib/remark/toc/types.js +9 -0
- package/lib/remark/toc/types.js.map +1 -0
- package/lib/remark/toc/utils.d.ts +26 -0
- package/lib/remark/toc/utils.d.ts.map +1 -0
- package/lib/remark/toc/utils.js +184 -0
- package/lib/remark/toc/utils.js.map +1 -0
- package/lib/remark/transformImage/index.d.ts +17 -0
- package/lib/remark/transformImage/index.d.ts.map +1 -0
- package/lib/remark/transformImage/index.js +187 -0
- package/lib/remark/transformImage/index.js.map +1 -0
- package/lib/remark/transformLinks/index.d.ts +17 -0
- package/lib/remark/transformLinks/index.d.ts.map +1 -0
- package/lib/remark/transformLinks/index.js +205 -0
- package/lib/remark/transformLinks/index.js.map +1 -0
- package/lib/remark/unusedDirectives/index.d.ts +9 -0
- package/lib/remark/unusedDirectives/index.d.ts.map +1 -0
- package/lib/remark/unusedDirectives/index.js +125 -0
- package/lib/remark/unusedDirectives/index.js.map +1 -0
- package/lib/remark/utils/index.d.ts +18 -0
- package/lib/remark/utils/index.d.ts.map +1 -0
- package/lib/remark/utils/index.js +93 -0
- package/lib/remark/utils/index.js.map +1 -0
- package/lib/serializeJS.d.ts +17 -0
- package/lib/serializeJS.d.ts.map +1 -0
- package/lib/serializeJS.js +24 -0
- package/lib/serializeJS.js.map +1 -0
- package/lib/utils.d.ts +35 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +97 -0
- package/lib/utils.js.map +1 -0
- package/package.json +65 -4
- package/src/createMDXLoader.ts +65 -0
- package/src/format.ts +50 -0
- package/src/frontMatter.ts +31 -0
- package/src/index.ts +44 -0
- package/src/loader.ts +227 -0
- package/src/options.ts +37 -0
- package/src/preprocessor.ts +47 -0
- package/src/processor.ts +306 -0
- package/src/remark/admonitions/LICENSE +45 -0
- package/src/remark/admonitions/README.md +3 -0
- package/src/remark/admonitions/index.ts +138 -0
- package/src/remark/contentTitle/index.ts +76 -0
- package/src/remark/details/index.ts +23 -0
- package/src/remark/head/index.ts +23 -0
- package/src/remark/headings/index.ts +158 -0
- package/src/remark/mdx1Compat/codeCompatPlugin.ts +35 -0
- package/src/remark/mermaid/index.ts +35 -0
- package/src/remark/resolveMarkdownLinks/index.ts +137 -0
- package/src/remark/toc/index.ts +183 -0
- package/src/remark/toc/types.ts +29 -0
- package/src/remark/toc/utils.ts +243 -0
- package/src/remark/transformImage/index.ts +268 -0
- package/src/remark/transformLinks/index.ts +291 -0
- package/src/remark/unusedDirectives/index.ts +171 -0
- package/src/remark/utils/index.ts +99 -0
- package/src/serializeJS.ts +22 -0
- package/src/types.d.mts +40 -0
- package/src/utils.ts +134 -0
|
@@ -0,0 +1,31 @@
|
|
|
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 {
|
|
9
|
+
JoiFrontMatter as Joi,
|
|
10
|
+
validateFrontMatter,
|
|
11
|
+
} from '@docusaurus/utils-validation';
|
|
12
|
+
|
|
13
|
+
import type {FormatInput} from './index';
|
|
14
|
+
|
|
15
|
+
export type MDXFrontMatter = {
|
|
16
|
+
format?: FormatInput;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const DefaultMDXFrontMatter: MDXFrontMatter = {
|
|
20
|
+
format: undefined,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const MDXFrontMatterSchema = Joi.object<MDXFrontMatter>({
|
|
24
|
+
format: Joi.string().equal('md', 'mdx', 'detect').optional(),
|
|
25
|
+
}).default(DefaultMDXFrontMatter);
|
|
26
|
+
|
|
27
|
+
export function validateMDXFrontMatter(frontMatter: unknown): MDXFrontMatter {
|
|
28
|
+
return validateFrontMatter(frontMatter, MDXFrontMatterSchema, {
|
|
29
|
+
allowUnknown: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
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 {ReactNode} from 'react';
|
|
9
|
+
|
|
10
|
+
import {mdxLoader} from './loader';
|
|
11
|
+
|
|
12
|
+
import type {TOCItem as TOCItemImported} from './remark/toc/types';
|
|
13
|
+
|
|
14
|
+
export {createMDXLoaderRule, createMDXLoaderItem} from './createMDXLoader';
|
|
15
|
+
|
|
16
|
+
export default mdxLoader;
|
|
17
|
+
|
|
18
|
+
export type TOCItem = TOCItemImported;
|
|
19
|
+
|
|
20
|
+
export type Format = 'md' | 'mdx';
|
|
21
|
+
|
|
22
|
+
export type FormatInput = Format | 'detect';
|
|
23
|
+
|
|
24
|
+
export type LoadedMDXContent<FrontMatter, Metadata, Assets = undefined> = {
|
|
25
|
+
/** As verbatim declared in the MDX document. */
|
|
26
|
+
readonly frontMatter: FrontMatter;
|
|
27
|
+
/** As provided by the content plugin. */
|
|
28
|
+
readonly metadata: Metadata;
|
|
29
|
+
/** A list of TOC items (headings). */
|
|
30
|
+
readonly toc: readonly TOCItem[];
|
|
31
|
+
/** First h1 title before any content. */
|
|
32
|
+
readonly contentTitle: string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Usually image assets that may be collocated like `./img/thumbnail.png`.
|
|
35
|
+
* The loader would also bundle these assets and the client should use these
|
|
36
|
+
* in priority.
|
|
37
|
+
*/
|
|
38
|
+
readonly assets: Assets;
|
|
39
|
+
(): ReactNode;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type {MDXPlugin} from './loader';
|
|
43
|
+
export type {MDXOptions} from './processor';
|
|
44
|
+
export type {Options} from './options';
|
package/src/loader.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
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 logger from '@docusaurus/logger';
|
|
9
|
+
import {
|
|
10
|
+
aliasedSitePath,
|
|
11
|
+
DEFAULT_PARSE_FRONT_MATTER,
|
|
12
|
+
getFileLoaderUtils,
|
|
13
|
+
getWebpackLoaderCompilerName,
|
|
14
|
+
} from '@docusaurus/utils';
|
|
15
|
+
import {
|
|
16
|
+
compileToJSX,
|
|
17
|
+
createAssetsExportCode,
|
|
18
|
+
extractContentTitleData,
|
|
19
|
+
} from './utils';
|
|
20
|
+
import {serializeJS} from './serializeJS';
|
|
21
|
+
import type {WebpackCompilerName} from '@docusaurus/utils';
|
|
22
|
+
import type {Options} from './options';
|
|
23
|
+
import type {LoaderContext} from 'webpack';
|
|
24
|
+
export type {MDXPlugin} from './processor';
|
|
25
|
+
|
|
26
|
+
async function loadMDX({
|
|
27
|
+
fileContent,
|
|
28
|
+
filePath,
|
|
29
|
+
options,
|
|
30
|
+
compilerName,
|
|
31
|
+
}: {
|
|
32
|
+
fileContent: string;
|
|
33
|
+
filePath: string;
|
|
34
|
+
options: Options;
|
|
35
|
+
compilerName: WebpackCompilerName;
|
|
36
|
+
}): Promise<string> {
|
|
37
|
+
const {frontMatter} = await options.markdownConfig.parseFrontMatter({
|
|
38
|
+
filePath,
|
|
39
|
+
fileContent,
|
|
40
|
+
defaultParseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
|
44
|
+
|
|
45
|
+
const result = await compileToJSX({
|
|
46
|
+
fileContent,
|
|
47
|
+
filePath,
|
|
48
|
+
frontMatter,
|
|
49
|
+
options,
|
|
50
|
+
compilerName,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const contentTitle = extractContentTitleData(result.data);
|
|
54
|
+
|
|
55
|
+
// MDX partials are MDX files starting with _ or in a folder starting with _
|
|
56
|
+
// Partial are not expected to have associated metadata files or front matter
|
|
57
|
+
const isMDXPartial = options.isMDXPartial?.(filePath);
|
|
58
|
+
if (isMDXPartial && hasFrontMatter) {
|
|
59
|
+
const errorMessage = `Docusaurus MDX partial files should not contain front matter.
|
|
60
|
+
Those partial files use the _ prefix as a convention by default, but this is configurable.
|
|
61
|
+
File at ${filePath} contains front matter that will be ignored:
|
|
62
|
+
${JSON.stringify(frontMatter, null, 2)}`;
|
|
63
|
+
|
|
64
|
+
if (!options.isMDXPartialFrontMatterWarningDisabled) {
|
|
65
|
+
const shouldError = process.env.NODE_ENV === 'test' || process.env.CI;
|
|
66
|
+
if (shouldError) {
|
|
67
|
+
throw new Error(errorMessage);
|
|
68
|
+
}
|
|
69
|
+
logger.warn(errorMessage);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const metadataPath = (function getMetadataPath() {
|
|
74
|
+
if (!isMDXPartial) {
|
|
75
|
+
return options.metadataPath?.(filePath);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
})();
|
|
79
|
+
|
|
80
|
+
const assets =
|
|
81
|
+
options.createAssets && !isMDXPartial
|
|
82
|
+
? options.createAssets({filePath, frontMatter})
|
|
83
|
+
: undefined;
|
|
84
|
+
|
|
85
|
+
const fileLoaderUtils = getFileLoaderUtils(compilerName === 'server');
|
|
86
|
+
|
|
87
|
+
// TODO use remark plugins to insert extra exports instead of string concat?
|
|
88
|
+
// cf how the toc is exported
|
|
89
|
+
const exportsCode = `
|
|
90
|
+
export const frontMatter = ${serializeJS(frontMatter)};
|
|
91
|
+
export const contentTitle = ${serializeJS(contentTitle)};
|
|
92
|
+
${
|
|
93
|
+
metadataPath
|
|
94
|
+
? `export {default as metadata} from '${aliasedSitePath(
|
|
95
|
+
metadataPath,
|
|
96
|
+
options.siteDir,
|
|
97
|
+
)}'`
|
|
98
|
+
: ''
|
|
99
|
+
}
|
|
100
|
+
${
|
|
101
|
+
assets
|
|
102
|
+
? `export const assets = ${createAssetsExportCode({
|
|
103
|
+
assets,
|
|
104
|
+
inlineMarkdownAssetImageFileLoader:
|
|
105
|
+
fileLoaderUtils.loaders.inlineMarkdownAssetImageFileLoader,
|
|
106
|
+
})};`
|
|
107
|
+
: ''
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
const code = `
|
|
112
|
+
${exportsCode}
|
|
113
|
+
${result.content}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
return code;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Note: we cache promises instead of strings
|
|
120
|
+
// This is because client/server compilations might be triggered in parallel
|
|
121
|
+
// When this happens for the same file, we don't want to compile it twice
|
|
122
|
+
async function loadMDXWithCaching({
|
|
123
|
+
resource,
|
|
124
|
+
fileContent,
|
|
125
|
+
filePath,
|
|
126
|
+
options,
|
|
127
|
+
compilerName,
|
|
128
|
+
}: {
|
|
129
|
+
resource: string; // path?query#hash
|
|
130
|
+
filePath: string; // path
|
|
131
|
+
fileContent: string;
|
|
132
|
+
options: Options;
|
|
133
|
+
compilerName: WebpackCompilerName;
|
|
134
|
+
}): Promise<string> {
|
|
135
|
+
const {crossCompilerCache} = options;
|
|
136
|
+
if (!crossCompilerCache) {
|
|
137
|
+
return loadMDX({
|
|
138
|
+
fileContent,
|
|
139
|
+
filePath,
|
|
140
|
+
options,
|
|
141
|
+
compilerName,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Note we "resource" as cache key, not "filePath" nor "fileContent"
|
|
146
|
+
// This is because:
|
|
147
|
+
// - the same file can be compiled in different variants (blog.mdx?truncated)
|
|
148
|
+
// - the same content can be processed differently (versioned docs links)
|
|
149
|
+
const cacheKey = resource;
|
|
150
|
+
|
|
151
|
+
// We can clean up the cache and free memory after cache entry consumption
|
|
152
|
+
// We know there are only 2 compilations for the same file
|
|
153
|
+
// Note: once we introduce RSCs we'll probably have 3 compilations
|
|
154
|
+
// Note: we can't use string keys in WeakMap
|
|
155
|
+
// But we could eventually use WeakRef for the values
|
|
156
|
+
const deleteCacheEntry = () => crossCompilerCache.delete(cacheKey);
|
|
157
|
+
|
|
158
|
+
const cacheEntry = crossCompilerCache?.get(cacheKey);
|
|
159
|
+
|
|
160
|
+
// When deduplicating client/server compilations, we always use the client
|
|
161
|
+
// compilation and not the server compilation
|
|
162
|
+
// This is important because the server compilation usually skips some steps
|
|
163
|
+
// Notably: the server compilation does not emit file-loader assets
|
|
164
|
+
// Using the server compilation otherwise leads to broken images
|
|
165
|
+
// See https://github.com/facebook/docusaurus/issues/10544#issuecomment-2390943794
|
|
166
|
+
// See https://github.com/facebook/docusaurus/pull/10553
|
|
167
|
+
// TODO a problem with this: server bundle will use client inline loaders
|
|
168
|
+
// This means server bundle will use ?emit=true for assets
|
|
169
|
+
// We should try to get rid of inline loaders to cleanup this caching logic
|
|
170
|
+
if (compilerName === 'client') {
|
|
171
|
+
const promise = loadMDX({
|
|
172
|
+
fileContent,
|
|
173
|
+
filePath,
|
|
174
|
+
options,
|
|
175
|
+
compilerName,
|
|
176
|
+
});
|
|
177
|
+
if (cacheEntry) {
|
|
178
|
+
promise.then(cacheEntry.resolve, cacheEntry.reject);
|
|
179
|
+
deleteCacheEntry();
|
|
180
|
+
} else {
|
|
181
|
+
const noop = () => {
|
|
182
|
+
throw new Error('this should never be called');
|
|
183
|
+
};
|
|
184
|
+
crossCompilerCache.set(cacheKey, {
|
|
185
|
+
promise,
|
|
186
|
+
resolve: noop,
|
|
187
|
+
reject: noop,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return promise;
|
|
191
|
+
}
|
|
192
|
+
// Server compilation always uses the result of the client compilation above
|
|
193
|
+
else if (compilerName === 'server') {
|
|
194
|
+
if (cacheEntry) {
|
|
195
|
+
deleteCacheEntry();
|
|
196
|
+
return cacheEntry.promise;
|
|
197
|
+
} else {
|
|
198
|
+
const {promise, resolve, reject} = Promise.withResolvers<string>();
|
|
199
|
+
crossCompilerCache.set(cacheKey, {promise, resolve, reject});
|
|
200
|
+
return promise;
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
throw new Error(`Unexpected compilerName=${compilerName}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function mdxLoader(
|
|
208
|
+
this: LoaderContext<Options>,
|
|
209
|
+
fileContent: string,
|
|
210
|
+
): Promise<void> {
|
|
211
|
+
const compilerName = getWebpackLoaderCompilerName(this);
|
|
212
|
+
const callback = this.async();
|
|
213
|
+
const options: Options = this.getOptions();
|
|
214
|
+
options.dependencies?.forEach(this.addDependency);
|
|
215
|
+
try {
|
|
216
|
+
const result = await loadMDXWithCaching({
|
|
217
|
+
resource: this.resource,
|
|
218
|
+
filePath: this.resourcePath,
|
|
219
|
+
fileContent,
|
|
220
|
+
options,
|
|
221
|
+
compilerName,
|
|
222
|
+
});
|
|
223
|
+
return callback(null, result);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return callback(error as Error);
|
|
226
|
+
}
|
|
227
|
+
}
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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 {Options as RemarkMdxOptions} from 'remark-mdx';
|
|
12
|
+
|
|
13
|
+
export type Options = Partial<MDXOptions> & {
|
|
14
|
+
dependencies?: string[];
|
|
15
|
+
|
|
16
|
+
markdownConfig: MarkdownConfig;
|
|
17
|
+
staticDirs: string[];
|
|
18
|
+
siteDir: string;
|
|
19
|
+
isMDXPartial?: (filePath: string) => boolean;
|
|
20
|
+
isMDXPartialFrontMatterWarningDisabled?: boolean;
|
|
21
|
+
removeContentTitle?: boolean;
|
|
22
|
+
metadataPath?: (filePath: string) => string;
|
|
23
|
+
createAssets?: (metadata: {
|
|
24
|
+
filePath: string;
|
|
25
|
+
frontMatter: {[key: string]: unknown};
|
|
26
|
+
}) => {[key: string]: unknown};
|
|
27
|
+
resolveMarkdownLink?: ResolveMarkdownLink;
|
|
28
|
+
// Options passed to remark-mdx (only for the 'mdx' format)
|
|
29
|
+
// Replaces our default {acornOptions: {ecmaVersion: 2025, sourceType: 'module'}}
|
|
30
|
+
remarkMdxOptions?: RemarkMdxOptions;
|
|
31
|
+
|
|
32
|
+
// Will usually be created by "createMDXLoaderItem"
|
|
33
|
+
processors?: SimpleProcessors;
|
|
34
|
+
crossCompilerCache?: Map<string, CrossCompilerCacheEntry>; // MDX => Promise<JSX> cache
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type CrossCompilerCacheEntry = PromiseWithResolvers<string>;
|
|
@@ -0,0 +1,47 @@
|
|
|
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 {
|
|
9
|
+
escapeMarkdownHeadingIds,
|
|
10
|
+
unwrapMdxCodeBlocks,
|
|
11
|
+
admonitionTitleToDirectiveLabel,
|
|
12
|
+
} from '@docusaurus/utils';
|
|
13
|
+
import {normalizeAdmonitionOptions} from './remark/admonitions';
|
|
14
|
+
import type {Options} from './options';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Preprocess the string before passing it to MDX
|
|
18
|
+
* This is not particularly recommended but makes it easier to upgrade to MDX 2
|
|
19
|
+
*/
|
|
20
|
+
export default function preprocessContent({
|
|
21
|
+
fileContent: initialFileContent,
|
|
22
|
+
filePath,
|
|
23
|
+
markdownConfig,
|
|
24
|
+
admonitions,
|
|
25
|
+
}: {
|
|
26
|
+
fileContent: string;
|
|
27
|
+
filePath: string;
|
|
28
|
+
markdownConfig: Options['markdownConfig'];
|
|
29
|
+
admonitions: Options['admonitions'] | undefined;
|
|
30
|
+
}): string {
|
|
31
|
+
let fileContent = initialFileContent;
|
|
32
|
+
if (markdownConfig.preprocessor) {
|
|
33
|
+
fileContent = markdownConfig.preprocessor({
|
|
34
|
+
fileContent,
|
|
35
|
+
filePath,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
fileContent = unwrapMdxCodeBlocks(fileContent);
|
|
39
|
+
if (markdownConfig.mdx1Compat.headingIds) {
|
|
40
|
+
fileContent = escapeMarkdownHeadingIds(fileContent);
|
|
41
|
+
}
|
|
42
|
+
if (markdownConfig.mdx1Compat.admonitions && admonitions) {
|
|
43
|
+
const {keywords} = normalizeAdmonitionOptions(admonitions);
|
|
44
|
+
fileContent = admonitionTitleToDirectiveLabel(fileContent, keywords);
|
|
45
|
+
}
|
|
46
|
+
return fileContent;
|
|
47
|
+
}
|
package/src/processor.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
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 {createProcessor as createMdxProcessor} from '@mdx-js/mdx';
|
|
9
|
+
import frontmatter from 'remark-frontmatter';
|
|
10
|
+
import rehypeRaw from 'rehype-raw';
|
|
11
|
+
import gfm from 'remark-gfm';
|
|
12
|
+
// TODO using fork until PR merged: https://github.com/leebyron/remark-comment/pull/3
|
|
13
|
+
import remarkComment from '@slorber/remark-comment';
|
|
14
|
+
import directive from 'remark-directive';
|
|
15
|
+
import remarkMdx from 'remark-mdx';
|
|
16
|
+
import {VFile} from 'vfile';
|
|
17
|
+
import emoji from 'remark-emoji';
|
|
18
|
+
import headings from './remark/headings';
|
|
19
|
+
import contentTitle from './remark/contentTitle';
|
|
20
|
+
import toc from './remark/toc';
|
|
21
|
+
import transformImage from './remark/transformImage';
|
|
22
|
+
import transformLinks from './remark/transformLinks';
|
|
23
|
+
import resolveMarkdownLinks from './remark/resolveMarkdownLinks';
|
|
24
|
+
import details from './remark/details';
|
|
25
|
+
import head from './remark/head';
|
|
26
|
+
import mermaid from './remark/mermaid';
|
|
27
|
+
import transformAdmonitions from './remark/admonitions';
|
|
28
|
+
import unusedDirectives from './remark/unusedDirectives';
|
|
29
|
+
import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
|
|
30
|
+
import {getFormat} from './format';
|
|
31
|
+
import type {WebpackCompilerName} from '@docusaurus/utils';
|
|
32
|
+
import type {MDXFrontMatter} from './frontMatter';
|
|
33
|
+
import type {Options} from './options';
|
|
34
|
+
import type {AdmonitionOptions} from './remark/admonitions';
|
|
35
|
+
import type {PluginOptions as ResolveMarkdownLinksOptions} from './remark/resolveMarkdownLinks';
|
|
36
|
+
import type {PluginOptions as TransformLinksOptions} from './remark/transformLinks';
|
|
37
|
+
import type {PluginOptions as TransformImageOptions} from './remark/transformImage';
|
|
38
|
+
import type {PluginOptions as UnusedDirectivesOptions} from './remark/unusedDirectives';
|
|
39
|
+
import type {ProcessorOptions} from '@mdx-js/mdx';
|
|
40
|
+
import type {Options as RemarkMdxOptions} from 'remark-mdx';
|
|
41
|
+
import type {Pluggable} from 'unified';
|
|
42
|
+
|
|
43
|
+
export type SimpleProcessorResult = {
|
|
44
|
+
content: string;
|
|
45
|
+
data: {[key: string]: unknown};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type SimpleProcessor = {
|
|
49
|
+
process: ({
|
|
50
|
+
content,
|
|
51
|
+
filePath,
|
|
52
|
+
frontMatter,
|
|
53
|
+
compilerName,
|
|
54
|
+
}: {
|
|
55
|
+
content: string;
|
|
56
|
+
filePath: string;
|
|
57
|
+
frontMatter: {[key: string]: unknown};
|
|
58
|
+
compilerName: WebpackCompilerName;
|
|
59
|
+
}) => Promise<SimpleProcessorResult>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type MDXPlugin = Pluggable;
|
|
63
|
+
|
|
64
|
+
// MDX defaults to ES2024, we opt-in for ES2025 syntax (import attributes...)
|
|
65
|
+
const DefaultRemarkMdxOptions: RemarkMdxOptions = {
|
|
66
|
+
acornOptions: {ecmaVersion: 2025, sourceType: 'module'},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type MDXOptions = {
|
|
70
|
+
admonitions: boolean | Partial<AdmonitionOptions>;
|
|
71
|
+
remarkPlugins: MDXPlugin[];
|
|
72
|
+
rehypePlugins: MDXPlugin[];
|
|
73
|
+
recmaPlugins: MDXPlugin[];
|
|
74
|
+
beforeDefaultRemarkPlugins: MDXPlugin[];
|
|
75
|
+
beforeDefaultRehypePlugins: MDXPlugin[];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function getAdmonitionsPlugins(
|
|
79
|
+
admonitionsOption: MDXOptions['admonitions'],
|
|
80
|
+
): MDXPlugin[] {
|
|
81
|
+
if (admonitionsOption) {
|
|
82
|
+
const plugin: MDXPlugin =
|
|
83
|
+
admonitionsOption === true
|
|
84
|
+
? transformAdmonitions
|
|
85
|
+
: [transformAdmonitions, admonitionsOption];
|
|
86
|
+
return [plugin];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getDefaultRemarkPlugins({options}: {options: Options}): MDXPlugin[] {
|
|
93
|
+
return [
|
|
94
|
+
[
|
|
95
|
+
headings,
|
|
96
|
+
{anchorsMaintainCase: options.markdownConfig.anchors.maintainCase},
|
|
97
|
+
],
|
|
98
|
+
...(options.markdownConfig.emoji ? [emoji] : []),
|
|
99
|
+
toc,
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// MDX's createProcessor() registers remark-mdx without options
|
|
104
|
+
// See https://github.com/mdx-js/mdx/issues/2628
|
|
105
|
+
// unified dedupes plugins by reference: using remark-mdx again reconfigures
|
|
106
|
+
// the existing attacher in place, at its original pipeline position
|
|
107
|
+
// This only applies to the 'mdx' format: we must not add remark-mdx for 'md'
|
|
108
|
+
function getRemarkMdxPlugins({
|
|
109
|
+
options,
|
|
110
|
+
format,
|
|
111
|
+
}: {
|
|
112
|
+
options: Options;
|
|
113
|
+
format: 'md' | 'mdx';
|
|
114
|
+
}): MDXPlugin[] {
|
|
115
|
+
if (format === 'md') {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
return [[remarkMdx, options.remarkMdxOptions ?? DefaultRemarkMdxOptions]];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// /!\ this method is synchronous on purpose
|
|
122
|
+
// Using async code here can create cache entry race conditions!
|
|
123
|
+
function createProcessorUncached({
|
|
124
|
+
options,
|
|
125
|
+
format,
|
|
126
|
+
}: {
|
|
127
|
+
options: Options;
|
|
128
|
+
format: 'md' | 'mdx';
|
|
129
|
+
}): SimpleProcessor {
|
|
130
|
+
const remarkPlugins: MDXPlugin[] = [
|
|
131
|
+
...getRemarkMdxPlugins({options, format}),
|
|
132
|
+
...(options.beforeDefaultRemarkPlugins ?? []),
|
|
133
|
+
frontmatter,
|
|
134
|
+
directive,
|
|
135
|
+
[contentTitle, {removeContentTitle: options.removeContentTitle}],
|
|
136
|
+
...getAdmonitionsPlugins(options.admonitions ?? false),
|
|
137
|
+
...getDefaultRemarkPlugins({options}),
|
|
138
|
+
details,
|
|
139
|
+
head,
|
|
140
|
+
...(options.markdownConfig.mermaid ? [mermaid] : []),
|
|
141
|
+
[
|
|
142
|
+
transformImage,
|
|
143
|
+
{
|
|
144
|
+
staticDirs: options.staticDirs,
|
|
145
|
+
siteDir: options.siteDir,
|
|
146
|
+
onBrokenMarkdownImages:
|
|
147
|
+
options.markdownConfig.hooks.onBrokenMarkdownImages,
|
|
148
|
+
} satisfies TransformImageOptions,
|
|
149
|
+
],
|
|
150
|
+
// TODO merge this with transformLinks?
|
|
151
|
+
options.resolveMarkdownLink
|
|
152
|
+
? [
|
|
153
|
+
resolveMarkdownLinks,
|
|
154
|
+
{
|
|
155
|
+
resolveMarkdownLink: options.resolveMarkdownLink,
|
|
156
|
+
onBrokenMarkdownLinks:
|
|
157
|
+
options.markdownConfig.hooks.onBrokenMarkdownLinks,
|
|
158
|
+
} satisfies ResolveMarkdownLinksOptions,
|
|
159
|
+
]
|
|
160
|
+
: undefined,
|
|
161
|
+
[
|
|
162
|
+
transformLinks,
|
|
163
|
+
{
|
|
164
|
+
staticDirs: options.staticDirs,
|
|
165
|
+
siteDir: options.siteDir,
|
|
166
|
+
onBrokenMarkdownLinks:
|
|
167
|
+
options.markdownConfig.hooks.onBrokenMarkdownLinks,
|
|
168
|
+
} satisfies TransformLinksOptions,
|
|
169
|
+
],
|
|
170
|
+
gfm,
|
|
171
|
+
options.markdownConfig.mdx1Compat.comments ? remarkComment : null,
|
|
172
|
+
...(options.remarkPlugins ?? []),
|
|
173
|
+
[
|
|
174
|
+
unusedDirectives,
|
|
175
|
+
{
|
|
176
|
+
onUnusedMarkdownDirectives:
|
|
177
|
+
options.markdownConfig.hooks.onUnusedMarkdownDirectives,
|
|
178
|
+
} satisfies UnusedDirectivesOptions,
|
|
179
|
+
],
|
|
180
|
+
].filter((plugin): plugin is MDXPlugin => Boolean(plugin));
|
|
181
|
+
|
|
182
|
+
// codeCompatPlugin needs to be applied last after user-provided plugins
|
|
183
|
+
// (after npm2yarn for example)
|
|
184
|
+
remarkPlugins.push(codeCompatPlugin);
|
|
185
|
+
|
|
186
|
+
const rehypePlugins: MDXPlugin[] = [
|
|
187
|
+
...(options.beforeDefaultRehypePlugins ?? []),
|
|
188
|
+
...(options.rehypePlugins ?? []),
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
// Maybe we'll want to introduce default recma plugins later?
|
|
192
|
+
// For example https://github.com/domdomegg/recma-mdx-displayname ?
|
|
193
|
+
const recmaPlugins = [...(options.recmaPlugins ?? [])];
|
|
194
|
+
|
|
195
|
+
if (format === 'md') {
|
|
196
|
+
// This is what permits to embed HTML elements with format 'md'
|
|
197
|
+
// See https://github.com/facebook/docusaurus/pull/8960
|
|
198
|
+
// See https://github.com/mdx-js/mdx/pull/2295#issuecomment-1540085960
|
|
199
|
+
const rehypeRawPlugin: MDXPlugin = [
|
|
200
|
+
rehypeRaw,
|
|
201
|
+
{
|
|
202
|
+
passThrough: [
|
|
203
|
+
'mdxFlowExpression',
|
|
204
|
+
'mdxJsxFlowElement',
|
|
205
|
+
'mdxJsxTextElement',
|
|
206
|
+
'mdxTextExpression',
|
|
207
|
+
'mdxjsEsm',
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
rehypePlugins.unshift(rehypeRawPlugin);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const processorOptions: ProcessorOptions & Options = {
|
|
215
|
+
...options,
|
|
216
|
+
remarkPlugins,
|
|
217
|
+
rehypePlugins,
|
|
218
|
+
recmaPlugins,
|
|
219
|
+
providerImportSource: '@mdx-js/react',
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const mdxProcessor = createMdxProcessor({
|
|
223
|
+
...processorOptions,
|
|
224
|
+
remarkRehypeOptions: options.markdownConfig.remarkRehypeOptions,
|
|
225
|
+
format,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
process: async ({content, filePath, frontMatter, compilerName}) => {
|
|
230
|
+
const vfile = new VFile({
|
|
231
|
+
value: content,
|
|
232
|
+
path: filePath,
|
|
233
|
+
data: {
|
|
234
|
+
frontMatter,
|
|
235
|
+
compilerName,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
return mdxProcessor.process(vfile).then((result) => ({
|
|
239
|
+
content: result.toString(),
|
|
240
|
+
data: result.data,
|
|
241
|
+
}));
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// We use different compilers depending on the file type (md vs mdx)
|
|
247
|
+
export type SimpleProcessors = {
|
|
248
|
+
mdProcessor: SimpleProcessor;
|
|
249
|
+
mdxProcessor: SimpleProcessor;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Compilers are cached so that Remark/Rehype plugins can run
|
|
253
|
+
// expensive code during initialization
|
|
254
|
+
const ProcessorsCache = new Map<string | Options, SimpleProcessors>();
|
|
255
|
+
|
|
256
|
+
export function createProcessors({
|
|
257
|
+
options,
|
|
258
|
+
}: {
|
|
259
|
+
options: Options;
|
|
260
|
+
}): SimpleProcessors {
|
|
261
|
+
return {
|
|
262
|
+
mdProcessor: createProcessorUncached({
|
|
263
|
+
options,
|
|
264
|
+
format: 'md',
|
|
265
|
+
}),
|
|
266
|
+
mdxProcessor: createProcessorUncached({
|
|
267
|
+
options,
|
|
268
|
+
format: 'mdx',
|
|
269
|
+
}),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function createProcessorsCacheEntry({
|
|
274
|
+
options,
|
|
275
|
+
}: {
|
|
276
|
+
options: Options;
|
|
277
|
+
}): SimpleProcessors {
|
|
278
|
+
const compilers = ProcessorsCache.get(options);
|
|
279
|
+
if (compilers) {
|
|
280
|
+
return compilers;
|
|
281
|
+
}
|
|
282
|
+
const processors = createProcessors({options});
|
|
283
|
+
ProcessorsCache.set(options, processors);
|
|
284
|
+
return processors;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function getProcessor({
|
|
288
|
+
filePath,
|
|
289
|
+
mdxFrontMatter,
|
|
290
|
+
options,
|
|
291
|
+
}: {
|
|
292
|
+
filePath: string;
|
|
293
|
+
mdxFrontMatter: MDXFrontMatter;
|
|
294
|
+
options: Options;
|
|
295
|
+
}): SimpleProcessor {
|
|
296
|
+
const processors =
|
|
297
|
+
options.processors ?? createProcessorsCacheEntry({options});
|
|
298
|
+
|
|
299
|
+
const format = getFormat({
|
|
300
|
+
filePath,
|
|
301
|
+
frontMatterFormat: mdxFrontMatter.format,
|
|
302
|
+
markdownConfigFormat: options.markdownConfig.format,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return format === 'md' ? processors.mdProcessor : processors.mdxProcessor;
|
|
306
|
+
}
|