feat: tabbed CJS/ESM code blocks (#488)

* feat: tabbed CJS/ESM code blocks

* chore: rename ESMCodeBlock to JsCodeBlock
This commit is contained in:
David Sanders 2024-01-16 16:11:52 -08:00 коммит произвёл GitHub
Родитель 40d22e3362
Коммит c36cf61ad4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
3 изменённых файлов: 135 добавлений и 0 удалений

Просмотреть файл

@ -5,6 +5,7 @@ import npm2yarn from '@docusaurus/remark-plugin-npm2yarn';
import apiLabels from './src/transformers/api-labels';
import apiOptionsClass from './src/transformers/api-options-class';
import apiStructurePreviews from './src/transformers/api-structure-previews';
import jsCodeBlocks from './src/transformers/js-code-blocks';
import fiddleEmbedder from './src/transformers/fiddle-embedder';
const config: Config = {
@ -205,6 +206,7 @@ const config: Config = {
apiLabels,
apiOptionsClass,
apiStructurePreviews,
jsCodeBlocks,
fiddleEmbedder,
[npm2yarn, { sync: true, converters: ['yarn'] }],
],

Просмотреть файл

@ -0,0 +1,31 @@
import React from 'react';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
interface JsCodeBlockProps {
cjs: string;
mjs: string;
}
const JsCodeBlock = (props: JsCodeBlockProps) => {
const { cjs, mjs } = props;
const tabValues = [
{ label: 'CJS', value: 'cjs', content: cjs },
{ label: 'ESM', value: 'mjs', content: mjs },
];
return (
<Tabs values={tabValues}>
{tabValues.map(({ content, value }) => {
return (
<TabItem value={value} key={value}>
<CodeBlock className={`language-${value}`}>{content}</CodeBlock>
</TabItem>
);
})}
</Tabs>
);
};
export default JsCodeBlock;

Просмотреть файл

@ -0,0 +1,102 @@
import { Node, Parent } from 'unist';
import { Code } from 'mdast';
import visitParents, { ActionTuple } from 'unist-util-visit-parents';
import { Import } from '../util/interfaces';
const CJS_PREAMBLE = '// CommonJS\n';
const MJS_PREAMBLE = '// ESM\n';
const OPT_OUT_META = 'no-combine';
export default function attacher() {
return transformer;
}
function matchCjsCodeBlock(node: Node): node is Code {
return isCode(node) && node.lang === 'cjs';
}
function matchMjsCodeBlock(node: Node): node is Code {
return isCode(node) && node.lang === 'mjs';
}
const importNode: Import = {
type: 'import',
value: "import JsCodeBlock from '@site/src/components/JsCodeBlock';",
};
async function transformer(tree: Parent) {
let documentHasExistingImport = false;
visitParents(tree, 'import', checkForJsCodeBlockImport);
visitParents(tree, matchCjsCodeBlock, maybeGenerateJsCodeBlock);
if (!documentHasExistingImport) {
tree.children.unshift(importNode);
}
function checkForJsCodeBlockImport(node: Node) {
if (
isImport(node) &&
node.value.includes('@site/src/components/JsCodeBlock')
) {
documentHasExistingImport = true;
}
}
function maybeGenerateJsCodeBlock(
node: Code,
ancestors: Parent[]
): ActionTuple | void {
const parent = ancestors[0];
const idx = parent.children.indexOf(node);
const cjsCodeBlock = node;
const mjsCodeBlock = parent.children[idx + 1];
// Check if the immediate sibling is the mjs code block
if (!matchMjsCodeBlock(mjsCodeBlock)) {
return;
}
// Let blocks explicitly opt-out of being combined
if (
cjsCodeBlock.meta?.split(' ').includes(OPT_OUT_META) ||
mjsCodeBlock.meta?.split(' ').includes(OPT_OUT_META)
) {
return;
}
let cjs = cjsCodeBlock.value;
if (cjs.startsWith(CJS_PREAMBLE)) {
cjs = cjs.slice(CJS_PREAMBLE.length);
}
let mjs = mjsCodeBlock.value;
if (mjs.startsWith(MJS_PREAMBLE)) {
mjs = mjs.slice(MJS_PREAMBLE.length);
}
// Replace the two code blocks with the JsCodeBlock
parent.children.splice(idx, 2, {
type: 'jsx',
value: `<JsCodeBlock
cjs={${JSON.stringify(cjs)}}
mjs={${JSON.stringify(mjs)}}
/>`,
} as Node);
// Return an ActionTuple [Action, Index], where
// Action SKIP means we want to skip visiting these new children
// Index is the index of the AST we want to continue parsing at.
return [visitParents.SKIP, idx + 1];
}
}
function isImport(node: Node): node is Import {
return node.type === 'import';
}
function isCode(node: Node): node is Code {
return node.type === 'code';
}