UNPKG

fumadocs-mdx

Version:

The built-in source for Fumadocs

361 lines (353 loc) 10.9 kB
import { findConfigFile, getConfigHash, loadConfig } from "../chunk-XZR5QXVY.js"; import { getGlobPatterns, getImportCode, isFileSupported, toImportPath } from "../chunk-OWZSTKKX.js"; import { ValidationError, getGitTimestamp, validate } from "../chunk-GX3THK2Q.js"; import "../chunk-XVL4ZQFK.js"; import { fumaMatter } from "../chunk-KVWX6THC.js"; // src/map/index.ts import * as path2 from "path"; import * as fs2 from "fs/promises"; // src/map/generate.ts import * as path from "path"; import * as fs from "fs/promises"; import { glob } from "tinyglobby"; // src/map/file-cache.ts import { LRUCache } from "lru-cache"; var map = new LRUCache({ max: 200 }); var fileCache = { read(namespace, path4) { return map.get(`${namespace}.${path4}`); }, write(namespace, path4, data) { map.set(`${namespace}.${path4}`, data); }, removeCache(path4) { for (const key of map.keys()) { const keyPath = key.slice(key.indexOf(".") + 1); if (keyPath === path4) map.delete(key); } } }; // src/map/generate.ts import { load } from "js-yaml"; async function readFileWithCache(file) { const cached = fileCache.read("read-file", file); if (cached) return cached; return (await fs.readFile(file)).toString(); } async function generateJS(configPath, config, importPath, configHash = false) { let asyncInit = false; const lines = [ getImportCode({ type: "named", names: ["_runtime"], specifier: "fumadocs-mdx" }), getImportCode({ type: "namespace", specifier: toImportPath(configPath, importPath), name: "_source" }) ]; const entries = Array.from(config.collections.entries()); async function getDocEntries(collectionName, files) { const items = files.map(async (file, i) => { const importId = `${collectionName}_${i}`; const params = [`collection=${collectionName}`]; if (configHash) { params.push(`hash=${configHash}`); } lines.unshift( getImportCode({ type: "namespace", name: importId, specifier: `${toImportPath(file.absolutePath, importPath)}?${params.join("&")}` }) ); return `{ info: ${JSON.stringify(file)}, data: ${importId} }`; }); return Promise.all(items); } async function getMetaEntries(collection, files) { const items = files.map(async (file) => { const source = await readFileWithCache(file.absolutePath).catch(() => ""); let data = source.length === 0 ? {} : parseMetaEntry(file.absolutePath, source); if (collection?.schema) { data = await validate( collection.schema, data, { source, path: file.absolutePath }, `invalid data in ${file.absolutePath}` ); } return JSON.stringify({ info: file, data }); }); return Promise.all(items); } async function getAsyncEntries(collection, files) { if (!asyncInit) { lines.unshift( getImportCode({ type: "named", specifier: "fumadocs-mdx/runtime/async", names: ["_runtimeAsync", "buildConfig"] }), "const _sourceConfig = buildConfig(_source)" ); asyncInit = true; } const entries2 = files.map(async (file) => { const parsed = fumaMatter( await readFileWithCache(file.absolutePath).catch(() => "") ); let data = parsed.data; if (collection.schema) { data = await validate( collection.schema, parsed.data, { path: file.absolutePath, source: parsed.content }, `invalid frontmatter in ${file.absolutePath}` ); } let lastModified; if (config.global?.lastModifiedTime === "git") { lastModified = await getGitTimestamp(file.absolutePath); } return JSON.stringify({ info: file, lastModified, data, content: parsed.content }); }); return Promise.all(entries2); } const declares = entries.map(async ([k, collection]) => { if (collection.type === "docs") { const docs = await getCollectionFiles(collection.docs); const metas = await getCollectionFiles(collection.meta); const metaEntries = (await getMetaEntries(collection.meta, metas)).join( ", " ); if (collection.docs.async) { const docsEntries2 = (await getAsyncEntries(collection.docs, docs)).join( ", " ); return `export const ${k} = _runtimeAsync.docs<typeof _source.${k}>([${docsEntries2}], [${metaEntries}], "${k}", _sourceConfig)`; } const docsEntries = (await getDocEntries(k, docs)).join(", "); return `export const ${k} = _runtime.docs<typeof _source.${k}>([${docsEntries}], [${metaEntries}])`; } const files = await getCollectionFiles(collection); if (collection.type === "doc" && collection.async) { return `export const ${k} = _runtimeAsync.doc<typeof _source.${k}>([${(await getAsyncEntries(collection, files)).join(", ")}], "${k}", _sourceConfig)`; } return `export const ${k} = _runtime.${collection.type}<typeof _source.${k}>([${(await getDocEntries(k, files)).join(", ")}]);`; }); const resolvedDeclares = await Promise.all(declares); return [ `// @ts-nocheck -- skip type checking`, ...lines, ...resolvedDeclares ].join("\n"); } async function getCollectionFiles(collection) { const files = /* @__PURE__ */ new Map(); const dirs = Array.isArray(collection.dir) ? collection.dir : [collection.dir]; const patterns = getGlobPatterns(collection); await Promise.all( dirs.map(async (dir) => { const result = await glob(patterns, { cwd: path.resolve(dir), absolute: true }); for (const item of result) { if (!isFileSupported(item, collection)) continue; files.set(item, { path: path.relative(dir, item), absolutePath: item }); } }) ); return Array.from(files.values()); } function parseMetaEntry(file, content) { const extname2 = path.extname(file); try { if (extname2 === ".json") return JSON.parse(content); if (extname2 === ".yaml") return load(content); } catch (e) { throw new Error(`Failed to parse meta file: ${file}.`, { cause: e }); } throw new Error(`Unknown meta file format: ${extname2}, in ${file}.`); } // src/map/index.ts async function start(dev, configPath, outDir) { let configHash = await getConfigHash(configPath); let config = await loadConfig(configPath, outDir, configHash, true); const outPath = path2.resolve(outDir, `index.ts`); async function updateMapFile() { const start2 = performance.now(); try { await fs2.writeFile( outPath, await generateJS( configPath, config, { relativeTo: outDir }, configHash ) ); } catch (err) { if (err instanceof ValidationError) { console.error(err.toStringFormatted()); } else { console.error(err); } } console.log(`[MDX] updated map file in ${performance.now() - start2}ms`); } await updateMapFile(); if (dev) { const { watcher } = await import("../watcher-4NDMOH4R.js"); const instance = watcher(configPath, config, [outPath]); instance.on("ready", () => { console.log("[MDX] started dev server"); }); instance.on("all", (event, file) => { if (typeof file !== "string") return; const absolutePath = path2.resolve(file); const onUpdate = async () => { const isConfigFile = absolutePath === configPath; if (isConfigFile) { configHash = await getConfigHash(configPath); config = await loadConfig(configPath, outDir, configHash, true); } if (event === "change") fileCache.removeCache(absolutePath); await updateMapFile(); }; void onUpdate(); }); process.on("exit", () => { console.log("[MDX] closing dev server"); void instance.close(); }); } } // src/next/create.ts import { readFileSync } from "fs"; var defaultPageExtensions = ["mdx", "md", "jsx", "js", "tsx", "ts"]; var isTurboExperimental; try { const content = readFileSync("./node_modules/next/package.json").toString(); const version = JSON.parse(content).version; isTurboExperimental = version.startsWith("15.0.") || version.startsWith("15.1.") || version.startsWith("15.2."); } catch { isTurboExperimental = false; } function createMDX({ configPath = findConfigFile(), outDir = ".source" } = {}) { const isDev = process.argv.includes("dev"); const isBuild = process.argv.includes("build"); if ((isDev || isBuild) && process.env._FUMADOCS_MDX !== "1") { process.env._FUMADOCS_MDX = "1"; void start(isDev, configPath, outDir); } return (nextConfig = {}) => { const mdxLoaderOptions = { configPath, outDir }; const turbo = { ...nextConfig.experimental?.turbo, ...nextConfig.turbopack, rules: { ...nextConfig.experimental?.turbo?.rules, ...nextConfig.turbopack?.rules, "*.{md,mdx}": { loaders: [ { loader: "fumadocs-mdx/loader-mdx", options: mdxLoaderOptions } ], as: "*.js" } } }; const updated = { ...nextConfig, pageExtensions: nextConfig.pageExtensions ?? defaultPageExtensions, webpack: (config, options) => { config.resolve ||= {}; config.module ||= {}; config.module.rules ||= []; config.module.rules.push({ test: /\.mdx?$/, use: [ options.defaultLoaders.babel, { loader: "fumadocs-mdx/loader-mdx", options: mdxLoaderOptions } ] }); config.plugins ||= []; return nextConfig.webpack?.(config, options) ?? config; } }; if (isTurboExperimental) { updated.experimental = { ...updated.experimental, turbo }; } else { updated.turbopack = turbo; } return updated; }; } // src/postinstall.ts import * as path3 from "path"; import * as fs3 from "fs/promises"; async function postInstall(configPath = findConfigFile(), outDir = ".source") { const jsOut = path3.resolve(outDir, "index.ts"); const hash = await getConfigHash(configPath); const config = await loadConfig(configPath, outDir, hash, true); await fs3.rm(path3.dirname(jsOut), { recursive: true }); await fs3.mkdir(path3.dirname(jsOut), { recursive: true }); await fs3.writeFile( jsOut, await generateJS(configPath, config, { relativeTo: outDir }, hash) ); console.log("[MDX] types generated"); } export { createMDX, postInstall, start };