diff --git a/.changeset/polite-knives-greet.md b/.changeset/polite-knives-greet.md new file mode 100644 index 000000000000..1fee5098508b --- /dev/null +++ b/.changeset/polite-knives-greet.md @@ -0,0 +1,7 @@ +--- +'astro': patch +'@astrojs/markdoc': patch +'@astrojs/mdx': patch +--- + +Improve style and script handling across content collection files. This addresses style bleed present in `@astrojs/markdoc` v0.1.0 diff --git a/examples/with-markdoc/src/content/config.ts b/examples/with-markdoc/src/content/config.ts deleted file mode 100644 index 2eccab0a37c7..000000000000 --- a/examples/with-markdoc/src/content/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineCollection, z } from 'astro:content'; - -const docs = defineCollection({ - schema: z.object({ - title: z.string(), - }), -}); - -export const collections = { docs }; diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index c7b1c4f596f1..11b24ec50098 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1275,6 +1275,12 @@ export interface ContentEntryType { } ): rollup.LoadResult | Promise; contentModuleTypes?: string; + /** + * Handle asset propagation for rendered content to avoid bleed. + * Ex. MDX content can import styles and scripts, so `handlePropagation` should be true. + * @default true + */ + handlePropagation?: boolean; } type GetContentEntryInfoReturnType = { diff --git a/packages/astro/src/content/consts.ts b/packages/astro/src/content/consts.ts index bda154692817..d74e60abdf2f 100644 --- a/packages/astro/src/content/consts.ts +++ b/packages/astro/src/content/consts.ts @@ -1,11 +1,13 @@ export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets'; +export const CONTENT_RENDER_FLAG = 'astroRenderContent'; export const CONTENT_FLAG = 'astroContentCollectionEntry'; export const DATA_FLAG = 'astroDataCollectionEntry'; -export const CONTENT_FLAGS = [CONTENT_FLAG, DATA_FLAG, PROPAGATED_ASSET_FLAG] as const; export const VIRTUAL_MODULE_ID = 'astro:content'; export const LINKS_PLACEHOLDER = '@@ASTRO-LINKS@@'; export const STYLES_PLACEHOLDER = '@@ASTRO-STYLES@@'; export const SCRIPTS_PLACEHOLDER = '@@ASTRO-SCRIPTS@@'; +export const CONTENT_FLAGS = [CONTENT_FLAG, CONTENT_RENDER_FLAG, DATA_FLAG, PROPAGATED_ASSET_FLAG] as const; + export const CONTENT_TYPES_FILE = 'types.d.ts'; diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index f38d9192a263..ece6f7cb1e31 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -270,62 +270,82 @@ async function render({ const baseMod = await renderEntryImport(); if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError; - const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod; - if (typeof getMod !== 'function') throw UnexpectedRenderError; - const mod = await getMod(); - if (mod == null || typeof mod !== 'object') throw UnexpectedRenderError; + if ( + baseMod.default != null && + typeof baseMod.default === 'object' && + baseMod.default.__astroPropagation === true + ) { + const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod.default; + if (typeof getMod !== 'function') throw UnexpectedRenderError; + const propagationMod = await getMod(); + if (propagationMod == null || typeof propagationMod !== 'object') throw UnexpectedRenderError; - const Content = createComponent({ - factory(result, baseProps, slots) { - let styles = '', - links = '', - scripts = ''; - if (Array.isArray(collectedStyles)) { - styles = collectedStyles - .map((style: any) => { - return renderUniqueStylesheet(result, { - type: 'inline', - content: style, - }); - }) - .join(''); - } - if (Array.isArray(collectedLinks)) { - links = collectedLinks - .map((link: any) => { - return renderUniqueStylesheet(result, { - type: 'external', - src: prependForwardSlash(link), - }); - }) - .join(''); - } - if (Array.isArray(collectedScripts)) { - scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join(''); - } + const Content = createComponent({ + factory(result, baseProps, slots) { + let styles = '', + links = '', + scripts = ''; + if (Array.isArray(collectedStyles)) { + styles = collectedStyles + .map((style: any) => { + return renderUniqueStylesheet(result, { + type: 'inline', + content: style, + }); + }) + .join(''); + } + if (Array.isArray(collectedLinks)) { + links = collectedLinks + .map((link: any) => { + return renderUniqueStylesheet(result, { + type: 'external', + src: prependForwardSlash(link), + }); + }) + .join(''); + } + if (Array.isArray(collectedScripts)) { + scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join(''); + } - let props = baseProps; - // Auto-apply MDX components export - if (id.endsWith('mdx')) { - props = { - components: mod.components ?? {}, - ...baseProps, - }; - } + let props = baseProps; + // Auto-apply MDX components export + if (id.endsWith('mdx')) { + props = { + components: propagationMod.components ?? {}, + ...baseProps, + }; + } - return createHeadAndContent( - unescapeHTML(styles + links + scripts) as any, - renderTemplate`${renderComponent(result, 'Content', mod.Content, props, slots)}` - ); - }, - propagation: 'self', - }); + return createHeadAndContent( + unescapeHTML(styles + links + scripts) as any, + renderTemplate`${renderComponent( + result, + 'Content', + propagationMod.Content, + props, + slots + )}` + ); + }, + propagation: 'self', + }); - return { - Content, - headings: mod.getHeadings?.() ?? [], - remarkPluginFrontmatter: mod.frontmatter ?? {}, - }; + return { + Content, + headings: propagationMod.getHeadings?.() ?? [], + remarkPluginFrontmatter: propagationMod.frontmatter ?? {}, + }; + } else if (baseMod.Content && typeof baseMod.Content === 'function') { + return { + Content: baseMod.Content, + headings: baseMod.getHeadings?.() ?? [], + remarkPluginFrontmatter: baseMod.frontmatter ?? {}, + }; + } else { + throw UnexpectedRenderError; + } } export function createReference({ lookupMap }: { lookupMap: ContentLookupMap }) { diff --git a/packages/astro/src/content/template/virtual-mod.mjs b/packages/astro/src/content/template/virtual-mod.mjs index 491e594a6388..e0ac7a5644c4 100644 --- a/packages/astro/src/content/template/virtual-mod.mjs +++ b/packages/astro/src/content/template/virtual-mod.mjs @@ -46,7 +46,7 @@ function createGlobLookup(glob) { } const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', { - query: { astroPropagatedAssets: true }, + query: { astroRenderContent: true }, }); const collectionToRenderEntryMap = createCollectionToGlobResultMap({ globResult: renderEntryGlob, diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 831b25fb468e..82a0f7992226 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -14,6 +14,7 @@ import type { } from '../@types/astro.js'; import { VALID_INPUT_FORMATS } from '../assets/consts.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; + import { formatYAMLException, isYAMLException } from '../core/errors/utils.js'; import { CONTENT_FLAGS, CONTENT_TYPES_FILE } from './consts.js'; import { errorMap } from './error-map.js'; @@ -328,7 +329,7 @@ export function parseFrontmatter(fileContents: string, filePath: string) { */ export const globalContentConfigObserver = contentObservable({ status: 'init' }); -export function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]) { +export function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]): boolean { const flags = new URLSearchParams(viteId.split('?')[1] ?? ''); return flags.has(flag); } diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts index 7e73f9f6bdbc..bbd9749a806a 100644 --- a/packages/astro/src/content/vite-plugin-content-assets.ts +++ b/packages/astro/src/content/vite-plugin-content-assets.ts @@ -1,4 +1,5 @@ -import { pathToFileURL } from 'url'; +import { extname } from 'node:path'; +import { pathToFileURL } from 'node:url'; import type { Plugin } from 'vite'; import type { AstroSettings } from '../@types/astro.js'; import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; @@ -11,16 +12,13 @@ import { joinPaths, prependForwardSlash } from '../core/path.js'; import { getStylesForURL } from '../core/render/dev/css.js'; import { getScriptsForURL } from '../core/render/dev/scripts.js'; import { + CONTENT_RENDER_FLAG, LINKS_PLACEHOLDER, PROPAGATED_ASSET_FLAG, SCRIPTS_PLACEHOLDER, STYLES_PLACEHOLDER, } from './consts.js'; - -function isPropagatedAsset(viteId: string) { - const flags = new URLSearchParams(viteId.split('?')[1]); - return flags.has(PROPAGATED_ASSET_FLAG); -} +import { hasContentFlag } from './utils.js'; export function astroContentAssetPropagationPlugin({ mode, @@ -32,13 +30,31 @@ export function astroContentAssetPropagationPlugin({ let devModuleLoader: ModuleLoader; return { name: 'astro:content-asset-propagation', + enforce: 'pre', + async resolveId(id, importer, opts) { + if (hasContentFlag(id, CONTENT_RENDER_FLAG)) { + const base = id.split('?')[0]; + + for (const { extensions, handlePropagation = true } of settings.contentEntryTypes) { + if (handlePropagation && extensions.includes(extname(base))) { + return this.resolve(`${base}?${PROPAGATED_ASSET_FLAG}`, importer, { + skipSelf: true, + ...opts, + }); + } + } + // Resolve to the base id (no content flags) + // if Astro doesn't need to handle propagation. + return this.resolve(base, importer, { skipSelf: true, ...opts }); + } + }, configureServer(server) { if (mode === 'dev') { devModuleLoader = createViteLoader(server); } }, async transform(_, id, options) { - if (isPropagatedAsset(id)) { + if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) { const basePath = id.split('?')[0]; let stringifiedLinks: string, stringifiedStyles: string, stringifiedScripts: string; @@ -73,14 +89,17 @@ export function astroContentAssetPropagationPlugin({ } const code = ` - export async function getMod() { + async function getMod() { return import(${JSON.stringify(basePath)}); } - export const collectedLinks = ${stringifiedLinks}; - export const collectedStyles = ${stringifiedStyles}; - export const collectedScripts = ${stringifiedScripts}; + const collectedLinks = ${stringifiedLinks}; + const collectedStyles = ${stringifiedStyles}; + const collectedScripts = ${stringifiedScripts}; + const defaultMod = { __astroPropagation: true, getMod, collectedLinks, collectedStyles, collectedScripts }; + export default defaultMod; `; - + // ^ Use a default export for tools like Markdoc + // to catch the `__astroPropagation` identifier return { code, map: { mappings: '' } }; } }, diff --git a/packages/astro/src/core/render/dev/vite.ts b/packages/astro/src/core/render/dev/vite.ts index 724ad172f3ae..b9a88e2ac00c 100644 --- a/packages/astro/src/core/render/dev/vite.ts +++ b/packages/astro/src/core/render/dev/vite.ts @@ -1,7 +1,6 @@ import type { ModuleLoader, ModuleNode } from '../../module-loader/index'; import npath from 'path'; -import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js'; import { unwrapId } from '../../util.js'; import { isCSSRequest } from './util.js'; @@ -10,9 +9,10 @@ import { isCSSRequest } from './util.js'; * List of file extensions signalling we can (and should) SSR ahead-of-time * See usage below */ -const fileExtensionsToSSR = new Set(['.astro', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]); +const fileExtensionsToSSR = new Set(['.astro', '.mdoc', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]); const STRIP_QUERY_PARAMS_REGEX = /\?.*$/; +const ASTRO_PROPAGATED_ASSET_REGEX = /\?astroPropagatedAssets/; /** recursively crawl the module graph to get all style files imported by parent id */ export async function* crawlGraph( @@ -23,7 +23,6 @@ export async function* crawlGraph( ): AsyncGenerator { const id = unwrapId(_id); const importedModules = new Set(); - if (new URL(id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) return; const moduleEntriesForId = isRootFile ? // "getModulesByFile" pulls from a delayed module cache (fun implementation detail), @@ -44,6 +43,7 @@ export async function* crawlGraph( if (id === entry.id) { scanned.add(id); const entryIsStyle = isCSSRequest(id); + for (const importedModule of entry.importedModules) { // some dynamically imported modules are *not* server rendered in time // to only SSR modules that we can safely transform, we check against @@ -60,15 +60,11 @@ export async function* crawlGraph( if (entryIsStyle && !isCSSRequest(importedModulePathname)) { continue; } + const isFileTypeNeedingSSR = fileExtensionsToSSR.has(npath.extname(importedModulePathname)); if ( - fileExtensionsToSSR.has( - npath.extname( - // Use `id` instead of `pathname` to preserve query params. - // Should not SSR a module with an unexpected query param, - // like "?astroPropagatedAssets" - importedModule.id - ) - ) + isFileTypeNeedingSSR && + // Should not SSR a module with ?astroPropagatedAssets + !ASTRO_PROPAGATED_ASSET_REGEX.test(importedModule.id) ) { const mod = loader.getModuleById(importedModule.id); if (!mod?.ssrModule) { diff --git a/packages/astro/src/vite-plugin-head/index.ts b/packages/astro/src/vite-plugin-head/index.ts index fc19893e831e..4f44aaf6e487 100644 --- a/packages/astro/src/vite-plugin-head/index.ts +++ b/packages/astro/src/vite-plugin-head/index.ts @@ -9,7 +9,8 @@ import { getTopLevelPages, walkParentInfos } from '../core/build/graph.js'; import type { BuildInternals } from '../core/build/internal.js'; import { getAstroMetadata } from '../vite-plugin-astro/index.js'; -const injectExp = /^\/\/\s*astro-head-inject/; +// Detect this in comments, both in .astro components and in js/ts files. +const injectExp = /(^\/\/|\/\/!)\s*astro-head-inject/; export default function configHeadVitePlugin({ settings, @@ -32,6 +33,7 @@ export default function configHeadVitePlugin({ seen.add(id); const mod = server.moduleGraph.getModuleById(id); const info = this.getModuleInfo(id); + if (info?.meta.astro) { const astroMetadata = getAstroMetadata(info); if (astroMetadata) { diff --git a/packages/astro/src/vite-plugin-markdown/content-entry-type.ts b/packages/astro/src/vite-plugin-markdown/content-entry-type.ts index cbf5cc957e36..a3489c940132 100644 --- a/packages/astro/src/vite-plugin-markdown/content-entry-type.ts +++ b/packages/astro/src/vite-plugin-markdown/content-entry-type.ts @@ -13,6 +13,8 @@ export const markdownContentEntryType: ContentEntryType = { rawData: parsed.matter, }; }, + // We need to handle propagation for Markdown because they support layouts which will bring in styles. + handlePropagation: true, }; /** @@ -30,6 +32,9 @@ export const mdxContentEntryType: ContentEntryType = { rawData: parsed.matter, }; }, + // MDX can import scripts and styles, + // so wrap all MDX files with script / style propagation checks + handlePropagation: true, contentModuleTypes: `declare module 'astro:content' { interface Render { '.mdx': Promise<{ diff --git a/packages/integrations/markdoc/components/Renderer.astro b/packages/integrations/markdoc/components/Renderer.astro index 5e2b6833a55a..6571e8c7175f 100644 --- a/packages/integrations/markdoc/components/Renderer.astro +++ b/packages/integrations/markdoc/components/Renderer.astro @@ -1,4 +1,5 @@ --- +//! astro-head-inject import type { Config } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; import { ComponentNode, createTreeNode } from './TreeNode.js'; @@ -14,4 +15,4 @@ const ast = Markdoc.Ast.fromJSON(stringifiedAst); const content = Markdoc.transform(ast, config); --- - + diff --git a/packages/integrations/markdoc/components/TreeNode.ts b/packages/integrations/markdoc/components/TreeNode.ts index a60597a0d881..3f9740af1633 100644 --- a/packages/integrations/markdoc/components/TreeNode.ts +++ b/packages/integrations/markdoc/components/TreeNode.ts @@ -2,7 +2,16 @@ import type { AstroInstance } from 'astro'; import { Fragment } from 'astro/jsx-runtime'; import type { RenderableTreeNode } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; -import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js'; +import { + createComponent, + renderComponent, + render, + renderScriptElement, + renderUniqueStylesheet, + createHeadAndContent, + unescapeHTML, + renderTemplate, +} from 'astro/runtime/server/index.js'; export type TreeNode = | { @@ -12,6 +21,9 @@ export type TreeNode = | { type: 'component'; component: AstroInstance['default']; + collectedLinks?: string[]; + collectedStyles?: string[]; + collectedScripts?: string[]; props: Record; children: TreeNode[]; } @@ -32,20 +44,63 @@ export const ComponentNode = createComponent({ )}`, }; if (treeNode.type === 'component') { - return renderComponent( - result, - treeNode.component.name, - treeNode.component, - treeNode.props, - slots + let styles = '', + links = '', + scripts = ''; + if (Array.isArray(treeNode.collectedStyles)) { + styles = treeNode.collectedStyles.map((style: any) => renderUniqueStylesheet({ + type: 'inline', + content: style, + })).join(''); + } + if (Array.isArray(treeNode.collectedLinks)) { + links = treeNode.collectedLinks + .map((link: any) => { + return renderUniqueStylesheet(result, { + href: link[0] === '/' ? link : '/' + link, + }); + }) + .join(''); + } + if (Array.isArray(treeNode.collectedScripts)) { + scripts = treeNode.collectedScripts + .map((script: any) => renderScriptElement(script)) + .join(''); + } + + const head = unescapeHTML(styles + links + scripts); + + let headAndContent = createHeadAndContent( + head, + renderTemplate`${renderComponent( + result, + treeNode.component.name, + treeNode.component, + treeNode.props, + slots + )}` ); + + // Let the runtime know that this component is being used. + result.propagators.set( + {}, + { + init() { + return headAndContent; + }, + } + ); + + return headAndContent; } return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots); }, - propagation: 'none', + propagation: 'self', }); -export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]): TreeNode { +export async function createTreeNode( + node: RenderableTreeNode | RenderableTreeNode[] +): Promise { if (typeof node === 'string' || typeof node === 'number') { return { type: 'text', content: String(node) }; } else if (Array.isArray(node)) { @@ -53,16 +108,17 @@ export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]): type: 'component', component: Fragment, props: {}, - children: node.map((child) => createTreeNode(child)), + children: await Promise.all(node.map((child) => createTreeNode(child))), }; } else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) { return { type: 'text', content: '' }; } + const children = await Promise.all(node.children.map((child) => createTreeNode(child))); + if (typeof node.name === 'function') { const component = node.name; const props = node.attributes; - const children = node.children.map((child) => createTreeNode(child)); return { type: 'component', @@ -70,12 +126,38 @@ export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]): props, children, }; + } else if (isPropagatedAssetsModule(node.name)) { + const { collectedStyles, collectedLinks, collectedScripts } = node.name; + const component = (await node.name.getMod())?.default ?? Fragment; + const props = node.attributes; + + return { + type: 'component', + component, + collectedStyles, + collectedLinks, + collectedScripts, + props, + children, + }; } else { return { type: 'element', tag: node.name, attributes: node.attributes, - children: node.children.map((child) => createTreeNode(child)), + children, }; } } + +type PropagatedAssetsModule = { + __astroPropagation: true; + getMod: () => Promise; + collectedStyles: string[]; + collectedLinks: string[]; + collectedScripts: string[]; +}; + +function isPropagatedAssetsModule(module: any): module is PropagatedAssetsModule { + return typeof module === 'object' && module != null && '__astroPropagation' in module; +} diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index 627f08c778fe..ba8a0af84849 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -32,7 +32,11 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration name: '@astrojs/markdoc', hooks: { 'astro:config:setup': async (params) => { - const { config: astroConfig, addContentEntryType } = params as SetupHookParams; + const { + config: astroConfig, + updateConfig, + addContentEntryType, + } = params as SetupHookParams; markdocConfigResult = await loadMarkdocConfig(astroConfig); const userMarkdocConfig = markdocConfigResult?.config ?? {}; @@ -49,6 +53,9 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration addContentEntryType({ extensions: ['.mdoc'], getEntryInfo, + // Markdoc handles script / style propagation + // for Astro components internally + handlePropagation: false, async getRenderModule({ entry, viteId }) { const ast = Markdoc.parse(entry.body); const pluginContext = this; @@ -88,7 +95,10 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration }); } - const res = `import { jsx as h } from 'astro/jsx-runtime'; + const res = `import { + createComponent, + renderComponent, + } from 'astro/runtime/server/index.js'; import { Renderer } from '@astrojs/markdoc/components'; import { collectHeadings, setupConfig, Markdoc } from '@astrojs/markdoc/runtime'; import * as entry from ${JSON.stringify(viteId + '?astroContentCollectionEntry')}; @@ -119,14 +129,24 @@ export function getHeadings() { const content = Markdoc.transform(ast, config); return collectHeadings(Array.isArray(content) ? content : content.children); } -export async function Content (props) { - const config = setupConfig({ - ...userConfig, - variables: { ...userConfig.variables, ...props }, - }, entry); - return h(Renderer, { config, stringifiedAst }); -}`; +export const Content = createComponent({ + factory(result, props) { + const config = setupConfig({ + ...userConfig, + variables: { ...userConfig.variables, ...props }, + }, entry); + + return renderComponent( + result, + Renderer.name, + Renderer, + { stringifiedAst, config }, + {} + ); + }, + propagation: 'self', +});`; return { code: res }; }, contentModuleTypes: await fs.promises.readFile( @@ -134,6 +154,27 @@ export async function Content (props) { 'utf-8' ), }); + + updateConfig({ + vite: { + plugins: [ + { + name: '@astrojs/markdoc:astro-propagated-assets', + enforce: 'pre', + // Astro component styles and scripts should only be injected + // When a given Markdoc file actually uses that component. + // Add the `astroPropagatedAssets` flag to inject only when rendered. + resolveId(this: rollup.TransformPluginContext, id: string, importer: string) { + if (importer === markdocConfigResult?.fileUrl.pathname && id.endsWith('.astro')) { + return this.resolve(id + '?astroPropagatedAssets', importer, { + skipSelf: true, + }); + } + }, + }, + ], + }, + }); }, 'astro:server:setup': async ({ server }) => { server.watcher.on('all', (event, entry) => { diff --git a/packages/integrations/markdoc/src/nodes/heading.ts b/packages/integrations/markdoc/src/nodes/heading.ts index 0210e9b9080e..cb50dd231c7e 100644 --- a/packages/integrations/markdoc/src/nodes/heading.ts +++ b/packages/integrations/markdoc/src/nodes/heading.ts @@ -37,13 +37,14 @@ export const heading: Schema = { const slug = getSlug(attributes, children, config.ctx.headingSlugger); const render = config.nodes?.heading?.render ?? `h${level}`; + const tagProps = // For components, pass down `level` as a prop, // alongside `__collectHeading` for our `headings` collector. // Avoid accidentally rendering `level` as an HTML attribute otherwise! - typeof render === 'function' - ? { ...attributes, id: slug, __collectHeading: true, level } - : { ...attributes, id: slug }; + typeof render === 'string' + ? { ...attributes, id: slug } + : { ...attributes, id: slug, __collectHeading: true, level }; return new Markdoc.Tag(render, tagProps, children); }, diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index 2ccf6626654b..1ef23e1afd2e 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -55,6 +55,9 @@ export default function mdx(partialMdxOptions: Partial = {}): AstroI new URL('../template/content-module-types.d.ts', import.meta.url), 'utf-8' ), + // MDX can import scripts and styles, + // so wrap all MDX files with script / style propagation checks + handlePropagation: true, }); const extendMarkdownConfig =