UNPKG

fumadocs-mdx

Version:

The built-in source for Fumadocs

232 lines (228 loc) 6.7 kB
import { countLines } from "../chunk-UCY7OBZG.js"; import { buildMDX } from "../chunk-HWSF4OGZ.js"; import "../chunk-3XN4P23K.js"; import { getGlobPatterns, ident, toImportPath } from "../chunk-OWZSTKKX.js"; import { ValidationError, getGitTimestamp, validate } from "../chunk-GX3THK2Q.js"; import { buildConfig } from "../chunk-XVL4ZQFK.js"; import { fumaMatter } from "../chunk-KVWX6THC.js"; // src/vite/index.ts import { parse } from "querystring"; import { z } from "zod"; import * as fs from "fs/promises"; import * as path from "path"; import { load } from "js-yaml"; // src/vite/generate-glob.ts function generateGlob(name, collection) { const patterns = mapGlobPatterns(getGlobPatterns(collection)); const options = { query: { collection: name }, base: getGlobBase(collection) }; if (collection.type === "meta") { options.import = "default"; } return `import.meta.glob(${JSON.stringify(patterns)}, ${JSON.stringify(options, null, 2)})`; } function mapGlobPatterns(patterns) { return patterns.map((file) => { if (file.startsWith("./")) return file; if (file.startsWith("/")) return `.${file}`; return `./${file}`; }); } function getGlobBase(collection) { let dir = collection.dir; if (Array.isArray(dir)) { if (dir.length !== 1) throw new Error( `[Fumadocs MDX] Vite Plugin doesn't support multiple \`dir\` for a collection at the moment.` ); dir = dir[0]; } if (!dir.startsWith("./") && !dir.startsWith("/")) { return "/" + dir; } return dir; } // src/vite/index.ts var onlySchema = z.literal(["frontmatter", "all"]); function mdx(config, options = {}) { const { generateIndexFile = true, configPath = "source.config.ts" } = options; const loaded = buildConfig(config); async function transformMeta(path2, query, value) { const isJson = path2.endsWith(".json"); const parsed = parse(query); const collection = parsed.collection ? loaded.collections.get(parsed.collection) : void 0; if (!collection) return null; let schema; switch (collection.type) { case "meta": schema = collection.schema; break; case "docs": schema = collection.meta.schema; break; } if (!schema) return null; let data; try { data = isJson ? JSON.parse(value) : load(value); } catch { return null; } const out = await validate( schema, data, { path: path2, source: value }, `invalid data in ${path2}` ); return { code: isJson ? JSON.stringify(out) : `export default ${JSON.stringify(out)}`, map: null }; } async function transformContent(file, query, value) { const matter = fumaMatter(value); const isDevelopment = this.environment.mode === "dev"; const parsed = parse(query); const collection = parsed.collection ? loaded.collections.get(parsed.collection) : void 0; const only = parsed.only ? onlySchema.parse(parsed.only) : "all"; let schema; let mdxOptions; switch (collection?.type) { case "doc": mdxOptions = collection.mdxOptions; schema = collection.schema; break; case "docs": mdxOptions = collection.docs.mdxOptions; schema = collection.docs.schema; break; } if (schema) { matter.data = await validate( schema, matter.data, { source: value, path: file }, `invalid frontmatter in ${file}` ); } if (only === "frontmatter") { return { code: `export const frontmatter = ${JSON.stringify(matter.data)}`, map: null }; } const data = {}; if (loaded.global.lastModifiedTime === "git") { data.lastModified = (await getGitTimestamp(file))?.getTime(); } mdxOptions ??= await loaded.getDefaultMDXOptions(); const lineOffset = isDevelopment ? countLines(matter.matter) : 0; const compiled = await buildMDX( parsed.collection ?? "global", "\n".repeat(lineOffset) + matter.content, { development: isDevelopment, ...mdxOptions, data, filePath: file, frontmatter: matter.data, _compiler: { addDependency: (file2) => { this.addWatchFile(file2); } } } ); return { code: String(compiled.value), map: compiled.map }; } return { name: "fumadocs-mdx", // needed, otherwise other plugins will be executed before our `transform`. enforce: "pre", async buildStart() { if (!generateIndexFile) return; console.log("[Fumadocs MDX] Generating index files"); const outdir = process.cwd(); const outFile = "source.generated.ts"; const lines = [ '/// <reference types="vite/client" />', `import { fromConfig } from 'fumadocs-mdx/runtime/vite';`, `import type * as Config from '${toImportPath(configPath, { relativeTo: outdir })}';`, "", `export const create = fromConfig<typeof Config>();` ]; function docs(name, collection) { const args = [ ident(`doc: ${generateGlob(name, collection.docs)}`), ident(`meta: ${generateGlob(name, collection.meta)}`) ].join(",\n"); return `export const ${name} = create.docs("${name}", { ${args} });`; } function doc(name, collection) { return `export const ${name} = create.doc("${name}", ${generateGlob(name, collection)});`; } function meta(name, collection) { return `export const ${name} = create.meta("${name}", ${generateGlob(name, collection)});`; } for (const [name, collection] of loaded.collections.entries()) { lines.push(""); if (collection.type === "docs") { lines.push(docs(name, collection)); } else if (collection.type === "meta") { lines.push(meta(name, collection)); } else { lines.push(doc(name, collection)); } } await fs.writeFile(path.join(outdir, outFile), lines.join("\n")); }, async transform(value, id) { const [file, query = ""] = id.split("?"); const ext = path.extname(file); try { if ([".yaml", ".json"].includes(ext)) return await transformMeta(file, query, value); if ([".md", ".mdx"].includes(ext)) return await transformContent.call(this, file, query, value); } catch (e) { if (e instanceof ValidationError) { throw new Error(e.toStringFormatted()); } throw e; } } }; } export { mdx as default };