UNPKG

@astrojs/mdx

Version:

Add support for MDX pages in your Astro site

148 lines (147 loc) 5.15 kB
import { createDefaultAstroMetadata, resolvePath } from "astro/markdown"; import { defineHastPlugin } from "satteri"; import { findAttrValue, hasDirective, isComponent } from "./jsx-utils.js"; function exportNameToString(name) { return name.type === "Identifier" ? name.name : String(name.value); } function collectImportsFromEsm(program, imports) { for (const stmt of program.body) { if (stmt.type !== "ImportDeclaration") continue; const source = String(stmt.source.value); let specSet = imports.get(source); if (!specSet) { specSet = /* @__PURE__ */ new Set(); imports.set(source, specSet); } for (const spec of stmt.specifiers) { switch (spec.type) { case "ImportDefaultSpecifier": specSet.add({ local: spec.local.name, imported: "default" }); break; case "ImportNamespaceSpecifier": specSet.add({ local: spec.local.name, imported: "*" }); break; case "ImportSpecifier": specSet.add({ local: spec.local.name, imported: exportNameToString(spec.imported) }); break; } } } } function findMatchingImport(tagName, imports) { const tagSpecifier = tagName.split(".")[0]; for (const [source, specs] of imports) { for (const { imported, local } of specs) { if (local === tagSpecifier) { if (tagSpecifier !== tagName) { switch (imported) { case "*": { const accessPath = tagName.slice(tagSpecifier.length + 1); return { name: accessPath, path: source }; } case "default": { const accessPath = tagName.slice(tagSpecifier.length + 1); return { name: `default.${accessPath}`, path: source }; } default: { return { name: tagName, path: source }; } } } return { name: imported, path: source }; } } } } function processJsxNode(node, ctx, imports, filePath) { const tagName = node.name; if (!tagName || !isComponent(tagName)) return; const hasClient = hasDirective(node, "client:"); const hasServerDefer = !hasClient && hasDirective(node, "server:defer"); if (!hasClient && !hasServerDefer) return; const matchedImport = findMatchingImport(tagName, imports); if (!matchedImport) { throw new Error( `Expected a matching import for component \`${tagName}\`. Did you forget to import it?` ); } if (matchedImport.path.endsWith(".astro") && hasClient) { let clientAttr = "client:*"; for (const a of node.attributes) { if (a.type === "mdxJsxAttribute" && a.name.startsWith("client:")) { clientAttr = a.name; break; } } console.warn( `You are attempting to render <${tagName} ${clientAttr} />, but ${tagName} is an Astro component. Astro components do not render in the client and should not have a hydration directive. Please use a framework component for client rendering.` ); } const resolvedPath = resolvePath(matchedImport.path, filePath); const exportName = matchedImport.name === "*" ? tagName.split(".").slice(1).join(".") : matchedImport.name; const metadata = ctx.data.__astroMetadata ??= createDefaultAstroMetadata(); if (hasClient && findAttrValue(node, "client:only") !== null) { metadata.clientOnlyComponents.push({ exportName: matchedImport.name, localName: "", specifier: tagName, resolvedPath }); ctx.setProperty(node, "client:display-name", tagName); ctx.setProperty(node, "client:component-path", resolvedPath); ctx.setProperty(node, "client:component-export", exportName); ctx.setProperty(node, "client:component-hydration", ""); } else if (hasClient) { metadata.hydratedComponents.push({ exportName: "*", localName: "", specifier: tagName, resolvedPath }); ctx.setProperty(node, "client:component-path", resolvedPath); ctx.setProperty(node, "client:component-export", exportName); ctx.setProperty(node, "client:component-hydration", ""); } else if (hasServerDefer) { metadata.serverComponents.push({ exportName: matchedImport.name, localName: tagName, specifier: matchedImport.path, resolvedPath }); ctx.setProperty(node, "server:component-directive", "server:defer"); ctx.setProperty(node, "server:component-path", resolvedPath); ctx.setProperty(node, "server:component-export", exportName); } } function createAstroMetadataPlugin(filePath) { const imports = /* @__PURE__ */ new Map(); return defineHastPlugin({ name: "astro-metadata", mdxjsEsm(node) { const program = node.parseExpression(); if (program) { collectImportsFromEsm(program, imports); } }, mdxJsxFlowElement: { filter: [], visit(node, ctx) { processJsxNode(node, ctx, imports, filePath); } }, mdxJsxTextElement: { filter: [], visit(node, ctx) { processJsxNode(node, ctx, imports, filePath); } } }); } export { createAstroMetadataPlugin };