Revert "revert: markdoc asset bleed (#7178)"
This reverts commit 57e65d247f
.
This commit is contained in:
parent
57e65d247f
commit
298524a55a
16 changed files with 294 additions and 120 deletions
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
'@astrojs/markdoc': patch
|
||||
'@astrojs/mdx': patch
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix: revert Markdoc asset bleed changes. Production build issues were discovered that deserve a different fix.
|
|
@ -1,9 +0,0 @@
|
|||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const docs = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { docs };
|
|
@ -1275,6 +1275,12 @@ export interface ContentEntryType {
|
|||
}
|
||||
): rollup.LoadResult | Promise<rollup.LoadResult>;
|
||||
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 = {
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
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';
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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: '' } };
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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<ModuleNode, void, unknown> {
|
||||
const id = unwrapId(_id);
|
||||
const importedModules = new Set<ModuleNode>();
|
||||
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,13 @@ 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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<{
|
||||
|
|
|
@ -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);
|
||||
---
|
||||
|
||||
<ComponentNode treeNode={createTreeNode(content)} />
|
||||
<ComponentNode treeNode={await createTreeNode(content)} />
|
||||
|
|
|
@ -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<string, any>;
|
||||
children: TreeNode[];
|
||||
}
|
||||
|
@ -32,20 +44,67 @@ 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<TreeNode> {
|
||||
if (typeof node === 'string' || typeof node === 'number') {
|
||||
return { type: 'text', content: String(node) };
|
||||
} else if (Array.isArray(node)) {
|
||||
|
@ -53,16 +112,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 +130,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<AstroInstance['default']>;
|
||||
collectedStyles: string[];
|
||||
collectedLinks: string[];
|
||||
collectedScripts: string[];
|
||||
};
|
||||
|
||||
function isPropagatedAssetsModule(module: any): module is PropagatedAssetsModule {
|
||||
return typeof module === 'object' && module != null && '__astroPropagation' in module;
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -55,6 +55,9 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): 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 =
|
||||
|
|
Loading…
Reference in a new issue