diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 642fe9ffb16f..7764e4966251 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -6,6 +6,7 @@ import type { ShikiConfig, RemarkPlugins, RehypePlugins, + FrontmatterPlugins, MarkdownHeader, MarkdownMetadata, MarkdownRenderingResult, @@ -592,6 +593,23 @@ export interface AstroUserConfig { * ``` */ rehypePlugins?: RehypePlugins; + /** + * @docs + * @name markdown.frontmatterPlugins + * @type {FrontmatterPlugins} + * @description + * Pass a custom Frontmatter plugin to customize Markdown frontmatter. + * + * ```js + * { + * markdown: { + * // Example: The default set of frontmatter plugins used by Astro + * frontmatterPlugins: [], + * }, + * }; + * ``` + */ + frontmatterPlugins?: FrontmatterPlugins; }; /** diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 27550c313d24..693c38bfaeee 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -2,7 +2,7 @@ import type { AstroConfig, AstroUserConfig, CLIFlags, ViteUserConfig } from '../ import type { Arguments as Flags } from 'yargs-parser'; import type * as Postcss from 'postcss'; import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki'; -import type { RemarkPlugin, RehypePlugin } from '@astrojs/markdown-remark'; +import type { RemarkPlugin, RehypePlugin, FrontmatterPlugin } from '@astrojs/markdown-remark'; import * as colors from 'kleur/colors'; import path from 'path'; @@ -180,6 +180,15 @@ export const AstroConfigSchema = z.object({ ]) .array() .default([]), + frontmatterPlugins: z + .union([ + z.string(), + z.tuple([z.string(), z.any()]), + z.custom((data) => typeof data === 'function'), + z.tuple([z.custom((data) => typeof data === 'function'), z.any()]), + ]) + .array() + .default([]), }) .default({}), vite: z diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index 9c7577fc36e6..2f9f53baac69 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -1,4 +1,4 @@ -import { renderMarkdown } from '@astrojs/markdown-remark'; +import { renderMarkdown, loadPlugins } from '@astrojs/markdown-remark'; import { transform } from '@astrojs/compiler'; import ancestor from 'common-ancestor-path'; import esbuild from 'esbuild'; @@ -118,7 +118,17 @@ export default function markdown({ config }: AstroPluginOptions): Plugin { const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr'); // Extract special frontmatter keys - const { data: frontmatter, content: markdownContent } = matter(source); + let { data: frontmatter, content: markdownContent } = matter(source); + + if (renderOpts.frontmatterPlugins.length > 0) { + const loadedFrontmatterPlugins = await Promise.all( + loadPlugins(renderOpts.frontmatterPlugins) + ); + loadedFrontmatterPlugins.forEach(([plugin, opts]) => { + frontmatter = plugin(frontmatter, getFileInfo(id, config), opts); + }); + } + let renderResult = await renderMarkdown(markdownContent, renderOpts); let { code: astroResult, metadata } = renderResult; const { layout = '', components = '', setup = '', ...content } = frontmatter; @@ -162,6 +172,7 @@ ${tsResult}`; sourcemap: false, sourcefile: id, }); + return { code, map: null, diff --git a/packages/astro/test/astro-markdown-plugins.test.js b/packages/astro/test/astro-markdown-plugins.test.js index 350997c66438..35903adde51c 100644 --- a/packages/astro/test/astro-markdown-plugins.test.js +++ b/packages/astro/test/astro-markdown-plugins.test.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; import addClasses from './fixtures/astro-markdown-plugins/add-classes.mjs'; +import updateFrontmatter from './fixtures/astro-markdown-plugins/update-frontmatter.mjs'; describe('Astro Markdown plugins', () => { let fixture; @@ -19,6 +20,7 @@ describe('Astro Markdown plugins', () => { ['rehype-toc', { headings: ['h2', 'h3'] }], [addClasses, { 'h1,h2,h3': 'title' }], ], + frontmatterPlugins: [updateFrontmatter], }, }); await fixture.build(); @@ -32,7 +34,7 @@ describe('Astro Markdown plugins', () => { expect($('.toc')).to.have.lengthOf(1); // teste 2: Added .title to h1 - expect($('#hello-world').hasClass('title')).to.equal(true); + expect($('#hello-world-from-page').hasClass('title')).to.equal(true); }); it('Can render Astro with plugins', async () => { @@ -43,6 +45,15 @@ describe('Astro Markdown plugins', () => { expect($('.toc')).to.have.lengthOf(1); // teste 2: Added .title to h1 - expect($('#hello-world').hasClass('title')).to.equal(true); + expect($('#hello-world-from-component').hasClass('title')).to.equal(true); + }); + + it('Can update Markdown frontmatter from config', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + // test 1: Switched layout + expect($('.container-alt')).to.have.lengthOf(1); + expect($('.container')).to.have.lengthOf(0); }); }); diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/src/layouts/content-alt.astro b/packages/astro/test/fixtures/astro-markdown-plugins/src/layouts/content-alt.astro new file mode 100644 index 000000000000..e82b5d21b376 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-plugins/src/layouts/content-alt.astro @@ -0,0 +1,10 @@ + + + + + +
+ +
+ + diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro index 4ddb6dabdb93..c9661262daf9 100644 --- a/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro +++ b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/astro.astro @@ -5,6 +5,6 @@ import Layout from '../layouts/content.astro'; - # Hello world + # Hello world from component diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md index 832ccf0bc84e..a6e42a1be813 100644 --- a/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md +++ b/packages/astro/test/fixtures/astro-markdown-plugins/src/pages/index.md @@ -2,4 +2,4 @@ layout: ../layouts/content.astro --- -# Hello world +# Hello world from page diff --git a/packages/astro/test/fixtures/astro-markdown-plugins/update-frontmatter.mjs b/packages/astro/test/fixtures/astro-markdown-plugins/update-frontmatter.mjs new file mode 100644 index 000000000000..13b2a2c12eae --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-plugins/update-frontmatter.mjs @@ -0,0 +1,5 @@ +export default (frontmatter, {fileId, fileUrl}) => { + let newFrontmatter = Object.assign({}, frontmatter) + newFrontmatter.layout = "../layouts/content-alt.astro"; + return newFrontmatter; +}; diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts index bf660a508553..e1da70252c85 100644 --- a/packages/markdown/remark/src/index.ts +++ b/packages/markdown/remark/src/index.ts @@ -21,6 +21,8 @@ import rehypeRaw from 'rehype-raw'; export * from './types.js'; +export { loadPlugins }; + export const DEFAULT_REMARK_PLUGINS = ['remark-gfm', 'remark-smartypants']; export const DEFAULT_REHYPE_PLUGINS = []; diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts index 3aef317107fa..edc24da3ec71 100644 --- a/packages/markdown/remark/src/types.ts +++ b/packages/markdown/remark/src/types.ts @@ -19,6 +19,18 @@ export type RehypePlugin = unified.Plugi export type RehypePlugins = (string | [string, any] | RehypePlugin | [RehypePlugin, any])[]; +export type FrontmatterPlugin = unified.Plugin< + PluginParameters, + hast.Root +>; + +export type FrontmatterPlugins = ( + | string + | [string, any] + | FrontmatterPlugin + | [FrontmatterPlugin, any] +)[]; + export interface ShikiConfig { langs: ILanguageRegistration[]; theme: Theme | IThemeRegistration; @@ -32,6 +44,7 @@ export interface AstroMarkdownOptions { shikiConfig: ShikiConfig; remarkPlugins: RemarkPlugins; rehypePlugins: RehypePlugins; + frontmatterPlugins: FrontmatterPlugins; } export interface MarkdownRenderingOptions extends AstroMarkdownOptions {