mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 13:48:14 +00:00
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
import type { ParseDataOptions } from 'astro/loaders';
|
|
|
|
import { glob } from 'astro/loaders';
|
|
|
|
/**
|
|
* Parser function that runs before Astro's schema validation.
|
|
* Receives the entry and the original filename (before generateId transforms it).
|
|
*/
|
|
type Parser = <TData extends Record<string, unknown>>(
|
|
options: ParseDataOptions<TData>,
|
|
originalEntry: string
|
|
) => Promise<ParseDataOptions<TData>>;
|
|
|
|
type GlobWithParserOptions = Parameters<typeof glob>[0] & {
|
|
parser: Parser;
|
|
};
|
|
|
|
/**
|
|
* Wraps Astro's glob loader to provide a parser function that has access to both
|
|
* the transformed entry and the original filename.
|
|
*
|
|
* This is useful when using generateId to transform entry IDs (e.g., for clean URLs)
|
|
* but still needing the original filename to extract metadata (e.g., dates from filenames).
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* loader: globWithParser({
|
|
* base: './src/content/blog',
|
|
* pattern: '**\/*.mdx',
|
|
* generateId: ({ entry }) => entry.replace(/^\d{4}-\d{2}-\d{2}-/, ''),
|
|
* parser: async (entry, originalEntry) => {
|
|
* // entry.id = "my-post", originalEntry = "2024-01-01-my-post.mdx"
|
|
* const date = extractDateFromFilename(originalEntry);
|
|
* entry.data.pubDate = date;
|
|
* return entry;
|
|
* }
|
|
* })
|
|
* ```
|
|
*/
|
|
export function globWithParser({ parser, generateId, ...globOptions }: GlobWithParserOptions) {
|
|
/**
|
|
* Store mapping of transformed IDs to original entry filenames.
|
|
* Created per-invocation to avoid memory leaks across builds.
|
|
* This is needed because generateId transforms the entry name (e.g., removes date prefix),
|
|
* but we need access to the original filename in the parser (e.g., to extract date from filename).
|
|
*/
|
|
const entryMap = new Map<string, string>();
|
|
|
|
// Wrap generateId to capture the original entry name before transformation
|
|
// This allows us to maintain a mapping from the transformed ID back to the original filename
|
|
const wrappedGenerateId = generateId
|
|
? (ctx: Parameters<NonNullable<typeof generateId>>[0]) => {
|
|
const newId = generateId(ctx);
|
|
// Store mapping: transformed ID -> original filename
|
|
entryMap.set(newId, ctx.entry);
|
|
return newId;
|
|
}
|
|
: undefined;
|
|
|
|
// Create the base glob loader with our wrapped generateId
|
|
const loader = glob({ ...globOptions, generateId: wrappedGenerateId });
|
|
const originalLoad = loader.load;
|
|
|
|
// Intercept the load function to inject our custom parser
|
|
// This allows us to provide both the transformed entry and original filename to the parser
|
|
loader.load = async ({ parseData, ...rest }) => {
|
|
return originalLoad({
|
|
parseData: async (entry) => {
|
|
// Retrieve the original filename from our map, falling back to entry.id if not found
|
|
const originalEntry = entryMap.get(entry.id) || entry.id;
|
|
// Call user's parser with both the transformed entry and original filename
|
|
return parseData(await parser(entry, originalEntry));
|
|
},
|
|
...rest,
|
|
});
|
|
};
|
|
|
|
return loader;
|
|
}
|