diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 1975f9db179d..b43a31dfec08 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -990,6 +990,7 @@ export interface ContentEntryType { body: string; slug: string; }>; + contentModuleTypes?: string; } export interface AstroSettings { diff --git a/packages/astro/src/content/template/types.d.ts b/packages/astro/src/content/template/types.d.ts index f14a541f1e8a..abe187a330bc 100644 --- a/packages/astro/src/content/template/types.d.ts +++ b/packages/astro/src/content/template/types.d.ts @@ -1,7 +1,17 @@ +declare module 'astro:content' { + interface Render { + '.md': Promise<{ + Content: import('astro').MarkdownInstance<{}>['Content']; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + }>; + } +} + declare module 'astro:content' { export { z } from 'astro/zod'; export type CollectionEntry = - (typeof entryMap)[C][keyof (typeof entryMap)[C]] & Render; + (typeof entryMap)[C][keyof (typeof entryMap)[C]]; type BaseSchemaWithoutEffects = | import('astro/zod').AnyZodObject @@ -57,14 +67,6 @@ declare module 'astro:content' { Required['schema'] >; - type Render = { - render(): Promise<{ - Content: import('astro').MarkdownInstance<{}>['Content']; - headings: import('astro').MarkdownHeading[]; - remarkPluginFrontmatter: Record; - }>; - }; - const entryMap: { // @@ENTRY_MAP@@ }; diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts index 5528c534fe34..1719931739c6 100644 --- a/packages/astro/src/content/types-generator.ts +++ b/packages/astro/src/content/types-generator.ts @@ -4,7 +4,7 @@ import type fsMod from 'node:fs'; import * as path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { normalizePath, ViteDevServer } from 'vite'; -import type { AstroSettings } from '../@types/astro.js'; +import type { AstroSettings, ContentEntryType } from '../@types/astro.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { info, LogOptions, warn } from '../core/logger/core.js'; import { isRelativePath } from '../core/path.js'; @@ -58,7 +58,7 @@ export async function createContentTypesGenerator({ let events: Promise<{ shouldGenerateTypes: boolean; error?: Error }>[] = []; let debounceTimeout: NodeJS.Timeout | undefined; - const contentTypesBase = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8'); + const typeTemplateContent = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8'); async function init(): Promise< { typesGenerated: true } | { typesGenerated: false; reason: 'no-content-dir' } @@ -245,8 +245,9 @@ export async function createContentTypesGenerator({ fs, contentTypes, contentPaths, - contentTypesBase, + typeTemplateContent, contentConfig: observable.status === 'loaded' ? observable.config : undefined, + contentEntryTypes: settings.contentEntryTypes, }); if (observable.status === 'loaded' && ['info', 'warn'].includes(logLevel)) { warnNonexistentCollections({ @@ -304,13 +305,15 @@ async function writeContentFiles({ fs, contentPaths, contentTypes, - contentTypesBase, + typeTemplateContent, + contentEntryTypes, contentConfig, }: { fs: typeof fsMod; contentPaths: ContentPaths; contentTypes: ContentTypes; - contentTypesBase: string; + typeTemplateContent: string; + contentEntryTypes: ContentEntryType[]; contentConfig?: ContentConfig; }) { let contentTypesStr = ''; @@ -322,8 +325,11 @@ async function writeContentFiles({ for (const entryKey of entryKeys) { const entryMetadata = contentTypes[collectionKey][entryKey]; const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : 'any'; + const renderType = `{ render(): Render[${JSON.stringify( + path.extname(JSON.parse(entryKey)) + )}] }`; const slugType = JSON.stringify(entryMetadata.slug); - contentTypesStr += `${entryKey}: {\n id: ${entryKey},\n slug: ${slugType},\n body: string,\n collection: ${collectionKey},\n data: ${dataType}\n},\n`; + contentTypesStr += `${entryKey}: {\n id: ${entryKey},\n slug: ${slugType},\n body: string,\n collection: ${collectionKey},\n data: ${dataType}\n} & ${renderType},\n`; } contentTypesStr += `},\n`; } @@ -343,13 +349,21 @@ async function writeContentFiles({ configPathRelativeToCacheDir = configPathRelativeToCacheDir.replace(/\.ts$/, ''); } - contentTypesBase = contentTypesBase.replace('// @@ENTRY_MAP@@', contentTypesStr); - contentTypesBase = contentTypesBase.replace( + for (const contentEntryType of contentEntryTypes) { + if (contentEntryType.contentModuleTypes) { + typeTemplateContent = contentEntryType.contentModuleTypes + '\n' + typeTemplateContent; + } + } + typeTemplateContent = typeTemplateContent.replace('// @@ENTRY_MAP@@', contentTypesStr); + typeTemplateContent = typeTemplateContent.replace( "'@@CONTENT_CONFIG_TYPE@@'", contentConfig ? `typeof import(${JSON.stringify(configPathRelativeToCacheDir)})` : 'never' ); - await fs.promises.writeFile(new URL(CONTENT_TYPES_FILE, contentPaths.cacheDir), contentTypesBase); + await fs.promises.writeFile( + new URL(CONTENT_TYPES_FILE, contentPaths.cacheDir), + typeTemplateContent + ); } function warnNonexistentCollections({ diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index 61f60ba0bbf6..65a218a04785 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -23,19 +23,6 @@ export type MdxOptions = Omit = {}): AstroIntegration { return { name: '@astrojs/mdx', @@ -47,6 +34,23 @@ export default function mdx(partialMdxOptions: Partial = {}): AstroI addContentEntryType, command, }: any) => { + const contentEntryType = { + extensions: ['.mdx'], + async getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) { + const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl)); + return { + data: parsed.data, + body: parsed.content, + slug: parsed.data.slug, + rawData: parsed.matter, + }; + }, + contentModuleTypes: await fs.readFile( + new URL('../template/content-module-types.d.ts', import.meta.url), + 'utf-8' + ), + }; + addPageExtension('.mdx'); addContentEntryType(contentEntryType); diff --git a/packages/integrations/mdx/template/content-module-types.d.ts b/packages/integrations/mdx/template/content-module-types.d.ts new file mode 100644 index 000000000000..957f99895baa --- /dev/null +++ b/packages/integrations/mdx/template/content-module-types.d.ts @@ -0,0 +1,9 @@ +declare module 'astro:content' { + interface Render { + '.mdx': Promise<{ + Content: import('astro').MarkdownInstance<{}>['Content']; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + }>; + } +}