wip: convert inline config to parser

This commit is contained in:
bholmesdev 2023-02-09 11:45:53 -05:00
parent 6b94a279dd
commit b9bf041e5b
9 changed files with 82 additions and 35 deletions

View file

@ -977,21 +977,25 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
integrations: AstroIntegration[]; integrations: AstroIntegration[];
} }
export interface ContentEntryType { export interface ContentEntryParser {
extensions: string[]; getEntryInfo(params: { contents: string; fileUrl: URL }): Promise<{
getEntryInfo(params: { fileUrl: URL }): Promise<{
data: Record<string, unknown>; data: Record<string, unknown>;
body: string;
slug: string;
/** /**
* Used for error hints to point to correct line and location * Used for error hints to point to correct line and location
* Should be the untouched data as read from the file, * Should be the untouched data as read from the file,
* including newlines * including newlines
*/ */
rawData: string; rawData: string;
body: string;
slug: string;
}>; }>;
} }
export interface ContentEntryType {
extensions: string[];
parser: string;
}
export interface AstroSettings { export interface AstroSettings {
config: AstroConfig; config: AstroConfig;
adapter: AstroAdapter | undefined; adapter: AstroAdapter | undefined;

View file

@ -5,7 +5,7 @@ import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url'; import { fileURLToPath, pathToFileURL } from 'node:url';
import { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite'; import { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
import { z } from 'zod'; 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 { AstroError, AstroErrorData } from '../core/errors/index.js';
import { appendForwardSlash } from '../core/path.js'; import { appendForwardSlash } from '../core/path.js';
import { CONTENT_TYPES_FILE, defaultContentEntryExts } from './consts.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<string, ContentEntryParser>();
export async function loadContentEntryParsers({
settings,
}: {
settings: Pick<AstroSettings, 'contentEntryTypes'>;
}): Promise<Map<string, ContentEntryParser>> {
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. * The content config is loaded separately from other `src/` files.
* This global observable lets dependent plugins (like the content flag plugin) * This global observable lets dependent plugins (like the content flag plugin)

View file

@ -1,5 +1,6 @@
import * as devalue from 'devalue'; import * as devalue from 'devalue';
import type fsMod from 'node:fs'; import type fsMod from 'node:fs';
import { extname } from 'node:path';
import { pathToFileURL } from 'url'; import { pathToFileURL } from 'url';
import type { Plugin } from 'vite'; import type { Plugin } from 'vite';
import { AstroSettings } from '../@types/astro.js'; import { AstroSettings } from '../@types/astro.js';
@ -16,6 +17,7 @@ import {
getEntrySlug, getEntrySlug,
getEntryType, getEntryType,
globalContentConfigObserver, globalContentConfigObserver,
loadContentEntryParsers,
parseFrontmatter, parseFrontmatter,
} from './utils.js'; } 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 rawContents = await fs.promises.readFile(fileId, 'utf-8');
const contentEntryType = settings.contentEntryTypes.find((entryType) => const fileExt = extname(fileId);
entryType.extensions.some((ext) => fileId.endsWith(ext)) const contentEntryParser = contentEntryParsers.get(fileExt);
); // if (!contentEntryParser) {
// throw new AstroError({
// ...AstroErrorData.UnknownContentCollectionError,
// message: `No content parser found for file extension "${fileExt}".`,
// });
// }
let body: string, let body: string,
unvalidatedData: Record<string, unknown>, unvalidatedData: Record<string, unknown>,
unvalidatedSlug: string, unvalidatedSlug: string,
rawData: string; rawData: string;
if (contentEntryType) { if (contentEntryParser) {
const info = await contentEntryType.getEntryInfo({ fileUrl: pathToFileURL(fileId) }); const info = await contentEntryParser.getEntryInfo({
fileUrl: pathToFileURL(fileId),
contents: rawContents,
});
body = info.body; body = info.body;
unvalidatedData = info.data; unvalidatedData = info.data;
unvalidatedSlug = info.slug; unvalidatedSlug = info.slug;

View file

@ -1 +1,2 @@
// @ts-ignore
export { default as Renderer } from './Renderer.astro'; export { default as Renderer } from './Renderer.astro';

View file

@ -20,6 +20,7 @@
"homepage": "https://docs.astro.build/en/guides/integrations-guide/markdoc/", "homepage": "https://docs.astro.build/en/guides/integrations-guide/markdoc/",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
"./content-type-parser": "./dist/content-type-parser.js",
"./components": "./components/index.ts", "./components": "./components/index.ts",
"./content-types": "./content-types.d.ts", "./content-types": "./content-types.d.ts",
"./package.json": "./package.json" "./package.json": "./package.json"

View file

@ -1,11 +1,24 @@
import matter from 'gray-matter'; import matter from 'gray-matter';
import { fileURLToPath } from 'node:url';
import type { ErrorPayload as ViteErrorPayload } from 'vite'; 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 * Match YAML exception handling from Astro core errors
* @see 'astro/src/core/errors.ts' * @see 'astro/src/core/errors.ts'
*/ */
export function parseFrontmatter(fileContents: string, filePath: string) { function parseFrontmatter(fileContents: string, filePath: string) {
try { try {
// `matter` is empty string on cache results // `matter` is empty string on cache results
// clear cache to prevent this // clear cache to prevent this

View file

@ -1,22 +1,10 @@
import type { AstroIntegration } from 'astro'; import type { AstroIntegration } from 'astro';
import type { InlineConfig } from 'vite'; import type { InlineConfig } from 'vite';
import _Markdoc from '@markdoc/markdoc'; import _Markdoc from '@markdoc/markdoc';
import fs from 'node:fs';
import { parseFrontmatter } from './utils.js';
import { fileURLToPath } from 'node:url';
const contentEntryType = { const contentEntryType = {
extensions: ['.mdoc'], extensions: ['.mdoc'],
async getEntryInfo({ fileUrl }: { fileUrl: URL }) { parser: '@astrojs/markdoc/content-type-parser',
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,
};
},
}; };
export default function markdoc(partialOptions: {} = {}): AstroIntegration { export default function markdoc(partialOptions: {} = {}): AstroIntegration {
@ -25,6 +13,7 @@ export default function markdoc(partialOptions: {} = {}): AstroIntegration {
hooks: { hooks: {
'astro:config:setup': async ({ updateConfig, config, addContentEntryType, command }: any) => { 'astro:config:setup': async ({ updateConfig, config, addContentEntryType, command }: any) => {
addContentEntryType(contentEntryType); addContentEntryType(contentEntryType);
console.log('Markdoc working!'); console.log('Markdoc working!');
const markdocConfigUrl = new URL('./markdoc.config.ts', config.srcDir); const markdocConfigUrl = new URL('./markdoc.config.ts', config.srcDir);

View file

@ -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,
};
},
};

View file

@ -25,16 +25,7 @@ export type MdxOptions = Omit<typeof markdownConfigDefaults, 'remarkPlugins' | '
const contentEntryType = { const contentEntryType = {
extensions: ['.mdx'], extensions: ['.mdx'],
async getEntryInfo({ fileUrl }: { fileUrl: URL }) { parser: '@astrojs/mdx/content-type-parser',
const rawContents = await fs.readFile(fileUrl, 'utf-8');
const parsed = parseFrontmatter(rawContents, fileURLToPath(fileUrl));
return {
data: parsed.data,
body: parsed.content,
slug: parsed.data.slug,
rawData: parsed.matter,
};
},
}; };
export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroIntegration { export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroIntegration {