-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: tabbed CJS/ESM code blocks (#488)
* feat: tabbed CJS/ESM code blocks * chore: rename ESMCodeBlock to JsCodeBlock
- Loading branch information
1 parent
40d22e3
commit c36cf61
Showing
3 changed files
with
135 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; | ||
} |