@mintlify/common 1.0.1111 → 1.0.1113
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/dist/mdx/getMDXOptions.d.ts +6 -1
- package/dist/mdx/getMDXOptions.js +27 -9
- package/dist/mdx/plugins/remark/index.d.ts +1 -0
- package/dist/mdx/plugins/remark/index.js +1 -0
- package/dist/mdx/plugins/remark/remarkMdxExpandExpressions.d.ts +94 -0
- package/dist/mdx/plugins/remark/remarkMdxExpandExpressions.js +262 -0
- package/dist/mdx/server-only/getMdx.js +4 -0
- package/dist/mdx/server-only/getSerializeExtras.d.ts +12 -0
- package/dist/mdx/server-only/getSerializeExtras.js +13 -0
- package/dist/mdx/server-only/index.d.ts +1 -0
- package/dist/mdx/server-only/index.js +1 -0
- package/dist/mdx/snippets/resolveImport/resolveComponentWithContent.js +98 -19
- package/dist/mdx/utils.js +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -2
|
@@ -12,11 +12,16 @@ type MDXOptionsData = {
|
|
|
12
12
|
tailwindSelectors?: string[];
|
|
13
13
|
path?: string;
|
|
14
14
|
};
|
|
15
|
-
export declare const getMDXOptions: ({ data, remarkPlugins, rehypePlugins, mdxExtracts, allowedComponents, }: {
|
|
15
|
+
export declare const getMDXOptions: ({ data, remarkPlugins, rehypePlugins, mdxExtracts, allowedComponents, fragmentPlugins, }: {
|
|
16
16
|
data: MDXOptionsData;
|
|
17
17
|
remarkPlugins?: PluggableList;
|
|
18
18
|
rehypePlugins?: PluggableList;
|
|
19
19
|
mdxExtracts?: MdxExtracts;
|
|
20
20
|
allowedComponents?: string[];
|
|
21
|
+
/** See `getSerializeExtras` — plugins fragments compiled by `remarkMdxExpandExpressions` also need. */
|
|
22
|
+
fragmentPlugins?: {
|
|
23
|
+
remarkPlugins: PluggableList;
|
|
24
|
+
rehypePlugins: PluggableList;
|
|
25
|
+
};
|
|
21
26
|
}) => SerializeOptions["mdxOptions"];
|
|
22
27
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { rehypeCodeBlocks, rehypeDynamicTailwindCss, rehypeKeyboardSymbols, rehypeListItemText, rehypeMdxExtractEndpoint, rehypeMdxExtractExamples, rehypeParamFieldIds, rehypeRawComponents, rehypeTable, rehypeUnicodeIds, rehypeZoomImages, remarkExtractChangelogFilters, remarkExtractTableOfContents, remarkFileTree, remarkFrames, remarkComponentIds, remarkMdxInjectSnippets, remarkMdxStyleStringToObject, remarkMdxRemoveUnusedVariables, remarkRemoveImports, remarkMdxExtractPanel, remarkResolveRelativeLinks, remarkVideo, remarkMdxClientComponentBoundaries, remarkExtractMultiView, remarkPrompt, remarkUnwrapInlineJsx, remarkUnwrapJsxHeadings, remarkMapExplicitAnchors, } from './plugins/index.js';
|
|
2
|
+
import { remarkMdxExpandExpressions, } from './plugins/remark/remarkMdxExpandExpressions.js';
|
|
2
3
|
import { remarkMdxRemoveUnknownJsx } from './plugins/remark/remarkMdxRemoveUnknownJsx/index.js';
|
|
3
4
|
import { remarkMermaid } from './plugins/remark/remarkMermaid.js';
|
|
4
5
|
import { remarkNoCopyLanguage } from './plugins/remark/remarkNoCopyLanguage.js';
|
|
@@ -11,20 +12,32 @@ const rehypeExtractors = (mdxExtracts, data) => {
|
|
|
11
12
|
[rehypeMdxExtractEndpoint, data.pageMetadata, data.config, mdxExtracts],
|
|
12
13
|
];
|
|
13
14
|
};
|
|
14
|
-
export const getMDXOptions = ({ data, remarkPlugins = [], rehypePlugins = [], mdxExtracts, allowedComponents = [], }) => {
|
|
15
|
-
|
|
15
|
+
export const getMDXOptions = ({ data, remarkPlugins = [], rehypePlugins = [], mdxExtracts, allowedComponents = [], fragmentPlugins, }) => {
|
|
16
|
+
// `fragment`: the list is compiling the body of an `<MDX>` inside an expression. Page-level
|
|
17
|
+
// extractors that hoist an element out of the tree (`<Panel>`, `<RequestExample>`,
|
|
18
|
+
// `<ResponseExample>`) are skipped there so the element stays in its branch and renders inline
|
|
19
|
+
// through the registered component instead of disappearing into fragment-scoped extracts.
|
|
20
|
+
// `knownComponents`: components the page defines (`export const X = …`, including snippet
|
|
21
|
+
// imports resolved at prebuild). A fragment tree has no exports of its own, so without this the
|
|
22
|
+
// unknown-JSX filter would strip `<X />` inside the fragment.
|
|
23
|
+
const buildPlugins = (extracts, { fragment = false, knownComponents = [], depth = 0, budget = undefined, } = {}) => ({
|
|
16
24
|
remarkPlugins: [
|
|
25
|
+
// first, so `<Snippet file>` is injected on the page and inside every `<MDX>` fragment
|
|
17
26
|
[remarkMdxInjectSnippets, data.snippetTreeMap],
|
|
27
|
+
[
|
|
28
|
+
remarkMdxExpandExpressions,
|
|
29
|
+
{ buildPlugins, extraPlugins: fragmentPlugins, knownComponents, depth, budget },
|
|
30
|
+
],
|
|
18
31
|
remarkMdxStyleStringToObject,
|
|
19
32
|
[remarkResolveRelativeLinks, { path: data.path }],
|
|
20
33
|
remarkPrompt,
|
|
21
34
|
[remarkComponentIds, data.pageMetadata],
|
|
22
35
|
remarkUnwrapJsxHeadings,
|
|
23
36
|
remarkUnwrapInlineJsx,
|
|
24
|
-
[remarkExtractTableOfContents,
|
|
25
|
-
[remarkExtractChangelogFilters,
|
|
26
|
-
[remarkMdxExtractPanel,
|
|
27
|
-
[remarkExtractMultiView,
|
|
37
|
+
[remarkExtractTableOfContents, extracts, data.pageMetadata], // modifies tree so cannot be excluded
|
|
38
|
+
[remarkExtractChangelogFilters, extracts],
|
|
39
|
+
...(fragment ? [] : [[remarkMdxExtractPanel, extracts]]),
|
|
40
|
+
[remarkExtractMultiView, extracts],
|
|
28
41
|
remarkMdxRemoveUnusedVariables,
|
|
29
42
|
remarkFrames,
|
|
30
43
|
remarkFileTree,
|
|
@@ -34,14 +47,14 @@ export const getMDXOptions = ({ data, remarkPlugins = [], rehypePlugins = [], md
|
|
|
34
47
|
remarkVideo,
|
|
35
48
|
...remarkPlugins,
|
|
36
49
|
remarkMapExplicitAnchors,
|
|
37
|
-
[remarkMdxRemoveUnknownJsx, { allowlist: allowedComponents }],
|
|
38
|
-
remarkMdxClientComponentBoundaries,
|
|
50
|
+
[remarkMdxRemoveUnknownJsx, { allowlist: [...allowedComponents, ...knownComponents] }],
|
|
51
|
+
[remarkMdxClientComponentBoundaries, { allowlist: knownComponents }],
|
|
39
52
|
],
|
|
40
53
|
rehypePlugins: [
|
|
41
54
|
rehypeCodeBlocks,
|
|
42
55
|
rehypeKeyboardSymbols,
|
|
43
56
|
rehypeParamFieldIds,
|
|
44
|
-
...rehypeExtractors(
|
|
57
|
+
...(fragment ? [] : rehypeExtractors(extracts, data)),
|
|
45
58
|
rehypeTable,
|
|
46
59
|
rehypeRawComponents,
|
|
47
60
|
rehypeListItemText,
|
|
@@ -50,6 +63,11 @@ export const getMDXOptions = ({ data, remarkPlugins = [], rehypePlugins = [], md
|
|
|
50
63
|
[rehypeDynamicTailwindCss, data.tailwindSelectors],
|
|
51
64
|
...rehypePlugins,
|
|
52
65
|
],
|
|
66
|
+
});
|
|
67
|
+
const plugins = buildPlugins(mdxExtracts);
|
|
68
|
+
return {
|
|
69
|
+
remarkPlugins: plugins.remarkPlugins,
|
|
70
|
+
rehypePlugins: plugins.rehypePlugins,
|
|
53
71
|
format: 'mdx',
|
|
54
72
|
};
|
|
55
73
|
};
|
|
@@ -6,6 +6,7 @@ export * from './remarkRemoveImports.js';
|
|
|
6
6
|
export * from './remarkExtractTableOfContents.js';
|
|
7
7
|
export * from './remarkMdxRemoveUnusedVariables.js';
|
|
8
8
|
export * from './remarkMdxRemoveUnknownJsx/index.js';
|
|
9
|
+
export * from './remarkMdxExpandExpressions.js';
|
|
9
10
|
export * from './remarkMdxClientComponentBoundaries.js';
|
|
10
11
|
export * from './remarkMermaid.js';
|
|
11
12
|
export * from './remarkNoCopyLanguage.js';
|
|
@@ -6,6 +6,7 @@ export * from './remarkRemoveImports.js';
|
|
|
6
6
|
export * from './remarkExtractTableOfContents.js';
|
|
7
7
|
export * from './remarkMdxRemoveUnusedVariables.js';
|
|
8
8
|
export * from './remarkMdxRemoveUnknownJsx/index.js';
|
|
9
|
+
export * from './remarkMdxExpandExpressions.js';
|
|
9
10
|
export * from './remarkMdxClientComponentBoundaries.js';
|
|
10
11
|
export * from './remarkMermaid.js';
|
|
11
12
|
export * from './remarkNoCopyLanguage.js';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
import { type PluggableList } from 'unified';
|
|
3
|
+
import type { MdxExtracts } from '../../../types/index.js';
|
|
4
|
+
export type RemarkMdxExpandExpressionsOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Builds the remark/rehype plugin lists a fragment is compiled with. Called with a
|
|
7
|
+
* fragment-scoped `MdxExtracts` so extractors in the list never overwrite the page's
|
|
8
|
+
* extracts; the table of contents is merged back explicitly (see below).
|
|
9
|
+
*/
|
|
10
|
+
buildPlugins: (mdxExtracts: MdxExtracts, options: {
|
|
11
|
+
fragment: true;
|
|
12
|
+
knownComponents: string[];
|
|
13
|
+
depth: number;
|
|
14
|
+
budget: FragmentBudget;
|
|
15
|
+
}) => {
|
|
16
|
+
remarkPlugins: PluggableList;
|
|
17
|
+
rehypePlugins: PluggableList;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Component names defined by an enclosing document (page exports, incl. snippet imports
|
|
21
|
+
* resolved at prebuild). Set on the fragment pipeline's own instance of this plugin so nested
|
|
22
|
+
* fragments still know them; the page-level instance derives them from the tree.
|
|
23
|
+
*/
|
|
24
|
+
knownComponents?: string[];
|
|
25
|
+
/** Nesting depth of the document being compiled: 0 for the page, +1 per enclosing fragment. */
|
|
26
|
+
depth?: number;
|
|
27
|
+
/** Expansion budget shared by a page compile and every fragment compiled for it. */
|
|
28
|
+
budget?: FragmentBudget;
|
|
29
|
+
/**
|
|
30
|
+
* Plugins `serialize` from `@mintlify/mdx/server` adds around `getMDXOptions` (smartypants,
|
|
31
|
+
* KaTeX, syntax highlighting). Supplied by server-only code (`getSerializeExtras`) so this
|
|
32
|
+
* module stays free of server-only imports and `getMDXOptions` remains client-bundleable.
|
|
33
|
+
*/
|
|
34
|
+
extraPlugins?: {
|
|
35
|
+
remarkPlugins: PluggableList;
|
|
36
|
+
rehypePlugins: PluggableList;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export type FragmentBudget = {
|
|
40
|
+
remaining: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Bounds on the work a single page compile may spend expanding `<MDX>` fragments. Every fragment
|
|
44
|
+
* re-runs the remark/rehype pipeline on its source and text nested `d` levels deep is compiled
|
|
45
|
+
* `d` times, so both are capped and the compile fails closed with a clear error instead of
|
|
46
|
+
* spending unbounded time on a pathological page.
|
|
47
|
+
*/
|
|
48
|
+
export declare const MAX_MDX_FRAGMENT_DEPTH = 8;
|
|
49
|
+
export declare const MAX_MDX_FRAGMENTS_PER_PAGE = 500;
|
|
50
|
+
/**
|
|
51
|
+
* Compile-time expansion of `<MDX>` inside `{…}` expressions.
|
|
52
|
+
*
|
|
53
|
+
* Block-level `<MDX>` children are parsed by the MDX compiler already. Inside an expression such
|
|
54
|
+
* as `{variant === "fos" ? <MDX>…</MDX> : null}` the children are plain JSX text and would render
|
|
55
|
+
* literally. This plugin recovers the raw source between the `<MDX>` tags, dedents it, parses it as
|
|
56
|
+
* MDX and runs the page pipeline (remark + rehype) on the fragment, then splices the resulting JSX
|
|
57
|
+
* back in as the `<MDX>` element's children. The `<MDX>` element itself is kept so the registered
|
|
58
|
+
* component still wraps the output.
|
|
59
|
+
*
|
|
60
|
+
* Must run first in the remark list (right after snippet injection) so the fragment sees the same
|
|
61
|
+
* plugins as page content. The fragment pipeline also prepends the plugins `serialize` from
|
|
62
|
+
* `@mintlify/mdx/server` adds around `getMDXOptions` (smartypants, KaTeX, syntax highlighting).
|
|
63
|
+
* Nested `<MDX>` expressions inside a fragment are expanded recursively because the fragment
|
|
64
|
+
* pipeline includes this plugin again.
|
|
65
|
+
*
|
|
66
|
+
* The expression text is re-parsed with acorn instead of reusing `data.estree` offsets, because
|
|
67
|
+
* expressions injected from snippets have a regenerated estree whose positions no longer index
|
|
68
|
+
* into `value`. `remarkMarkAndUnravel` is a local copy of the @mdx-js/mdx plugin (not exported)
|
|
69
|
+
* that the page pipeline runs before any remark plugin. `mdxjsEsm` statements in a fragment
|
|
70
|
+
* cannot live inside an expression and are dropped. In an inline (`mdxTextExpression`) a lone
|
|
71
|
+
* `<p>` wrapper is unwrapped so it does not nest in the surrounding paragraph.
|
|
72
|
+
*
|
|
73
|
+
* Escaping: the fragment source is JSX text, so a literal brace must be written the JSX way,
|
|
74
|
+
* e.g. `{'{'}` / `{'}'}`; that expression is then evaluated by MDX and renders the brace. For the
|
|
75
|
+
* same reason a custom heading anchor cannot use `## Title {#id}` inside an expression fragment;
|
|
76
|
+
* write `<h2 id="id">Title</h2>` instead. Nothing beyond dedenting is done to the recovered text.
|
|
77
|
+
*
|
|
78
|
+
* Components defined on the page (`export const Local = () => …`, and named snippet imports,
|
|
79
|
+
* which prebuild rewrites into page exports) are passed into the fragment pipeline as known
|
|
80
|
+
* names so the unknown-JSX filter keeps `<Local />` inside a fragment, as it does on the page.
|
|
81
|
+
*
|
|
82
|
+
* Page-level elements: `<Panel>`, `<RequestExample>` and `<ResponseExample>` are hoisted out of a
|
|
83
|
+
* page into extracts by dedicated plugins. A fragment is compiled with those plugins omitted
|
|
84
|
+
* (`buildPlugins(…, { fragment: true })`), so such an element inside an `<MDX>` fragment stays in
|
|
85
|
+
* its branch and renders inline through the registered component. Only the page-level occurrence
|
|
86
|
+
* feeds the side panel / sticky examples.
|
|
87
|
+
*
|
|
88
|
+
* Table of contents: the fragment pipeline runs `remarkExtractTableOfContents` (it also rewrites
|
|
89
|
+
* headings to `<Heading>`, so it cannot be skipped) into a fragment-scoped extracts object that is
|
|
90
|
+
* discarded, so headings inside an expression fragment get ids but are not listed in the page's
|
|
91
|
+
* table of contents yet — that merge is a follow-up PR.
|
|
92
|
+
*/
|
|
93
|
+
export declare const remarkMdxExpandExpressions: (options: RemarkMdxExpandExpressionsOptions) => (tree: Root) => Promise<void>;
|
|
94
|
+
export declare const dedent: (text: string) => string;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { Parser } from 'acorn';
|
|
11
|
+
import jsx from 'acorn-jsx';
|
|
12
|
+
import { jsx as jsxHandlers, toJs } from 'estree-util-to-js';
|
|
13
|
+
import { walk } from 'estree-walker';
|
|
14
|
+
import { toEstree } from 'hast-util-to-estree';
|
|
15
|
+
import remarkRehype from 'remark-rehype';
|
|
16
|
+
import { unified } from 'unified';
|
|
17
|
+
import { visit } from 'unist-util-visit';
|
|
18
|
+
import { visitParents } from 'unist-util-visit-parents';
|
|
19
|
+
import { getAST } from '../../remark.js';
|
|
20
|
+
import { getExportedFunctionNames } from './remarkMdxClientComponentBoundaries.js';
|
|
21
|
+
const REMARK_REHYPE_OPTIONS = {
|
|
22
|
+
allowDangerousHtml: true,
|
|
23
|
+
passThrough: [
|
|
24
|
+
'mdxFlowExpression',
|
|
25
|
+
'mdxJsxFlowElement',
|
|
26
|
+
'mdxJsxTextElement',
|
|
27
|
+
'mdxTextExpression',
|
|
28
|
+
'mdxjsEsm',
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
const JSXParser = Parser.extend(jsx());
|
|
32
|
+
/**
|
|
33
|
+
* Bounds on the work a single page compile may spend expanding `<MDX>` fragments. Every fragment
|
|
34
|
+
* re-runs the remark/rehype pipeline on its source and text nested `d` levels deep is compiled
|
|
35
|
+
* `d` times, so both are capped and the compile fails closed with a clear error instead of
|
|
36
|
+
* spending unbounded time on a pathological page.
|
|
37
|
+
*/
|
|
38
|
+
export const MAX_MDX_FRAGMENT_DEPTH = 8;
|
|
39
|
+
export const MAX_MDX_FRAGMENTS_PER_PAGE = 500;
|
|
40
|
+
/**
|
|
41
|
+
* Compile-time expansion of `<MDX>` inside `{…}` expressions.
|
|
42
|
+
*
|
|
43
|
+
* Block-level `<MDX>` children are parsed by the MDX compiler already. Inside an expression such
|
|
44
|
+
* as `{variant === "fos" ? <MDX>…</MDX> : null}` the children are plain JSX text and would render
|
|
45
|
+
* literally. This plugin recovers the raw source between the `<MDX>` tags, dedents it, parses it as
|
|
46
|
+
* MDX and runs the page pipeline (remark + rehype) on the fragment, then splices the resulting JSX
|
|
47
|
+
* back in as the `<MDX>` element's children. The `<MDX>` element itself is kept so the registered
|
|
48
|
+
* component still wraps the output.
|
|
49
|
+
*
|
|
50
|
+
* Must run first in the remark list (right after snippet injection) so the fragment sees the same
|
|
51
|
+
* plugins as page content. The fragment pipeline also prepends the plugins `serialize` from
|
|
52
|
+
* `@mintlify/mdx/server` adds around `getMDXOptions` (smartypants, KaTeX, syntax highlighting).
|
|
53
|
+
* Nested `<MDX>` expressions inside a fragment are expanded recursively because the fragment
|
|
54
|
+
* pipeline includes this plugin again.
|
|
55
|
+
*
|
|
56
|
+
* The expression text is re-parsed with acorn instead of reusing `data.estree` offsets, because
|
|
57
|
+
* expressions injected from snippets have a regenerated estree whose positions no longer index
|
|
58
|
+
* into `value`. `remarkMarkAndUnravel` is a local copy of the @mdx-js/mdx plugin (not exported)
|
|
59
|
+
* that the page pipeline runs before any remark plugin. `mdxjsEsm` statements in a fragment
|
|
60
|
+
* cannot live inside an expression and are dropped. In an inline (`mdxTextExpression`) a lone
|
|
61
|
+
* `<p>` wrapper is unwrapped so it does not nest in the surrounding paragraph.
|
|
62
|
+
*
|
|
63
|
+
* Escaping: the fragment source is JSX text, so a literal brace must be written the JSX way,
|
|
64
|
+
* e.g. `{'{'}` / `{'}'}`; that expression is then evaluated by MDX and renders the brace. For the
|
|
65
|
+
* same reason a custom heading anchor cannot use `## Title {#id}` inside an expression fragment;
|
|
66
|
+
* write `<h2 id="id">Title</h2>` instead. Nothing beyond dedenting is done to the recovered text.
|
|
67
|
+
*
|
|
68
|
+
* Components defined on the page (`export const Local = () => …`, and named snippet imports,
|
|
69
|
+
* which prebuild rewrites into page exports) are passed into the fragment pipeline as known
|
|
70
|
+
* names so the unknown-JSX filter keeps `<Local />` inside a fragment, as it does on the page.
|
|
71
|
+
*
|
|
72
|
+
* Page-level elements: `<Panel>`, `<RequestExample>` and `<ResponseExample>` are hoisted out of a
|
|
73
|
+
* page into extracts by dedicated plugins. A fragment is compiled with those plugins omitted
|
|
74
|
+
* (`buildPlugins(…, { fragment: true })`), so such an element inside an `<MDX>` fragment stays in
|
|
75
|
+
* its branch and renders inline through the registered component. Only the page-level occurrence
|
|
76
|
+
* feeds the side panel / sticky examples.
|
|
77
|
+
*
|
|
78
|
+
* Table of contents: the fragment pipeline runs `remarkExtractTableOfContents` (it also rewrites
|
|
79
|
+
* headings to `<Heading>`, so it cannot be skipped) into a fragment-scoped extracts object that is
|
|
80
|
+
* discarded, so headings inside an expression fragment get ids but are not listed in the page's
|
|
81
|
+
* table of contents yet — that merge is a follow-up PR.
|
|
82
|
+
*/
|
|
83
|
+
export const remarkMdxExpandExpressions = (options) => (tree) => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
+
var _a, _b, _c;
|
|
85
|
+
const depth = (_a = options.depth) !== null && _a !== void 0 ? _a : 0;
|
|
86
|
+
const expressions = [];
|
|
87
|
+
visitParents(tree, (node, ancestors) => {
|
|
88
|
+
if (node.type !== 'mdxFlowExpression' && node.type !== 'mdxTextExpression')
|
|
89
|
+
return;
|
|
90
|
+
// A page-level <Panel> is stringified back to MDX source by remarkMdxExtractPanel and
|
|
91
|
+
// compiled again on its own (getMdx). Leave its expressions as the author wrote them so
|
|
92
|
+
// that second compile expands them; expanding here would bake generated JSX (<em>, <h5>,
|
|
93
|
+
// …) into the panel source, which the second pass strips as unknown components.
|
|
94
|
+
if (depth === 0 &&
|
|
95
|
+
ancestors.some((a) => a.type === 'mdxJsxFlowElement' && 'name' in a && a.name === 'Panel')) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
expressions.push(node);
|
|
99
|
+
});
|
|
100
|
+
if (expressions.length === 0)
|
|
101
|
+
return;
|
|
102
|
+
const knownComponents = [
|
|
103
|
+
...new Set([...((_b = options.knownComponents) !== null && _b !== void 0 ? _b : []), ...getExportedFunctionNames(tree)]),
|
|
104
|
+
];
|
|
105
|
+
const budget = (_c = options.budget) !== null && _c !== void 0 ? _c : { remaining: MAX_MDX_FRAGMENTS_PER_PAGE };
|
|
106
|
+
for (const node of expressions) {
|
|
107
|
+
yield expandExpression(node, options, { knownComponents, depth, budget });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
const expandExpression = (node_1, options_1, _a) => __awaiter(void 0, [node_1, options_1, _a], void 0, function* (node, options, { knownComponents, depth, budget }) {
|
|
111
|
+
var _b, _c, _d, _e;
|
|
112
|
+
if (!node.value.includes('<MDX'))
|
|
113
|
+
return;
|
|
114
|
+
let expression;
|
|
115
|
+
try {
|
|
116
|
+
expression = JSXParser.parseExpressionAt(node.value, 0, {
|
|
117
|
+
ecmaVersion: 'latest',
|
|
118
|
+
sourceType: 'module',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (_f) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const elements = collectMdxElements(expression);
|
|
125
|
+
if (elements.length === 0)
|
|
126
|
+
return;
|
|
127
|
+
let changed = false;
|
|
128
|
+
for (const element of elements) {
|
|
129
|
+
const opening = element.openingElement;
|
|
130
|
+
const closing = element.closingElement;
|
|
131
|
+
if (!closing || element.children.length === 0)
|
|
132
|
+
continue;
|
|
133
|
+
const raw = dedent(node.value.slice(opening.end, closing.start));
|
|
134
|
+
if (raw.trim() === '')
|
|
135
|
+
continue;
|
|
136
|
+
if (depth >= MAX_MDX_FRAGMENT_DEPTH) {
|
|
137
|
+
throw new Error(`<MDX> fragments are nested more than ${MAX_MDX_FRAGMENT_DEPTH} levels deep; flatten the nesting.`);
|
|
138
|
+
}
|
|
139
|
+
if (budget.remaining <= 0) {
|
|
140
|
+
throw new Error(`This page expands more than ${MAX_MDX_FRAGMENTS_PER_PAGE} <MDX> fragments; split the page or move content into block-level <MDX>.`);
|
|
141
|
+
}
|
|
142
|
+
budget.remaining -= 1;
|
|
143
|
+
const fragmentExtracts = {};
|
|
144
|
+
const { remarkPlugins, rehypePlugins } = options.buildPlugins(fragmentExtracts, {
|
|
145
|
+
fragment: true,
|
|
146
|
+
knownComponents,
|
|
147
|
+
depth: depth + 1,
|
|
148
|
+
budget,
|
|
149
|
+
});
|
|
150
|
+
const processor = unified()
|
|
151
|
+
.use(remarkMarkAndUnravel)
|
|
152
|
+
.use((_c = (_b = options.extraPlugins) === null || _b === void 0 ? void 0 : _b.remarkPlugins) !== null && _c !== void 0 ? _c : [])
|
|
153
|
+
.use(remarkPlugins)
|
|
154
|
+
.use(remarkRehype, REMARK_REHYPE_OPTIONS)
|
|
155
|
+
.use((_e = (_d = options.extraPlugins) === null || _d === void 0 ? void 0 : _d.rehypePlugins) !== null && _e !== void 0 ? _e : [])
|
|
156
|
+
.use(rehypePlugins);
|
|
157
|
+
const hast = (yield processor.run(getAST(raw)));
|
|
158
|
+
if (node.type === 'mdxTextExpression')
|
|
159
|
+
unwrapSingleParagraph(hast);
|
|
160
|
+
const program = toEstree(hast, {
|
|
161
|
+
elementAttributeNameCase: 'react',
|
|
162
|
+
stylePropertyNameCase: 'dom',
|
|
163
|
+
});
|
|
164
|
+
const statement = [...program.body]
|
|
165
|
+
.reverse()
|
|
166
|
+
.find((statement) => statement.type === 'ExpressionStatement');
|
|
167
|
+
if (!statement || statement.type !== 'ExpressionStatement')
|
|
168
|
+
continue;
|
|
169
|
+
const compiled = statement.expression;
|
|
170
|
+
element.children =
|
|
171
|
+
compiled.type === 'JSXFragment'
|
|
172
|
+
? compiled.children
|
|
173
|
+
: [compiled];
|
|
174
|
+
changed = true;
|
|
175
|
+
}
|
|
176
|
+
if (!changed)
|
|
177
|
+
return;
|
|
178
|
+
const program = {
|
|
179
|
+
type: 'Program',
|
|
180
|
+
sourceType: 'module',
|
|
181
|
+
body: [{ type: 'ExpressionStatement', expression }],
|
|
182
|
+
comments: [],
|
|
183
|
+
};
|
|
184
|
+
node.value = toJs(program, { handlers: jsxHandlers }).value.replace(/;\s*$/, '');
|
|
185
|
+
node.data = Object.assign(Object.assign({}, node.data), { estree: program });
|
|
186
|
+
});
|
|
187
|
+
const collectMdxElements = (expression) => {
|
|
188
|
+
const elements = [];
|
|
189
|
+
walk(expression, {
|
|
190
|
+
enter(jsNode) {
|
|
191
|
+
if (isMdxElement(jsNode)) {
|
|
192
|
+
elements.push(jsNode);
|
|
193
|
+
this.skip();
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
return elements;
|
|
198
|
+
};
|
|
199
|
+
const isMdxElement = (node) => node.type === 'JSXElement' &&
|
|
200
|
+
node.openingElement.name.type === 'JSXIdentifier' &&
|
|
201
|
+
node.openingElement.name.name === 'MDX';
|
|
202
|
+
export const dedent = (text) => {
|
|
203
|
+
var _a, _b;
|
|
204
|
+
const lines = text.split('\n');
|
|
205
|
+
let indent = Infinity;
|
|
206
|
+
for (const line of lines) {
|
|
207
|
+
if (line.trim() === '')
|
|
208
|
+
continue;
|
|
209
|
+
const leading = (_b = (_a = line.match(/^[ \t]*/)) === null || _a === void 0 ? void 0 : _a[0].length) !== null && _b !== void 0 ? _b : 0;
|
|
210
|
+
if (leading < indent)
|
|
211
|
+
indent = leading;
|
|
212
|
+
}
|
|
213
|
+
if (!Number.isFinite(indent) || indent === 0)
|
|
214
|
+
return text;
|
|
215
|
+
return lines.map((line) => (line.trim() === '' ? '' : line.slice(indent))).join('\n');
|
|
216
|
+
};
|
|
217
|
+
const unwrapSingleParagraph = (hast) => {
|
|
218
|
+
const children = hast.children.filter((child) => !(child.type === 'text' && /^[\t\n\f\r ]*$/.test(child.value)));
|
|
219
|
+
const only = children[0];
|
|
220
|
+
if (children.length === 1 && (only === null || only === void 0 ? void 0 : only.type) === 'element' && only.tagName === 'p') {
|
|
221
|
+
hast.children = only.children;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const remarkMarkAndUnravel = () => (tree) => {
|
|
225
|
+
visit(tree, (node, index, parent) => {
|
|
226
|
+
if (parent && typeof index === 'number' && node.type === 'paragraph') {
|
|
227
|
+
let all = true;
|
|
228
|
+
let oneOrMore = false;
|
|
229
|
+
for (const child of node.children) {
|
|
230
|
+
if (child.type === 'mdxJsxTextElement' || child.type === 'mdxTextExpression') {
|
|
231
|
+
oneOrMore = true;
|
|
232
|
+
}
|
|
233
|
+
else if (child.type === 'text' && /^[\t\n\f\r ]*$/.test(child.value)) {
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
all = false;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (all && oneOrMore) {
|
|
241
|
+
const newChildren = [];
|
|
242
|
+
for (const child of node.children) {
|
|
243
|
+
if (child.type === 'mdxJsxTextElement') {
|
|
244
|
+
child.type = 'mdxJsxFlowElement';
|
|
245
|
+
}
|
|
246
|
+
if (child.type === 'mdxTextExpression') {
|
|
247
|
+
child.type = 'mdxFlowExpression';
|
|
248
|
+
}
|
|
249
|
+
if (child.type === 'text' && /^[\t\r\n ]+$/.test(child.value))
|
|
250
|
+
continue;
|
|
251
|
+
newChildren.push(child);
|
|
252
|
+
}
|
|
253
|
+
parent.children.splice(index, 1, ...newChildren);
|
|
254
|
+
return index;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
|
|
258
|
+
node.data = Object.assign(Object.assign({}, node.data), { _mdxExplicitJsx: true });
|
|
259
|
+
}
|
|
260
|
+
return undefined;
|
|
261
|
+
});
|
|
262
|
+
};
|
|
@@ -14,6 +14,7 @@ import { codeStylingToThemeOrThemes } from '../getCodeStyling.js';
|
|
|
14
14
|
import { preprocessCustomHeadingIds } from '../preprocessCustomHeadingIds.js';
|
|
15
15
|
import { replaceVariables } from '../replaceVariables.js';
|
|
16
16
|
import { createSnippetTreeMap } from './getMdx/snippets.js';
|
|
17
|
+
import { getSerializeExtras } from './getSerializeExtras.js';
|
|
17
18
|
export function getMdx(_a) {
|
|
18
19
|
return __awaiter(this, arguments, void 0, function* ({ path, content, metadata, snippets, subdomain, codeStyling, config, tailwindSelectors = undefined, pageType = 'default', trace = undefined, customLanguages = [], variables = undefined, rehypePlugins = [], remarkPlugins = [], allowedComponents = [], }) {
|
|
19
20
|
const traceFn = trace !== null && trace !== void 0 ? trace : ((_name, fn) => fn());
|
|
@@ -43,18 +44,21 @@ export function getMdx(_a) {
|
|
|
43
44
|
if (pageType === 'pdf') {
|
|
44
45
|
plugins = [...plugins, remarkExpandContent, remarkSplitCodeGroup, remarkSplitTabs];
|
|
45
46
|
}
|
|
47
|
+
const syntaxHighlightingOptions = Object.assign(Object.assign({}, codeStylingToThemeOrThemes(codeStyling)), { customLanguages });
|
|
46
48
|
const mdxOptions = getMDXOptions({
|
|
47
49
|
data: mdxOptionsData,
|
|
48
50
|
remarkPlugins: plugins,
|
|
49
51
|
rehypePlugins,
|
|
50
52
|
mdxExtracts,
|
|
51
53
|
allowedComponents,
|
|
54
|
+
fragmentPlugins: getSerializeExtras(syntaxHighlightingOptions),
|
|
52
55
|
});
|
|
53
56
|
const mdxOptionsNoJs = getMDXOptions({
|
|
54
57
|
data: mdxOptionsData,
|
|
55
58
|
remarkPlugins: [remarkMdxRemoveJs, ...plugins],
|
|
56
59
|
rehypePlugins,
|
|
57
60
|
allowedComponents,
|
|
61
|
+
fragmentPlugins: getSerializeExtras(syntaxHighlightingOptions),
|
|
58
62
|
});
|
|
59
63
|
const scope = {
|
|
60
64
|
codeStyling,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type RehypeSyntaxHighlightingOptions } from '@mintlify/mdx/plugins';
|
|
2
|
+
import type { PluggableList } from 'unified';
|
|
3
|
+
/**
|
|
4
|
+
* The plugins `serialize` from `@mintlify/mdx/server` wraps around `getMDXOptions`' lists.
|
|
5
|
+
* `remarkMdxExpandExpressions` needs the same set so `<MDX>` fragments compile like page content;
|
|
6
|
+
* it lives here because `rehypeSyntaxHighlighting` is server-only and `getMDXOptions` is bundled
|
|
7
|
+
* for the client.
|
|
8
|
+
*/
|
|
9
|
+
export declare const getSerializeExtras: (syntaxHighlightingOptions?: RehypeSyntaxHighlightingOptions) => {
|
|
10
|
+
remarkPlugins: PluggableList;
|
|
11
|
+
rehypePlugins: PluggableList;
|
|
12
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { rehypeSyntaxHighlighting, } from '@mintlify/mdx/plugins';
|
|
2
|
+
import rehypeKatex from 'rehype-katex';
|
|
3
|
+
import remarkSmartypants from 'remark-smartypants';
|
|
4
|
+
/**
|
|
5
|
+
* The plugins `serialize` from `@mintlify/mdx/server` wraps around `getMDXOptions`' lists.
|
|
6
|
+
* `remarkMdxExpandExpressions` needs the same set so `<MDX>` fragments compile like page content;
|
|
7
|
+
* it lives here because `rehypeSyntaxHighlighting` is server-only and `getMDXOptions` is bundled
|
|
8
|
+
* for the client.
|
|
9
|
+
*/
|
|
10
|
+
export const getSerializeExtras = (syntaxHighlightingOptions) => ({
|
|
11
|
+
remarkPlugins: [remarkSmartypants],
|
|
12
|
+
rehypePlugins: [rehypeKatex, [rehypeSyntaxHighlighting, syntaxHighlightingOptions]],
|
|
13
|
+
});
|