wip: convert inline config to parser
This commit is contained in:
parent
6b94a279dd
commit
b9bf041e5b
9 changed files with 82 additions and 35 deletions
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
// @ts-ignore
|
||||||
export { default as Renderer } from './Renderer.astro';
|
export { default as Renderer } from './Renderer.astro';
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
|
@ -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);
|
||||||
|
|
||||||
|
|
14
packages/integrations/mdx/src/content-type-parser.ts
Normal file
14
packages/integrations/mdx/src/content-type-parser.ts
Normal 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,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue