feat: tabbed CJS/ESM code blocks (#488)
* feat: tabbed CJS/ESM code blocks * chore: rename ESMCodeBlock to JsCodeBlock
This commit is contained in:
Родитель
40d22e3362
Коммит
c36cf61ad4
|
@ -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';
|
||||
}
|
Загрузка…
Ссылка в новой задаче