mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-28 05:58:38 +02:00
feat: upgrade to MDX v2 (#8288)
Co-authored-by: Armano <armano2@users.noreply.github.com>
This commit is contained in:
parent
10f161d578
commit
bf913aea2a
161 changed files with 4028 additions and 2821 deletions
|
@ -45,7 +45,7 @@ exports[`toc remark plugin escapes inline code 1`] = `
|
|||
id: 'divitestidiv-1',
|
||||
level: 2
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
## \`<Head />\`
|
||||
|
||||
|
@ -78,7 +78,7 @@ exports[`toc remark plugin exports even with existing name 1`] = `
|
|||
id: 'avengers',
|
||||
level: 3
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
## Thanos
|
||||
|
||||
|
@ -89,11 +89,11 @@ exports[`toc remark plugin exports even with existing name 1`] = `
|
|||
`;
|
||||
|
||||
exports[`toc remark plugin handles empty headings 1`] = `
|
||||
"export const toc = [];
|
||||
"export const toc = []
|
||||
|
||||
# Ignore this
|
||||
|
||||
##
|
||||
##
|
||||
|
||||
## 
|
||||
"
|
||||
|
@ -120,7 +120,7 @@ export const toc = [
|
|||
id: 'again',
|
||||
level: 3
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
## Title
|
||||
|
||||
|
@ -133,7 +133,7 @@ Content.
|
|||
`;
|
||||
|
||||
exports[`toc remark plugin outputs empty array for no TOC 1`] = `
|
||||
"export const toc = [];
|
||||
"export const toc = []
|
||||
|
||||
foo
|
||||
|
||||
|
@ -172,9 +172,9 @@ exports[`toc remark plugin works on non text phrasing content 1`] = `
|
|||
id: 'inlinecode',
|
||||
level: 2
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
## _Emphasis_
|
||||
## *Emphasis*
|
||||
|
||||
### **Importance**
|
||||
|
||||
|
@ -208,7 +208,7 @@ exports[`toc remark plugin works on text content 1`] = `
|
|||
id: 'i--unicode',
|
||||
level: 2
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
### Endi
|
||||
|
||||
|
|
|
@ -6,22 +6,25 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import remark from 'remark';
|
||||
import mdx from 'remark-mdx';
|
||||
import vfile from 'to-vfile';
|
||||
import plugin from '../index';
|
||||
import headings from '../../headings/index';
|
||||
|
||||
const processFixture = async (name: string) => {
|
||||
const {remark} = await import('remark');
|
||||
const {default: gfm} = await import('remark-gfm');
|
||||
const {default: mdx} = await import('remark-mdx');
|
||||
|
||||
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
|
||||
const file = await vfile.read(filePath);
|
||||
const result = await remark()
|
||||
.use(headings)
|
||||
.use(gfm)
|
||||
.use(mdx)
|
||||
.use(plugin)
|
||||
.process(file);
|
||||
|
||||
return result.toString();
|
||||
return result.value;
|
||||
};
|
||||
|
||||
describe('toc remark plugin', () => {
|
||||
|
|
|
@ -8,28 +8,42 @@
|
|||
import {parse, type ParserOptions} from '@babel/parser';
|
||||
import traverse from '@babel/traverse';
|
||||
import stringifyObject from 'stringify-object';
|
||||
import toString from 'mdast-util-to-string';
|
||||
import visit from 'unist-util-visit';
|
||||
import {toValue} from '../utils';
|
||||
|
||||
import type {Identifier} from '@babel/types';
|
||||
import type {TOCItem} from '../..';
|
||||
import type {Node, Parent} from 'unist';
|
||||
import type {Heading, Literal} from 'mdast';
|
||||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||
import type {Transformer} from 'unified';
|
||||
|
||||
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
||||
// This might change soon, likely after TS 5.2
|
||||
// See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
|
||||
// import type {Plugin} from 'unified';
|
||||
type Plugin = any; // TODO fix this asap
|
||||
|
||||
export type TOCItem = {
|
||||
readonly value: string;
|
||||
readonly id: string;
|
||||
readonly level: number;
|
||||
};
|
||||
|
||||
const parseOptions: ParserOptions = {
|
||||
plugins: ['jsx'],
|
||||
sourceType: 'module',
|
||||
};
|
||||
|
||||
const name = 'toc';
|
||||
|
||||
const isImport = (child: Node): child is Literal => child.type === 'import';
|
||||
const isImport = (child: any): child is Literal =>
|
||||
child.type === 'mdxjsEsm' && child.value.startsWith('import');
|
||||
const hasImports = (index: number) => index > -1;
|
||||
const isExport = (child: Node): child is Literal => child.type === 'export';
|
||||
const isExport = (child: any): child is Literal =>
|
||||
child.type === 'mdxjsEsm' && child.value.startsWith('export');
|
||||
|
||||
const isTarget = (child: Literal) => {
|
||||
interface PluginOptions {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
const isTarget = (child: Literal, name: string) => {
|
||||
let found = false;
|
||||
const ast = parse(child.value, parseOptions);
|
||||
traverse(ast, {
|
||||
|
@ -42,24 +56,23 @@ const isTarget = (child: Literal) => {
|
|||
return found;
|
||||
};
|
||||
|
||||
const getOrCreateExistingTargetIndex = (children: Node[]) => {
|
||||
const getOrCreateExistingTargetIndex = async (
|
||||
children: Node[],
|
||||
name: string,
|
||||
) => {
|
||||
let importsIndex = -1;
|
||||
let targetIndex = -1;
|
||||
|
||||
children.forEach((child, index) => {
|
||||
if (isImport(child)) {
|
||||
importsIndex = index;
|
||||
} else if (isExport(child) && isTarget(child)) {
|
||||
} else if (isExport(child) && isTarget(child, name)) {
|
||||
targetIndex = index;
|
||||
}
|
||||
});
|
||||
|
||||
if (targetIndex === -1) {
|
||||
const target = {
|
||||
default: false,
|
||||
type: 'export',
|
||||
value: `export const ${name} = [];`,
|
||||
};
|
||||
const target = await createExportNode(name, []);
|
||||
|
||||
targetIndex = hasImports(importsIndex) ? importsIndex + 1 : 0;
|
||||
children.splice(targetIndex, 0, target);
|
||||
|
@ -68,31 +81,72 @@ const getOrCreateExistingTargetIndex = (children: Node[]) => {
|
|||
return targetIndex;
|
||||
};
|
||||
|
||||
export default function plugin(): Transformer {
|
||||
return (root) => {
|
||||
const plugin: Plugin = function plugin(
|
||||
options: PluginOptions = {},
|
||||
): Transformer {
|
||||
const name = options.name || 'toc';
|
||||
|
||||
return async (root) => {
|
||||
const {toString} = await import('mdast-util-to-string');
|
||||
const headings: TOCItem[] = [];
|
||||
|
||||
visit(root, 'heading', (child: Heading, index, parent) => {
|
||||
visit(root, 'heading', (child: Heading) => {
|
||||
const value = toString(child);
|
||||
|
||||
// depth: 1 headings are titles and not included in the TOC
|
||||
if (parent !== root || !value || child.depth < 2) {
|
||||
// depth:1 headings are titles and not included in the TOC
|
||||
if (!value || child.depth < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
headings.push({
|
||||
value: toValue(child),
|
||||
value: toValue(child, toString),
|
||||
id: child.data!.id as string,
|
||||
level: child.depth,
|
||||
});
|
||||
});
|
||||
const {children} = root as Parent<Literal>;
|
||||
const targetIndex = getOrCreateExistingTargetIndex(children);
|
||||
const targetIndex = await getOrCreateExistingTargetIndex(children, name);
|
||||
|
||||
if (headings.length) {
|
||||
children[targetIndex]!.value = `export const ${name} = ${stringifyObject(
|
||||
headings,
|
||||
)};`;
|
||||
if (headings?.length) {
|
||||
children[targetIndex] = await createExportNode(name, headings);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
||||
async function createExportNode(name: string, object: any) {
|
||||
const {valueToEstree} = await import('estree-util-value-to-estree');
|
||||
|
||||
return {
|
||||
type: 'mdxjsEsm',
|
||||
value: `export const ${name} = ${stringifyObject(object)}`,
|
||||
data: {
|
||||
estree: {
|
||||
type: 'Program',
|
||||
body: [
|
||||
{
|
||||
type: 'ExportNamedDeclaration',
|
||||
declaration: {
|
||||
type: 'VariableDeclaration',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
name,
|
||||
},
|
||||
init: valueToEstree(object),
|
||||
},
|
||||
],
|
||||
kind: 'const',
|
||||
},
|
||||
specifiers: [],
|
||||
source: null,
|
||||
},
|
||||
],
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue