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[];
}
export interface ContentEntryType {
extensions: string[];
getEntryInfo(params: { fileUrl: URL }): Promise<{
export interface ContentEntryParser {
getEntryInfo(params: { contents: string; fileUrl: URL }): Promise<{
data: Record<string, unknown>;
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;

View file

@ -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<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.
* This global observable lets dependent plugins (like the content flag plugin)

View file

@ -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<string, unknown>,
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;

View file

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

View file

@ -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"

View file

@ -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

View file

@ -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);

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 = {
extensions: ['.mdx'],
async getEntryInfo({ fileUrl }: { fileUrl: URL }) {
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,
};
},
parser: '@astrojs/mdx/content-type-parser',
};
export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroIntegration {