From b9bf041e5babe2b077c688402f5b066f1b08e4f7 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 9 Feb 2023 11:45:53 -0500 Subject: [PATCH] wip: convert inline config to parser --- packages/astro/src/@types/astro.ts | 14 +++++++---- packages/astro/src/content/utils.ts | 24 ++++++++++++++++++- .../content/vite-plugin-content-imports.ts | 22 +++++++++++++---- .../integrations/markdoc/components/index.ts | 1 + packages/integrations/markdoc/package.json | 1 + .../src/{utils.ts => content-type-parser.ts} | 15 +++++++++++- packages/integrations/markdoc/src/index.ts | 15 ++---------- .../mdx/src/content-type-parser.ts | 14 +++++++++++ packages/integrations/mdx/src/index.ts | 11 +-------- 9 files changed, 82 insertions(+), 35 deletions(-) rename packages/integrations/markdoc/src/{utils.ts => content-type-parser.ts} (60%) create mode 100644 packages/integrations/mdx/src/content-type-parser.ts diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 390f3c715..502935c1b 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -977,21 +977,25 @@ export interface AstroConfig extends z.output { integrations: AstroIntegration[]; } -export interface ContentEntryType { - extensions: string[]; - getEntryInfo(params: { fileUrl: URL }): Promise<{ +export interface ContentEntryParser { + getEntryInfo(params: { contents: string; fileUrl: URL }): Promise<{ data: Record; + body: string; + slug: string; /** * Used for error hints to point to correct line and location * Should be the untouched data as read from the file, * including newlines */ rawData: string; - body: string; - slug: string; }>; } +export interface ContentEntryType { + extensions: string[]; + parser: string; +} + export interface AstroSettings { config: AstroConfig; adapter: AstroAdapter | undefined; diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 4458af098..4ddf17a22 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -5,7 +5,7 @@ import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite'; import { z } from 'zod'; -import { AstroConfig, AstroSettings } from '../@types/astro.js'; +import { AstroConfig, AstroSettings, ContentEntryParser } from '../@types/astro.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { appendForwardSlash } from '../core/path.js'; import { CONTENT_TYPES_FILE, defaultContentEntryExts } from './consts.js'; @@ -244,6 +244,28 @@ export function parseFrontmatter(fileContents: string, filePath: string) { } } +// NOTE: Will require dev server restart to debug parser changes +const contentEntryExtToParserCache = new Map(); + +export async function loadContentEntryParsers({ + settings, +}: { + settings: Pick; +}): Promise> { + if (contentEntryExtToParserCache.size > 0) return contentEntryExtToParserCache; + + for (const contentEntryType of settings.contentEntryTypes) { + for (const extension of contentEntryType.extensions) { + // TODO: explore SSR loading strategy if Content Collections processing + // is moved from build to SSR + const parser = await import(contentEntryType.parser); + // TODO: Zod check that parser is a valid parser + contentEntryExtToParserCache.set(extension, parser as ContentEntryParser); + } + } + return contentEntryExtToParserCache; +} + /** * The content config is loaded separately from other `src/` files. * This global observable lets dependent plugins (like the content flag plugin) diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index 8334a4d5d..0b4f6dcab 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -1,5 +1,6 @@ import * as devalue from 'devalue'; import type fsMod from 'node:fs'; +import { extname } from 'node:path'; import { pathToFileURL } from 'url'; import type { Plugin } from 'vite'; import { AstroSettings } from '../@types/astro.js'; @@ -16,6 +17,7 @@ import { getEntrySlug, getEntryType, globalContentConfigObserver, + loadContentEntryParsers, parseFrontmatter, } from './utils.js'; @@ -65,16 +67,26 @@ export function astroContentImportPlugin({ }); }); } + + const contentEntryParsers = await loadContentEntryParsers({ settings }); const rawContents = await fs.promises.readFile(fileId, 'utf-8'); - const contentEntryType = settings.contentEntryTypes.find((entryType) => - entryType.extensions.some((ext) => fileId.endsWith(ext)) - ); + const fileExt = extname(fileId); + const contentEntryParser = contentEntryParsers.get(fileExt); + // if (!contentEntryParser) { + // throw new AstroError({ + // ...AstroErrorData.UnknownContentCollectionError, + // message: `No content parser found for file extension "${fileExt}".`, + // }); + // } let body: string, unvalidatedData: Record, unvalidatedSlug: string, rawData: string; - if (contentEntryType) { - const info = await contentEntryType.getEntryInfo({ fileUrl: pathToFileURL(fileId) }); + if (contentEntryParser) { + const info = await contentEntryParser.getEntryInfo({ + fileUrl: pathToFileURL(fileId), + contents: rawContents, + }); body = info.body; unvalidatedData = info.data; unvalidatedSlug = info.slug; diff --git a/packages/integrations/markdoc/components/index.ts b/packages/integrations/markdoc/components/index.ts index bd5ef1e28..72f0d750e 100644 --- a/packages/integrations/markdoc/components/index.ts +++ b/packages/integrations/markdoc/components/index.ts @@ -1 +1,2 @@ +// @ts-ignore export { default as Renderer } from './Renderer.astro'; diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index 74e31dbf8..40209a0b9 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -20,6 +20,7 @@ "homepage": "https://docs.astro.build/en/guides/integrations-guide/markdoc/", "exports": { ".": "./dist/index.js", + "./content-type-parser": "./dist/content-type-parser.js", "./components": "./components/index.ts", "./content-types": "./content-types.d.ts", "./package.json": "./package.json" diff --git a/packages/integrations/markdoc/src/utils.ts b/packages/integrations/markdoc/src/content-type-parser.ts similarity index 60% rename from packages/integrations/markdoc/src/utils.ts rename to packages/integrations/markdoc/src/content-type-parser.ts index abcebbf8b..bffaba4b8 100644 --- a/packages/integrations/markdoc/src/utils.ts +++ b/packages/integrations/markdoc/src/content-type-parser.ts @@ -1,11 +1,24 @@ import matter from 'gray-matter'; +import { fileURLToPath } from 'node:url'; import type { ErrorPayload as ViteErrorPayload } from 'vite'; +export default { + async getEntryInfo({ contents, fileUrl }: { contents: string; fileUrl: URL }) { + const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl)); + return { + data: parsed.data, + body: parsed.content, + slug: parsed.data.slug, + rawData: parsed.matter, + }; + }, +}; + /** * Match YAML exception handling from Astro core errors * @see 'astro/src/core/errors.ts' */ -export function parseFrontmatter(fileContents: string, filePath: string) { +function parseFrontmatter(fileContents: string, filePath: string) { try { // `matter` is empty string on cache results // clear cache to prevent this diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index 17c433ef3..d90273c31 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -1,22 +1,10 @@ import type { AstroIntegration } from 'astro'; import type { InlineConfig } from 'vite'; import _Markdoc from '@markdoc/markdoc'; -import fs from 'node:fs'; -import { parseFrontmatter } from './utils.js'; -import { fileURLToPath } from 'node:url'; const contentEntryType = { extensions: ['.mdoc'], - async getEntryInfo({ fileUrl }: { fileUrl: URL }) { - const rawContents = await fs.promises.readFile(fileUrl, 'utf-8'); - const parsed = parseFrontmatter(rawContents, fileURLToPath(fileUrl)); - return { - data: parsed.data, - body: parsed.content, - slug: parsed.data.slug, - rawData: parsed.matter, - }; - }, + parser: '@astrojs/markdoc/content-type-parser', }; export default function markdoc(partialOptions: {} = {}): AstroIntegration { @@ -25,6 +13,7 @@ export default function markdoc(partialOptions: {} = {}): AstroIntegration { hooks: { 'astro:config:setup': async ({ updateConfig, config, addContentEntryType, command }: any) => { addContentEntryType(contentEntryType); + console.log('Markdoc working!'); const markdocConfigUrl = new URL('./markdoc.config.ts', config.srcDir); diff --git a/packages/integrations/mdx/src/content-type-parser.ts b/packages/integrations/mdx/src/content-type-parser.ts new file mode 100644 index 000000000..cf2371cb3 --- /dev/null +++ b/packages/integrations/mdx/src/content-type-parser.ts @@ -0,0 +1,14 @@ +import { fileURLToPath } from 'node:url'; +import { parseFrontmatter } from './utils.js'; + +export default { + async getEntryInfo({ contents, fileUrl }: { contents: string; fileUrl: URL }) { + const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl)); + return { + data: parsed.data, + body: parsed.content, + slug: parsed.data.slug, + rawData: parsed.matter, + }; + }, +}; diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index 78f68b80a..39c54cb02 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -25,16 +25,7 @@ export type MdxOptions = Omit = {}): AstroIntegration {