@astrojs/mdx
Version:
Add support for MDX pages in your Astro site
153 lines (152 loc) • 5.93 kB
JavaScript
import { pathToFileURL } from "node:url";
import { isFrontmatterValid } from "@astrojs/internal-helpers/frontmatter";
import {
satteriCollectImagesPlugin,
satteriCreateHighlightFn,
satteriHeadingIdsPlugin,
satteriHighlightPlugin
} from "@astrojs/markdown-satteri";
import { createDefaultAstroMetadata } from "astro/markdown";
import {
mdxToJs
} from "satteri";
import { ASTRO_IMAGE_IMPORT, USES_ASTRO_IMAGE_FLAG } from "../image-constants.js";
import { shouldAddCharset } from "./charset.js";
import { createAstroMetadataPlugin } from "./hast-astro-metadata.js";
import { createImageToComponentPlugin } from "./hast-images-to-component.js";
function createMdxProcessor(mdxOptions, satteriOptions, ctx) {
let highlightFn;
let initPromise;
function initHighlighter() {
initPromise = satteriCreateHighlightFn(mdxOptions.syntaxHighlight, mdxOptions.shikiConfig).then(
(fn) => {
highlightFn = fn;
}
);
}
return {
async process(content, filePath, frontmatter) {
if (!highlightFn && !initPromise) {
initHighlighter();
}
if (initPromise) await initPromise;
const astroData = {
frontmatter,
headings: [],
localImagePaths: /* @__PURE__ */ new Set(),
remoteImagePaths: /* @__PURE__ */ new Set()
};
const collectImages = satteriCollectImagesPlugin();
const headingIds = satteriHeadingIdsPlugin();
const astroMeta = createAstroMetadataPlugin(filePath);
const imageImportInfo = {
importedImages: /* @__PURE__ */ new Map(),
hasImages: false
};
const imageToComponent = createImageToComponentPlugin(imageImportInfo);
const syntaxHighlight = mdxOptions.syntaxHighlight;
const excludeLangs = typeof syntaxHighlight === "object" ? syntaxHighlight.excludeLangs : void 0;
const allMdastPlugins = [
...satteriOptions.mdastPlugins,
collectImages
];
const hastPlugins = [];
if (highlightFn) {
hastPlugins.push(satteriHighlightPlugin(highlightFn, excludeLangs));
}
if (satteriOptions.hastPlugins.length) {
hastPlugins.push(...satteriOptions.hastPlugins);
}
hastPlugins.push(imageToComponent, headingIds, astroMeta);
let optimizeStatic;
if (mdxOptions.optimize) {
const ignoreElements = typeof mdxOptions.optimize === "object" ? mdxOptions.optimize.ignoreElementNames : void 0;
optimizeStatic = {
component: "Fragment",
prop: "set:html",
...ignoreElements && { ignoreElements }
};
}
const mdxResult = await mdxToJs(content, {
mdastPlugins: allMdastPlugins,
hastPlugins,
optimizeStatic,
features: {
...satteriOptions.features,
// `mdxOptions.gfm`/`smartypants` are always boolean-shaped; skip the override when
// satteri's feature is an object so granular config isn't clobbered.
...typeof satteriOptions.features.gfm === "object" ? {} : { gfm: mdxOptions.gfm !== false },
...typeof satteriOptions.features.smartPunctuation === "object" ? {} : { smartPunctuation: mdxOptions.smartypants !== false }
},
fileURL: pathToFileURL(filePath),
jsxImportSource: "astro",
data: { astro: astroData }
});
let compiled = mdxResult.code;
const astro = mdxResult.data.astro;
const headings = astro?.headings ?? [];
const astroMetadata = mdxResult.data.__astroMetadata ?? createDefaultAstroMetadata();
const resolvedFrontmatter = astro?.frontmatter;
if (!resolvedFrontmatter || !isFrontmatterValid(resolvedFrontmatter)) {
throw new Error(
"[MDX] A S\xE4tteri plugin attempted to inject invalid frontmatter. Ensure `ctx.data.astro.frontmatter` is a valid object that is not `null` or `undefined`."
);
}
compiled = compiled.replace(/^export default MDXContent;\s*$/m, "");
if (imageImportInfo.hasImages) {
compiled += `
import { Image as ${ASTRO_IMAGE_IMPORT} } from "astro:assets";`;
for (const [src, importName] of imageImportInfo.importedImages) {
compiled += `
import ${importName} from ${JSON.stringify(src)};`;
}
compiled += `
export const ${USES_ASTRO_IMAGE_FLAG} = true;`;
}
compiled += `
export const frontmatter = ${JSON.stringify(resolvedFrontmatter)};`;
compiled += `
export function getHeadings() { return ${JSON.stringify(headings)}; }`;
if (resolvedFrontmatter.layout) {
compiled = compiled.replace(/^function MDXContent\(/m, "function __OriginalMDXContent__(");
compiled += `
import { jsx as __astro_layout_jsx__ } from 'astro/jsx-runtime';
import __astro_layout_component__ from ${JSON.stringify(resolvedFrontmatter.layout)};
export default function MDXContent(props) {
const content = __OriginalMDXContent__(props);
const { layout, ...frontmatterContent } = frontmatter;
frontmatterContent.file = file;
frontmatterContent.url = url;
return __astro_layout_jsx__(__astro_layout_component__, {
file,
url,
content: frontmatterContent,
frontmatter: frontmatterContent,
headings: getHeadings(),
'server:root': true,
children: content,
});
}`;
} else if (shouldAddCharset(content, filePath, ctx.srcDir)) {
compiled = compiled.replace(/^function MDXContent\(/m, "function __OriginalMDXContent__(");
compiled += `
import { jsx as __astro_charset_jsx__, jsxs as __astro_charset_jsxs__, Fragment as __astro_charset_Fragment__ } from 'astro/jsx-runtime';
export default function MDXContent(props) {
return __astro_charset_jsxs__(__astro_charset_Fragment__, {
children: [
__astro_charset_jsx__('meta', { charset: 'utf-8' }),
__OriginalMDXContent__(props),
],
});
}`;
}
return {
code: compiled,
astroMetadata
};
}
};
}
export {
createMdxProcessor
};