feat: addContentEntryType integration hook
This commit is contained in:
parent
b97ea127e0
commit
092a1b1541
12 changed files with 135 additions and 43 deletions
|
@ -978,12 +978,27 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
|
||||||
integrations: AstroIntegration[];
|
integrations: AstroIntegration[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ContentEntryType {
|
||||||
|
extensions: string[];
|
||||||
|
getEntryInfo(params: { fileUrl: URL }): Promise<{
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
/**
|
||||||
|
* 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 AstroSettings {
|
export interface AstroSettings {
|
||||||
config: AstroConfig;
|
config: AstroConfig;
|
||||||
|
|
||||||
adapter: AstroAdapter | undefined;
|
adapter: AstroAdapter | undefined;
|
||||||
injectedRoutes: InjectedRoute[];
|
injectedRoutes: InjectedRoute[];
|
||||||
pageExtensions: string[];
|
pageExtensions: string[];
|
||||||
|
contentEntryTypes: ContentEntryType[];
|
||||||
renderers: AstroRenderer[];
|
renderers: AstroRenderer[];
|
||||||
scripts: {
|
scripts: {
|
||||||
stage: InjectedScriptStage;
|
stage: InjectedScriptStage;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/** TODO as const*/
|
export const defaultContentEntryExts = ['.md', '.mdx'] as const;
|
||||||
export const defaultContentFileExts = ['.md', '.mdx'];
|
|
||||||
export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';
|
export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';
|
||||||
export const CONTENT_FLAG = 'astroContent';
|
export const CONTENT_FLAG = 'astroContent';
|
||||||
export const VIRTUAL_MODULE_ID = 'astro:content';
|
export const VIRTUAL_MODULE_ID = 'astro:content';
|
||||||
|
|
|
@ -8,12 +8,13 @@ import type { AstroSettings } from '../@types/astro.js';
|
||||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||||
import { info, LogOptions, warn } from '../core/logger/core.js';
|
import { info, LogOptions, warn } from '../core/logger/core.js';
|
||||||
import { isRelativePath } from '../core/path.js';
|
import { isRelativePath } from '../core/path.js';
|
||||||
import { CONTENT_TYPES_FILE, defaultContentFileExts } from './consts.js';
|
import { CONTENT_TYPES_FILE } from './consts.js';
|
||||||
import {
|
import {
|
||||||
ContentConfig,
|
ContentConfig,
|
||||||
ContentObservable,
|
ContentObservable,
|
||||||
ContentPaths,
|
ContentPaths,
|
||||||
EntryInfo,
|
EntryInfo,
|
||||||
|
getContentEntryExts,
|
||||||
getContentPaths,
|
getContentPaths,
|
||||||
getEntryInfo,
|
getEntryInfo,
|
||||||
getEntrySlug,
|
getEntrySlug,
|
||||||
|
@ -22,7 +23,6 @@ import {
|
||||||
NoCollectionError,
|
NoCollectionError,
|
||||||
parseFrontmatter,
|
parseFrontmatter,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { contentEntryTypes } from './~dream.js';
|
|
||||||
|
|
||||||
type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
|
type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
|
||||||
type RawContentEvent = { name: ChokidarEvent; entry: string };
|
type RawContentEvent = { name: ChokidarEvent; entry: string };
|
||||||
|
@ -58,10 +58,7 @@ export async function createContentTypesGenerator({
|
||||||
}: CreateContentGeneratorParams) {
|
}: CreateContentGeneratorParams) {
|
||||||
const contentTypes: ContentTypes = {};
|
const contentTypes: ContentTypes = {};
|
||||||
const contentPaths = getContentPaths(settings.config, fs);
|
const contentPaths = getContentPaths(settings.config, fs);
|
||||||
const contentFileExts = [
|
const contentEntryExts = getContentEntryExts(settings);
|
||||||
...defaultContentFileExts,
|
|
||||||
...contentEntryTypes.map((t) => t.extensions).flat(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let events: EventWithOptions[] = [];
|
let events: EventWithOptions[] = [];
|
||||||
let debounceTimeout: NodeJS.Timeout | undefined;
|
let debounceTimeout: NodeJS.Timeout | undefined;
|
||||||
|
@ -126,7 +123,7 @@ export async function createContentTypesGenerator({
|
||||||
}
|
}
|
||||||
return { shouldGenerateTypes: true };
|
return { shouldGenerateTypes: true };
|
||||||
}
|
}
|
||||||
const fileType = getEntryType(fileURLToPath(event.entry), contentPaths, contentFileExts);
|
const fileType = getEntryType(fileURLToPath(event.entry), contentPaths, contentEntryExts);
|
||||||
if (fileType === 'ignored') {
|
if (fileType === 'ignored') {
|
||||||
return { shouldGenerateTypes: false };
|
return { shouldGenerateTypes: false };
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,7 @@ import { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from '
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { AstroConfig, AstroSettings } from '../@types/astro.js';
|
import { AstroConfig, AstroSettings } 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 { CONTENT_TYPES_FILE, defaultContentEntryExts } from './consts.js';
|
||||||
import { contentFileExts, CONTENT_TYPES_FILE } from './consts.js';
|
|
||||||
|
|
||||||
export const collectionConfigParser = z.object({
|
export const collectionConfigParser = z.object({
|
||||||
schema: z.any().optional(),
|
schema: z.any().optional(),
|
||||||
|
@ -119,6 +118,14 @@ export async function getEntryData(
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getContentEntryExts(settings: Pick<AstroSettings, 'contentEntryTypes'>) {
|
||||||
|
return [
|
||||||
|
// TODO: roll defaults into settings
|
||||||
|
...defaultContentEntryExts,
|
||||||
|
...settings.contentEntryTypes.map((t) => t.extensions).flat(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export class NoCollectionError extends Error {}
|
export class NoCollectionError extends Error {}
|
||||||
|
|
||||||
export function getEntryInfo(
|
export function getEntryInfo(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import npath from 'node:path';
|
import npath 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 { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js';
|
import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js';
|
||||||
import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js';
|
import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js';
|
||||||
import { AstroBuildPlugin } from '../core/build/plugin.js';
|
import { AstroBuildPlugin } from '../core/build/plugin.js';
|
||||||
|
@ -11,23 +12,30 @@ import { prependForwardSlash } from '../core/path.js';
|
||||||
import { getStylesForURL } from '../core/render/dev/css.js';
|
import { getStylesForURL } from '../core/render/dev/css.js';
|
||||||
import { getScriptsForURL } from '../core/render/dev/scripts.js';
|
import { getScriptsForURL } from '../core/render/dev/scripts.js';
|
||||||
import {
|
import {
|
||||||
defaultContentFileExts,
|
|
||||||
LINKS_PLACEHOLDER,
|
LINKS_PLACEHOLDER,
|
||||||
PROPAGATED_ASSET_FLAG,
|
PROPAGATED_ASSET_FLAG,
|
||||||
SCRIPTS_PLACEHOLDER,
|
SCRIPTS_PLACEHOLDER,
|
||||||
STYLES_PLACEHOLDER,
|
STYLES_PLACEHOLDER,
|
||||||
} from './consts.js';
|
} from './consts.js';
|
||||||
|
import { getContentEntryExts } from './utils.js';
|
||||||
|
|
||||||
function isPropagatedAsset(viteId: string): boolean {
|
function isPropagatedAsset(viteId: string, contentEntryExts: string[]): boolean {
|
||||||
const url = new URL(viteId, 'file://');
|
const url = new URL(viteId, 'file://');
|
||||||
return (
|
return (
|
||||||
url.searchParams.has(PROPAGATED_ASSET_FLAG) &&
|
url.searchParams.has(PROPAGATED_ASSET_FLAG) &&
|
||||||
defaultContentFileExts.some((ext) => url.pathname.endsWith(ext))
|
contentEntryExts.some((ext) => url.pathname.endsWith(ext))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function astroContentAssetPropagationPlugin({ mode }: { mode: string }): Plugin {
|
export function astroContentAssetPropagationPlugin({
|
||||||
|
mode,
|
||||||
|
settings,
|
||||||
|
}: {
|
||||||
|
mode: string;
|
||||||
|
settings: AstroSettings;
|
||||||
|
}): Plugin {
|
||||||
let devModuleLoader: ModuleLoader;
|
let devModuleLoader: ModuleLoader;
|
||||||
|
const contentEntryExts = getContentEntryExts(settings);
|
||||||
return {
|
return {
|
||||||
name: 'astro:content-asset-propagation',
|
name: 'astro:content-asset-propagation',
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
|
@ -37,7 +45,7 @@ export function astroContentAssetPropagationPlugin({ mode }: { mode: string }):
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
load(id) {
|
load(id) {
|
||||||
if (isPropagatedAsset(id)) {
|
if (isPropagatedAsset(id, contentEntryExts)) {
|
||||||
const basePath = id.split('?')[0];
|
const basePath = id.split('?')[0];
|
||||||
const code = `
|
const code = `
|
||||||
export async function getMod() {
|
export async function getMod() {
|
||||||
|
@ -52,7 +60,7 @@ export function astroContentAssetPropagationPlugin({ mode }: { mode: string }):
|
||||||
},
|
},
|
||||||
async transform(code, id, options) {
|
async transform(code, id, options) {
|
||||||
if (!options?.ssr) return;
|
if (!options?.ssr) return;
|
||||||
if (devModuleLoader && isPropagatedAsset(id)) {
|
if (devModuleLoader && isPropagatedAsset(id, contentEntryExts)) {
|
||||||
const basePath = id.split('?')[0];
|
const basePath = id.split('?')[0];
|
||||||
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
|
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
|
||||||
await devModuleLoader.import(basePath);
|
await devModuleLoader.import(basePath);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { contentEntryTypes } from './~dream.js';
|
|
||||||
import * as devalue from 'devalue';
|
import * as devalue from 'devalue';
|
||||||
import type fsMod from 'node:fs';
|
import type fsMod from 'node:fs';
|
||||||
import { pathToFileURL } from 'url';
|
import { pathToFileURL } from 'url';
|
||||||
|
@ -7,9 +6,10 @@ import { AstroSettings } from '../@types/astro.js';
|
||||||
import { AstroErrorData } from '../core/errors/errors-data.js';
|
import { AstroErrorData } from '../core/errors/errors-data.js';
|
||||||
import { AstroError } from '../core/errors/errors.js';
|
import { AstroError } from '../core/errors/errors.js';
|
||||||
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
|
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
|
||||||
import { defaultContentFileExts, CONTENT_FLAG } from './consts.js';
|
import { CONTENT_FLAG } from './consts.js';
|
||||||
import {
|
import {
|
||||||
ContentConfig,
|
ContentConfig,
|
||||||
|
getContentEntryExts,
|
||||||
getContentPaths,
|
getContentPaths,
|
||||||
getEntryData,
|
getEntryData,
|
||||||
getEntryInfo,
|
getEntryInfo,
|
||||||
|
@ -19,9 +19,9 @@ import {
|
||||||
parseFrontmatter,
|
parseFrontmatter,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
|
||||||
function isContentFlagImport(viteId: string) {
|
function isContentFlagImport(viteId: string, contentEntryExts: string[]) {
|
||||||
const { searchParams } = new URL(viteId, 'file://');
|
const { searchParams, pathname } = new URL(viteId, 'file://');
|
||||||
return searchParams.has(CONTENT_FLAG);
|
return searchParams.has(CONTENT_FLAG) && contentEntryExts.some((ext) => pathname.endsWith(ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function astroContentImportPlugin({
|
export function astroContentImportPlugin({
|
||||||
|
@ -32,16 +32,13 @@ export function astroContentImportPlugin({
|
||||||
settings: AstroSettings;
|
settings: AstroSettings;
|
||||||
}): Plugin {
|
}): Plugin {
|
||||||
const contentPaths = getContentPaths(settings.config, fs);
|
const contentPaths = getContentPaths(settings.config, fs);
|
||||||
const contentFileExts = [
|
const contentEntryExts = getContentEntryExts(settings);
|
||||||
...defaultContentFileExts,
|
|
||||||
...contentEntryTypes.map((t) => t.extensions).flat(),
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'astro:content-imports',
|
name: 'astro:content-imports',
|
||||||
async load(id) {
|
async load(id) {
|
||||||
const { fileId } = getFileInfo(id, settings.config);
|
const { fileId } = getFileInfo(id, settings.config);
|
||||||
if (isContentFlagImport(id)) {
|
if (isContentFlagImport(id, contentEntryExts)) {
|
||||||
const observable = globalContentConfigObserver.get();
|
const observable = globalContentConfigObserver.get();
|
||||||
|
|
||||||
// Content config should be loaded before this plugin is used
|
// Content config should be loaded before this plugin is used
|
||||||
|
@ -74,7 +71,7 @@ export function astroContentImportPlugin({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const rawContents = await fs.promises.readFile(fileId, 'utf-8');
|
const rawContents = await fs.promises.readFile(fileId, 'utf-8');
|
||||||
const contentEntryType = contentEntryTypes.find((entryType) =>
|
const contentEntryType = settings.contentEntryTypes.find((entryType) =>
|
||||||
entryType.extensions.some((ext) => fileId.endsWith(ext))
|
entryType.extensions.some((ext) => fileId.endsWith(ext))
|
||||||
);
|
);
|
||||||
let body: string,
|
let body: string,
|
||||||
|
@ -129,11 +126,11 @@ export const _internal = {
|
||||||
viteServer.watcher.on('all', async (event, entry) => {
|
viteServer.watcher.on('all', async (event, entry) => {
|
||||||
if (
|
if (
|
||||||
['add', 'unlink', 'change'].includes(event) &&
|
['add', 'unlink', 'change'].includes(event) &&
|
||||||
getEntryType(entry, contentPaths, contentFileExts) === 'config'
|
getEntryType(entry, contentPaths, contentEntryExts) === 'config'
|
||||||
) {
|
) {
|
||||||
// Content modules depend on config, so we need to invalidate them.
|
// Content modules depend on config, so we need to invalidate them.
|
||||||
for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) {
|
for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) {
|
||||||
if (isContentFlagImport(modUrl)) {
|
if (isContentFlagImport(modUrl, contentEntryExts)) {
|
||||||
const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl);
|
const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl);
|
||||||
if (mod) {
|
if (mod) {
|
||||||
viteServer.moduleGraph.invalidateModule(mod);
|
viteServer.moduleGraph.invalidateModule(mod);
|
||||||
|
@ -144,7 +141,7 @@ export const _internal = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async transform(code, id) {
|
async transform(code, id) {
|
||||||
if (isContentFlagImport(id)) {
|
if (isContentFlagImport(id, contentEntryExts)) {
|
||||||
// Escape before Rollup internal transform.
|
// Escape before Rollup internal transform.
|
||||||
// Base on MUCH trial-and-error, inspired by MDX integration 2-step transform.
|
// Base on MUCH trial-and-error, inspired by MDX integration 2-step transform.
|
||||||
return { code: escapeViteEnvReferences(code) };
|
return { code: escapeViteEnvReferences(code) };
|
||||||
|
|
|
@ -4,9 +4,8 @@ import type { Plugin } from 'vite';
|
||||||
import { normalizePath } from 'vite';
|
import { normalizePath } from 'vite';
|
||||||
import type { AstroSettings } from '../@types/astro.js';
|
import type { AstroSettings } from '../@types/astro.js';
|
||||||
import { appendForwardSlash, prependForwardSlash } from '../core/path.js';
|
import { appendForwardSlash, prependForwardSlash } from '../core/path.js';
|
||||||
import { defaultContentFileExts, VIRTUAL_MODULE_ID } from './consts.js';
|
import { VIRTUAL_MODULE_ID } from './consts.js';
|
||||||
import { getContentPaths } from './utils.js';
|
import { getContentEntryExts, getContentPaths } from './utils.js';
|
||||||
import { contentEntryTypes } from './~dream.js';
|
|
||||||
|
|
||||||
interface AstroContentVirtualModPluginParams {
|
interface AstroContentVirtualModPluginParams {
|
||||||
settings: AstroSettings;
|
settings: AstroSettings;
|
||||||
|
@ -23,12 +22,9 @@ export function astroContentVirtualModPlugin({
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const contentFileExts = [
|
const contentEntryExts = getContentEntryExts(settings);
|
||||||
...defaultContentFileExts,
|
|
||||||
...contentEntryTypes.map((t) => t.extensions).flat(),
|
|
||||||
];
|
|
||||||
|
|
||||||
const entryGlob = `${relContentDir}**/*{${contentFileExts.join(',')}}`;
|
const entryGlob = `${relContentDir}**/*{${contentEntryExts.join(',')}}`;
|
||||||
const virtualModContents = fsMod
|
const virtualModContents = fsMod
|
||||||
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
|
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
|
||||||
.replace('@@CONTENT_DIR@@', relContentDir)
|
.replace('@@CONTENT_DIR@@', relContentDir)
|
||||||
|
|
|
@ -16,6 +16,8 @@ export function createBaseSettings(config: AstroConfig): AstroSettings {
|
||||||
adapter: undefined,
|
adapter: undefined,
|
||||||
injectedRoutes: [],
|
injectedRoutes: [],
|
||||||
pageExtensions: ['.astro', '.html', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS],
|
pageExtensions: ['.astro', '.html', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS],
|
||||||
|
/** TODO: default Markdown entry type */
|
||||||
|
contentEntryTypes: [],
|
||||||
renderers: [jsxRenderer],
|
renderers: [jsxRenderer],
|
||||||
scripts: [],
|
scripts: [],
|
||||||
watchFiles: [],
|
watchFiles: [],
|
||||||
|
|
|
@ -114,7 +114,7 @@ export async function createVite(
|
||||||
astroInjectEnvTsPlugin({ settings, logging, fs }),
|
astroInjectEnvTsPlugin({ settings, logging, fs }),
|
||||||
astroContentVirtualModPlugin({ settings }),
|
astroContentVirtualModPlugin({ settings }),
|
||||||
astroContentImportPlugin({ fs, settings }),
|
astroContentImportPlugin({ fs, settings }),
|
||||||
astroContentAssetPropagationPlugin({ mode }),
|
astroContentAssetPropagationPlugin({ mode, settings }),
|
||||||
],
|
],
|
||||||
publicDir: fileURLToPath(settings.config.publicDir),
|
publicDir: fileURLToPath(settings.config.publicDir),
|
||||||
root: fileURLToPath(settings.config.root),
|
root: fileURLToPath(settings.config.root),
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
AstroRenderer,
|
AstroRenderer,
|
||||||
AstroSettings,
|
AstroSettings,
|
||||||
BuildConfig,
|
BuildConfig,
|
||||||
|
ContentEntryType,
|
||||||
HookParameters,
|
HookParameters,
|
||||||
RouteData,
|
RouteData,
|
||||||
} from '../@types/astro.js';
|
} from '../@types/astro.js';
|
||||||
|
@ -100,11 +101,22 @@ export async function runHookConfigSetup({
|
||||||
const exts = (input.flat(Infinity) as string[]).map((ext) => `.${ext.replace(/^\./, '')}`);
|
const exts = (input.flat(Infinity) as string[]).map((ext) => `.${ext.replace(/^\./, '')}`);
|
||||||
updatedSettings.pageExtensions.push(...exts);
|
updatedSettings.pageExtensions.push(...exts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Semi-private `addContentEntryType` hook
|
||||||
|
function addContentEntryType(contentEntryType: ContentEntryType) {
|
||||||
|
updatedSettings.contentEntryTypes.push(contentEntryType);
|
||||||
|
}
|
||||||
|
|
||||||
Object.defineProperty(hooks, 'addPageExtension', {
|
Object.defineProperty(hooks, 'addPageExtension', {
|
||||||
value: addPageExtension,
|
value: addPageExtension,
|
||||||
writable: false,
|
writable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(hooks, 'addContentEntryType', {
|
||||||
|
value: addContentEntryType,
|
||||||
|
writable: false,
|
||||||
|
enumerable: false,
|
||||||
|
});
|
||||||
await withTakingALongTimeMsg({
|
await withTakingALongTimeMsg({
|
||||||
name: integration.name,
|
name: integration.name,
|
||||||
hookResult: integration.hooks['astro:config:setup'](hooks),
|
hookResult: integration.hooks['astro:config:setup'](hooks),
|
||||||
|
|
|
@ -1,13 +1,47 @@
|
||||||
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 = {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async render({ entry }: { entry: any }) {
|
||||||
|
function getParsed() {
|
||||||
|
return Markdoc.parse(entry.body);
|
||||||
|
}
|
||||||
|
async function getTransformed(inlineConfig: any) {
|
||||||
|
let config = inlineConfig;
|
||||||
|
// TODO: load config file
|
||||||
|
// if (!config) {
|
||||||
|
// try {
|
||||||
|
// const importedConfig = await import('./markdoc.config.ts');
|
||||||
|
// config = importedConfig.default.transform;
|
||||||
|
// } catch {}
|
||||||
|
// }
|
||||||
|
return Markdoc.transform(getParsed(), config);
|
||||||
|
}
|
||||||
|
return { getParsed, getTransformed };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default function markdoc(partialOptions: {} = {}): AstroIntegration {
|
export default function markdoc(partialOptions: {} = {}): AstroIntegration {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/markdoc',
|
name: '@astrojs/markdoc',
|
||||||
hooks: {
|
hooks: {
|
||||||
'astro:config:setup': async ({ updateConfig, config, addPageExtension, command }: any) => {
|
'astro:config:setup': async ({ updateConfig, config, addContentEntryType, command }: any) => {
|
||||||
addPageExtension('.mdoc');
|
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);
|
||||||
|
|
||||||
|
|
25
packages/integrations/markdoc/src/utils.ts
Normal file
25
packages/integrations/markdoc/src/utils.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import matter from 'gray-matter';
|
||||||
|
import type { ErrorPayload as ViteErrorPayload } from 'vite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match YAML exception handling from Astro core errors
|
||||||
|
* @see 'astro/src/core/errors.ts'
|
||||||
|
*/
|
||||||
|
export function parseFrontmatter(fileContents: string, filePath: string) {
|
||||||
|
try {
|
||||||
|
// `matter` is empty string on cache results
|
||||||
|
// clear cache to prevent this
|
||||||
|
(matter as any).clearCache();
|
||||||
|
return matter(fileContents);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.name === 'YAMLException') {
|
||||||
|
const err: Error & ViteErrorPayload['err'] = e;
|
||||||
|
err.id = filePath;
|
||||||
|
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
||||||
|
err.message = e.reason;
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue