@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.
Files changed (130) hide show
  1. package/README.md +57 -2
  2. package/lib/createMDXLoader.d.ts +14 -0
  3. package/lib/createMDXLoader.d.ts.map +1 -0
  4. package/lib/createMDXLoader.js +52 -0
  5. package/lib/createMDXLoader.js.map +1 -0
  6. package/lib/format.d.ts +14 -0
  7. package/lib/format.d.ts.map +1 -0
  8. package/lib/format.js +41 -0
  9. package/lib/format.js.map +1 -0
  10. package/lib/frontMatter.d.ts +13 -0
  11. package/lib/frontMatter.d.ts.map +1 -0
  12. package/lib/frontMatter.js +23 -0
  13. package/lib/frontMatter.js.map +1 -0
  14. package/lib/index.d.ts +35 -0
  15. package/lib/index.d.ts.map +1 -0
  16. package/lib/index.js +15 -0
  17. package/lib/index.js.map +1 -0
  18. package/lib/loader.d.ts +11 -0
  19. package/lib/loader.d.ts.map +1 -0
  20. package/lib/loader.js +170 -0
  21. package/lib/loader.js.map +1 -0
  22. package/lib/options.d.ts +35 -0
  23. package/lib/options.d.ts.map +1 -0
  24. package/lib/options.js +9 -0
  25. package/lib/options.js.map +1 -0
  26. package/lib/preprocessor.d.ts +18 -0
  27. package/lib/preprocessor.d.ts.map +1 -0
  28. package/lib/preprocessor.js +34 -0
  29. package/lib/preprocessor.js.map +1 -0
  30. package/lib/processor.d.ts +49 -0
  31. package/lib/processor.d.ts.map +1 -0
  32. package/lib/processor.js +209 -0
  33. package/lib/processor.js.map +1 -0
  34. package/lib/remark/admonitions/index.d.ts +11 -0
  35. package/lib/remark/admonitions/index.d.ts.map +1 -0
  36. package/lib/remark/admonitions/index.js +107 -0
  37. package/lib/remark/admonitions/index.js.map +1 -0
  38. package/lib/remark/contentTitle/index.d.ts +13 -0
  39. package/lib/remark/contentTitle/index.d.ts.map +1 -0
  40. package/lib/remark/contentTitle/index.js +62 -0
  41. package/lib/remark/contentTitle/index.js.map +1 -0
  42. package/lib/remark/details/index.d.ts +10 -0
  43. package/lib/remark/details/index.d.ts.map +1 -0
  44. package/lib/remark/details/index.js +22 -0
  45. package/lib/remark/details/index.js.map +1 -0
  46. package/lib/remark/head/index.d.ts +10 -0
  47. package/lib/remark/head/index.d.ts.map +1 -0
  48. package/lib/remark/head/index.js +22 -0
  49. package/lib/remark/head/index.js.map +1 -0
  50. package/lib/remark/headings/index.d.ts +14 -0
  51. package/lib/remark/headings/index.d.ts.map +1 -0
  52. package/lib/remark/headings/index.js +126 -0
  53. package/lib/remark/headings/index.js.map +1 -0
  54. package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts +11 -0
  55. package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts.map +1 -0
  56. package/lib/remark/mdx1Compat/codeCompatPlugin.js +30 -0
  57. package/lib/remark/mdx1Compat/codeCompatPlugin.js.map +1 -0
  58. package/lib/remark/mermaid/index.d.ts +10 -0
  59. package/lib/remark/mermaid/index.d.ts.map +1 -0
  60. package/lib/remark/mermaid/index.js +34 -0
  61. package/lib/remark/mermaid/index.js.map +1 -0
  62. package/lib/remark/resolveMarkdownLinks/index.d.ts +33 -0
  63. package/lib/remark/resolveMarkdownLinks/index.d.ts.map +1 -0
  64. package/lib/remark/resolveMarkdownLinks/index.js +87 -0
  65. package/lib/remark/resolveMarkdownLinks/index.js.map +1 -0
  66. package/lib/remark/toc/index.d.ts +14 -0
  67. package/lib/remark/toc/index.d.ts.map +1 -0
  68. package/lib/remark/toc/index.js +115 -0
  69. package/lib/remark/toc/index.js.map +1 -0
  70. package/lib/remark/toc/types.d.ts +22 -0
  71. package/lib/remark/toc/types.d.ts.map +1 -0
  72. package/lib/remark/toc/types.js +9 -0
  73. package/lib/remark/toc/types.js.map +1 -0
  74. package/lib/remark/toc/utils.d.ts +26 -0
  75. package/lib/remark/toc/utils.d.ts.map +1 -0
  76. package/lib/remark/toc/utils.js +184 -0
  77. package/lib/remark/toc/utils.js.map +1 -0
  78. package/lib/remark/transformImage/index.d.ts +17 -0
  79. package/lib/remark/transformImage/index.d.ts.map +1 -0
  80. package/lib/remark/transformImage/index.js +187 -0
  81. package/lib/remark/transformImage/index.js.map +1 -0
  82. package/lib/remark/transformLinks/index.d.ts +17 -0
  83. package/lib/remark/transformLinks/index.d.ts.map +1 -0
  84. package/lib/remark/transformLinks/index.js +205 -0
  85. package/lib/remark/transformLinks/index.js.map +1 -0
  86. package/lib/remark/unusedDirectives/index.d.ts +9 -0
  87. package/lib/remark/unusedDirectives/index.d.ts.map +1 -0
  88. package/lib/remark/unusedDirectives/index.js +125 -0
  89. package/lib/remark/unusedDirectives/index.js.map +1 -0
  90. package/lib/remark/utils/index.d.ts +18 -0
  91. package/lib/remark/utils/index.d.ts.map +1 -0
  92. package/lib/remark/utils/index.js +93 -0
  93. package/lib/remark/utils/index.js.map +1 -0
  94. package/lib/serializeJS.d.ts +17 -0
  95. package/lib/serializeJS.d.ts.map +1 -0
  96. package/lib/serializeJS.js +24 -0
  97. package/lib/serializeJS.js.map +1 -0
  98. package/lib/utils.d.ts +35 -0
  99. package/lib/utils.d.ts.map +1 -0
  100. package/lib/utils.js +97 -0
  101. package/lib/utils.js.map +1 -0
  102. package/package.json +65 -4
  103. package/src/createMDXLoader.ts +65 -0
  104. package/src/format.ts +50 -0
  105. package/src/frontMatter.ts +31 -0
  106. package/src/index.ts +44 -0
  107. package/src/loader.ts +227 -0
  108. package/src/options.ts +37 -0
  109. package/src/preprocessor.ts +47 -0
  110. package/src/processor.ts +306 -0
  111. package/src/remark/admonitions/LICENSE +45 -0
  112. package/src/remark/admonitions/README.md +3 -0
  113. package/src/remark/admonitions/index.ts +138 -0
  114. package/src/remark/contentTitle/index.ts +76 -0
  115. package/src/remark/details/index.ts +23 -0
  116. package/src/remark/head/index.ts +23 -0
  117. package/src/remark/headings/index.ts +158 -0
  118. package/src/remark/mdx1Compat/codeCompatPlugin.ts +35 -0
  119. package/src/remark/mermaid/index.ts +35 -0
  120. package/src/remark/resolveMarkdownLinks/index.ts +137 -0
  121. package/src/remark/toc/index.ts +183 -0
  122. package/src/remark/toc/types.ts +29 -0
  123. package/src/remark/toc/utils.ts +243 -0
  124. package/src/remark/transformImage/index.ts +268 -0
  125. package/src/remark/transformLinks/index.ts +291 -0
  126. package/src/remark/unusedDirectives/index.ts +171 -0
  127. package/src/remark/utils/index.ts +99 -0
  128. package/src/serializeJS.ts +22 -0
  129. package/src/types.d.mts +40 -0
  130. 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
+ }
@@ -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
+ }