Skip to content

Commit

Permalink
Attempt to upgrade to MDX 3
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronMoat committed Mar 26, 2024
1 parent 5de550e commit c89f934
Show file tree
Hide file tree
Showing 32 changed files with 1,639 additions and 852 deletions.
9 changes: 9 additions & 0 deletions .changeset/few-plums-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'scoobie': major
---

Upgrade to MDX v3 (from v1). scoobie has attempted to patch over any breaking changes, so it may not require manual changes in your package. However:

- MDX v2/v3 have syntactical differences, so you may need to update your MDX files. Review https://mdxjs.com/migrating/v2/#update-mdx-content.
- If you previously installed `@mdx-js/loader` v1 as a peer dependency, remove it. It's now bundled as a dependency in scoobie.
- Any custom tooling or setups, such as plugins, are highly likely to be broken. You may need to update and test.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
dist-storybook-tmp/
lib/
mermaid/
/mermaid/
node_modules/

.DS_Store
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,10 +555,11 @@ This allows you to derive arbitrary components from select parts of the document

```tsx
import { Text } from 'braid-design-system';
import React, { Children } from 'react';
import type { MDXContent } from 'mdx/types';
import { Children } from 'react';
import { WrapperRenderer } from 'scoobie';

export const NodeCount = (Document: MDX.Document) => (
export const NodeCount = (Document: MDXContent) => (
<WrapperRenderer document={Document}>
{({ children }) => (
<Text>{Children.toArray(children).length} top-level node(s)</Text>
Expand Down
22 changes: 10 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@
"version": "15.3.0",
"dependencies": {
"@capsizecss/core": "^4.0.0",
"@mdx-js/react": "^1.6.22",
"@types/mdx-js__react": "^1.5.5",
"@mdx-js/loader": "3.0.1",
"@mdx-js/react": "^3.0.1",
"@types/fs-extra": "^11.0.4",
"@types/mdx": "2.0.11",
"@vanilla-extract/css": "^1.2.3",
"@vanilla-extract/css-utils": "^0.1.1",
"babel-loader": "^9.0.0",
"clsx": "^2.0.0",
"find-up": "^5.0.0",
"fs-extra": "^11.0.0",
"estree-walker": "3.0.3",
"fs-extra": "^11.2.0",
"jsonc-parser": "^3.0.0",
"polished": "^4.1.3",
"prism-react-renderer": "2.3.1",
"react-keyed-flatten-children": "^3.0.0",
"refractor": "^3.4.0",
"remark-gfm": "4.0.0",
"remark-slug": "^6.1.0",
"svgo": "^3.0.0",
"svgo-loader": "^4.0.0",
Expand All @@ -31,8 +34,8 @@
"@changesets/cli": "2.27.1",
"@changesets/get-github-info": "0.6.0",
"@mermaid-js/mermaid-cli": "10.8.0",
"@types/react": "18.2.58",
"@storybook/addon-essentials": "7.6.14",
"@types/react": "18.2.58",
"@types/react-dom": "18.2.19",
"braid-design-system": "32.16.3",
"loki": "0.34.0",
Expand All @@ -41,8 +44,7 @@
"react-helmet-async": "1.3.0",
"react-router-dom": "6.22.0",
"sku": "12.4.10",
"webpack": "5.90.1",
"@mdx-js/loader": "^1.6.22"
"webpack": "5.90.1"
},
"files": [
"src",
Expand All @@ -57,15 +59,11 @@
"braid-design-system": ">= 31.21.0",
"react": ">= 17 < 19",
"react-router-dom": ">= 5.3.0",
"sku": ">= 10.13.4 < 13",
"@mdx-js/loader": "^1.6.22"
"sku": ">= 10.13.4 < 13"
},
"peerDependenciesMeta": {
"@mermaid-js/mermaid-cli": {
"optional": true
},
"@mdx-js/loader": {
"optional": true
}
},
"repository": {
Expand Down
65 changes: 65 additions & 0 deletions recma/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { PrivateIdentifier, Program, Property } from 'estree';
import type { Plugin, Transformer } from 'unified';

// TODO: I've committed React crimes here with no keys

function recmaPluginMarkdownArrays(): Transformer<Program> {
return async function transformer(ast) {
const { walk } = await import('estree-walker');

walk(ast, {
enter(node) {
// Avoid wrapping arrays in fragments so our react elements can pass arrays to <Stack /> for Braid to be happy
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name.includes('jsx')
) {
if (
node.arguments[0].type === 'Identifier' &&
node.arguments[0].name === '_createMdxContent'
) {
node.callee = {
type: 'Identifier',
name: '_createMdxContent',
};
node.arguments = [
{
type: 'Identifier',
name: 'props',
},
];
} else if (
node.arguments[0].type === 'Identifier' &&
node.arguments[0].name === '_Fragment' &&
node.arguments[1].type === 'ObjectExpression'
) {
const children = node.arguments[1].properties.find(
(prop): prop is Property =>
prop.type === 'Property' &&
(prop.key as PrivateIdentifier).name === 'children',
);
// @ts-expect-error --
delete node.arguments;
// @ts-expect-error
delete node.callee;
// @ts-expect-error
node.type = 'ArrayExpression';
// @ts-expect-error
node.elements = children?.value?.elements;
}
}

if (node.type === 'ArrayExpression') {
node.elements = node.elements.filter(
(elem) => elem?.type !== 'Literal' || elem.value !== '\n',
);
}
},
});
};
}

export const recmaPlugins: Array<Plugin<[], Program>> = [
recmaPluginMarkdownArrays,
];
118 changes: 118 additions & 0 deletions rehype/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Stolen from https://github.com/remcohaszing/rehype-mdx-code-props/blob/main/src/rehype-mdx-code-props.ts to get back old behaviour of passing meta

import type {
ExpressionStatement,
JSXAttribute,
JSXElement,
JSXSpreadAttribute,
Program,
} from 'estree-jsx';
import type { Root } from 'hast';
import { toEstree } from 'hast-util-to-estree';
import type { Plugin } from 'unified';
import { visitParents } from 'unist-util-visit-parents';

/**
* @internal
*/
declare module 'hast' {
interface ElementData {
/**
* Code meta defined by the mdast.
*/
meta?: string;
}
}

type JSXAttributes = Array<JSXAttribute | JSXSpreadAttribute>;

/**
* Get the JSX attributes for an estree program containing just a single JSX element.
*
* @param program
* The estree program
* @returns
* The JSX attributes of the JSX element.
*/
function getOpeningAttributes(program: Program): JSXAttributes {
const { expression } = program.body[0] as ExpressionStatement;
const { openingElement } = expression as JSXElement;
return openingElement.attributes;
}

export interface RehypeMdxCodePropsOptions {
/**
* The tag name to add the attributes to.
*
* @default 'pre'
*/
tagName?: 'code' | 'pre';
}

/**
* An MDX rehype plugin for transforming markdown code meta into JSX props.
*/
const rehypeMdxCodeProps: Plugin<[RehypeMdxCodePropsOptions?], Root> = ({
tagName = 'pre',
} = {}) => {
if (tagName !== 'code' && tagName !== 'pre') {
throw new Error(`Expected tagName to be 'code' or 'pre', got: ${tagName}`);
}

return (ast) => {
visitParents(ast, 'element', (node, ancestors) => {
if (node.tagName !== 'code') {
return;
}

const meta = node.data?.meta;
if (typeof meta !== 'string') {
return;
}

if (!meta) {
return;
}

let child = node;
let parent = ancestors.at(-1)!;

if (tagName === 'pre') {
if (parent.type !== 'element') {
return;
}

if (parent.tagName !== 'pre') {
return;
}

if (parent.children.length !== 1) {
return;
}

child = parent;
parent = ancestors.at(-2)!;
}

const attribute: JSXAttribute = {
type: 'JSXAttribute',
name: { type: 'JSXIdentifier', name: 'meta' },
value: {
type: 'Literal',
value: meta,
},
};

const estree = toEstree(child);
getOpeningAttributes(estree).push(attribute);

parent.children[parent.children.indexOf(child)] = {
type: 'mdxFlowExpression',
value: '',
data: { estree },
};
});
};
};

export const rehypePlugins = [rehypeMdxCodeProps];
2 changes: 1 addition & 1 deletion remark/formatTableCells.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const containsParagraphNode = (node) =>
*
* This can be disabled by using an explicit `<p>` tag in the cell.
*/
module.exports.formatTableCells = () => (tree) =>
export const formatTableCells = () => (tree) =>
visit(tree, 'table', (node) =>
node.children.forEach((row, rowIndex) =>
row.children.forEach((cell) => {
Expand Down
Loading

0 comments on commit c89f934

Please sign in to comment.