UNPKG

@astrojs/mdx

Version:

Add support for MDX pages in your Astro site

157 lines (156 loc) 5.83 kB
import { SKIP, visit } from "estree-util-visit"; import { toHtml } from "hast-util-to-html"; const exportConstComponentsRe = /export\s+const\s+components\s*=/; const rehypeOptimizeStatic = (options) => { return (tree) => { const ignoreElementNames = new Set(options?.ignoreElementNames); for (const child of tree.children) { if (child.type === "mdxjsEsm" && exportConstComponentsRe.test(child.value)) { const keys = getExportConstComponentObjectKeys(child); if (keys) { for (const key of keys) { ignoreElementNames.add(key); } } break; } } const allPossibleElements = /* @__PURE__ */ new Set(); const elementStack = []; const elementMetadatas = /* @__PURE__ */ new WeakMap(); const isNodeNonStatic = (node) => { return node.type.startsWith("mdx") || // @ts-expect-error `node` should never have `type: 'root'`, but in some cases plugins may inject it as children, // which MDX will render as a fragment instead (an MDX fragment is a `mdxJsxFlowElement` type). node.type === "root" || // @ts-expect-error Access `.tagName` naively for perf ignoreElementNames.has(node.tagName); }; visit(tree, { // @ts-expect-error Force coerce node as hast node enter(node, key, index, parents) { if (key != null && key !== "children") return SKIP; simplifyPlainMdxComponentNode(node, ignoreElementNames); if (isNodeNonStatic(node)) { for (const el of elementStack) { allPossibleElements.delete(el); } elementStack.length = 0; } if (node.type === "element" || isMdxComponentNode(node)) { elementStack.push(node); allPossibleElements.add(node); if (index != null && node.type === "element") { elementMetadatas.set(node, { parent: parents[parents.length - 1], index }); } } }, // @ts-expect-error Force coerce node as hast node leave(node, key, _, parents) { if (key != null && key !== "children") return SKIP; if (node.type === "element" || isMdxComponentNode(node)) { elementStack.pop(); const parent = parents[parents.length - 1]; if (allPossibleElements.has(parent)) { allPossibleElements.delete(node); } } } }); const elementGroups = findElementGroups(allPossibleElements, elementMetadatas, isNodeNonStatic); for (const el of allPossibleElements) { if (el.children.length === 0) continue; if (isMdxComponentNode(el)) { el.attributes.push({ type: "mdxJsxAttribute", name: "set:html", value: toHtml(el.children) }); } else { el.properties["set:html"] = toHtml(el.children); } el.children = []; } for (let i = elementGroups.length - 1; i >= 0; i--) { const group = elementGroups[i]; const fragmentNode = { type: "mdxJsxFlowElement", name: "Fragment", attributes: [ { type: "mdxJsxAttribute", name: "set:html", value: toHtml(group.children) } ], children: [] }; group.parent.children.splice(group.startIndex, group.children.length, fragmentNode); } }; }; function findElementGroups(allPossibleElements, elementMetadatas, isNodeNonStatic) { const elementGroups = []; for (const el of allPossibleElements) { if (isNodeNonStatic(el)) continue; const metadata = elementMetadatas.get(el); if (!metadata) { throw new Error( "Internal MDX error: rehype-optimize-static should have metadata for element node" ); } const groupableElements = [el]; for (let i = metadata.index + 1; i < metadata.parent.children.length; i++) { const node = metadata.parent.children[i]; if (isNodeNonStatic(node)) break; if (node.type === "element") { const existed = allPossibleElements.delete(node); if (!existed) break; } groupableElements.push(node); } if (groupableElements.length > 1) { elementGroups.push({ parent: metadata.parent, startIndex: metadata.index, children: groupableElements }); allPossibleElements.delete(el); } } return elementGroups; } function isMdxComponentNode(node) { return node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement"; } function getExportConstComponentObjectKeys(node) { const exportNamedDeclaration = node.data?.estree?.body[0]; if (exportNamedDeclaration?.type !== "ExportNamedDeclaration") return; const variableDeclaration = exportNamedDeclaration.declaration; if (variableDeclaration?.type !== "VariableDeclaration") return; const variableInit = variableDeclaration.declarations[0]?.init; if (variableInit?.type !== "ObjectExpression") return; const keys = []; for (const propertyNode of variableInit.properties) { if (propertyNode.type === "Property" && propertyNode.key.type === "Identifier") { keys.push(propertyNode.key.name); } } return keys; } function simplifyPlainMdxComponentNode(node, ignoreElementNames) { if (!isMdxComponentNode(node) || // Attributes could be dynamic, so bail if so. node.attributes.length > 0 || // Fragments are also dynamic !node.name || // Ignore if the node name is in the ignore list ignoreElementNames.has(node.name) || // If the node name has uppercase characters, it's likely an actual MDX component node.name.toLowerCase() !== node.name) { return; } const newNode = node; newNode.type = "element"; newNode.tagName = node.name; newNode.properties = {}; node.attributes = void 0; node.data = void 0; } export { rehypeOptimizeStatic };