feat: upgrade to MDX v2 (#8288)

Co-authored-by: Armano <armano2@users.noreply.github.com>
This commit is contained in:
Sébastien Lorber 2023-04-21 19:48:57 +02:00 committed by GitHub
parent 10f161d578
commit bf913aea2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
161 changed files with 4028 additions and 2821 deletions

View file

@ -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
##
##
## ![](an-image.svg)
"
@ -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

View file

@ -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', () => {

View file

@ -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',
},
},
};
}