astro/packages/integrations/markdoc/src/runtime.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

214 lines
6 KiB
TypeScript
Raw Normal View History

import type { MarkdownHeading } from '@astrojs/markdown-remark';
import Markdoc, {
type ConfigType,
type Node,
type NodeType,
type RenderableTreeNode,
} from '@markdoc/markdoc';
2023-06-27 19:08:29 +00:00
import type { AstroInstance } from 'astro';
import { createComponent, renderComponent } from 'astro/runtime/server/index.js';
import { type AstroMarkdocConfig } from './config.js';
2023-05-24 20:54:43 +00:00
import { setupHeadingConfig } from './heading-ids.js';
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
import { htmlTag } from './html/tagdefs/html.tag.js';
2023-07-24 23:36:32 +00:00
import type { MarkdocIntegrationOptions } from './options.js';
/**
* Merge user config with default config and set up context (ex. heading ID slugger)
* Called on each file's individual transform.
* TODO: virtual module to merge configs per-build instead of per-file?
*/
2023-07-24 23:36:32 +00:00
export async function setupConfig(
userConfig: AstroMarkdocConfig = {},
options: MarkdocIntegrationOptions | undefined
): Promise<MergedConfig> {
let defaultConfig: AstroMarkdocConfig = setupHeadingConfig();
if (userConfig.extends) {
for (let extension of userConfig.extends) {
if (extension instanceof Promise) {
extension = await extension;
}
defaultConfig = mergeConfig(defaultConfig, extension);
}
}
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
let merged = mergeConfig(defaultConfig, userConfig);
2023-07-24 23:36:32 +00:00
if (options?.allowHTML) {
merged = mergeConfig(merged, HTML_CONFIG);
}
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
2023-07-24 23:36:32 +00:00
return merged;
}
/** Used for synchronous `getHeadings()` function */
2023-07-24 23:36:32 +00:00
export function setupConfigSync(
userConfig: AstroMarkdocConfig = {},
options: MarkdocIntegrationOptions | undefined
): MergedConfig {
const defaultConfig: AstroMarkdocConfig = setupHeadingConfig();
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
let merged = mergeConfig(defaultConfig, userConfig);
2023-07-24 23:36:32 +00:00
if (options?.allowHTML) {
merged = mergeConfig(merged, HTML_CONFIG);
}
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
2023-07-24 23:36:32 +00:00
return merged;
}
type MergedConfig = Required<Omit<AstroMarkdocConfig, 'extends'>>;
/** Merge function from `@markdoc/markdoc` internals */
export function mergeConfig(
configA: AstroMarkdocConfig,
configB: AstroMarkdocConfig
): MergedConfig {
return {
...configA,
...configB,
ctx: {
...configA.ctx,
...configB.ctx,
},
tags: {
...configA.tags,
...configB.tags,
},
nodes: {
...configA.nodes,
...configB.nodes,
},
functions: {
...configA.functions,
...configB.functions,
},
variables: {
...configA.variables,
...configB.variables,
},
partials: {
...configA.partials,
...configB.partials,
},
validation: {
...configA.validation,
...configB.validation,
},
};
}
export function resolveComponentImports(
markdocConfig: Required<Pick<AstroMarkdocConfig, 'tags' | 'nodes'>>,
tagComponentMap: Record<string, AstroInstance['default']>,
nodeComponentMap: Record<NodeType, AstroInstance['default']>
) {
for (const [tag, render] of Object.entries(tagComponentMap)) {
const config = markdocConfig.tags[tag];
if (config) config.render = render;
}
for (const [node, render] of Object.entries(nodeComponentMap)) {
const config = markdocConfig.nodes[node as NodeType];
if (config) config.render = render;
}
return markdocConfig;
}
/**
* Get text content as a string from a Markdoc transform AST
*/
export function getTextContent(childNodes: RenderableTreeNode[]): string {
let text = '';
for (const node of childNodes) {
if (typeof node === 'string' || typeof node === 'number') {
text += node;
} else if (typeof node === 'object' && Markdoc.Tag.isTag(node)) {
text += getTextContent(node.children);
}
}
return text;
}
const headingLevels = [1, 2, 3, 4, 5, 6] as const;
/**
* Collect headings from Markdoc transform AST
* for `headings` result on `render()` return value
*/
export function collectHeadings(
children: RenderableTreeNode[],
collectedHeadings: MarkdownHeading[]
) {
for (const node of children) {
if (typeof node !== 'object' || !Markdoc.Tag.isTag(node)) continue;
if (node.attributes.__collectHeading === true && typeof node.attributes.level === 'number') {
collectedHeadings.push({
slug: node.attributes.id,
depth: node.attributes.level,
text: getTextContent(node.children),
});
continue;
}
for (const level of headingLevels) {
if (node.name === 'h' + level) {
collectedHeadings.push({
slug: node.attributes.id,
depth: level,
text: getTextContent(node.children),
});
}
}
collectHeadings(node.children, collectedHeadings);
}
}
2023-07-24 23:36:32 +00:00
export function createGetHeadings(
stringifiedAst: string,
userConfig: AstroMarkdocConfig,
options: MarkdocIntegrationOptions | undefined
) {
return function getHeadings() {
/* Yes, we are transforming twice (once from `getHeadings()` and again from <Content /> in case of variables).
TODO: propose new `render()` API to allow Markdoc variable passing to `render()` itself,
instead of the Content component. Would remove double-transform and unlock variable resolution in heading slugs. */
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
const config = setupConfigSync(userConfig, options);
const ast = Markdoc.Ast.fromJSON(stringifiedAst);
const content = Markdoc.transform(ast as Node, config as ConfigType);
let collectedHeadings: MarkdownHeading[] = [];
collectHeadings(Array.isArray(content) ? content : [content], collectedHeadings);
return collectedHeadings;
};
}
export function createContentComponent(
Renderer: AstroInstance['default'],
stringifiedAst: string,
userConfig: AstroMarkdocConfig,
2023-07-24 23:36:32 +00:00
options: MarkdocIntegrationOptions | undefined,
tagComponentMap: Record<string, AstroInstance['default']>,
nodeComponentMap: Record<NodeType, AstroInstance['default']>
) {
return createComponent({
async factory(result: any, props: Record<string, any>) {
const withVariables = mergeConfig(userConfig, { variables: props });
const config = resolveComponentImports(
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
await setupConfig(withVariables, options),
tagComponentMap,
nodeComponentMap
);
return renderComponent(result, Renderer.name, Renderer, { stringifiedAst, config }, {});
},
propagation: 'self',
2023-07-24 18:47:07 +00:00
} as any);
}
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
// statically define a partial MarkdocConfig which registers the required "html-tag" Markdoc tag when the "allowHTML" feature is enabled
const HTML_CONFIG: AstroMarkdocConfig = {
2023-07-24 23:36:32 +00:00
tags: {
'html-tag': htmlTag,
},
Add "allowHTML" option for Markdoc with HTML parsing/processing (#7597) * 7576 - initial support for HTML inside Markdoc. This uses htmlparser2 to perform a pure token transform/mutation on the markdown-it tokens, replacing the original raw HTML string tokens with a richer set of tokens per HTML node, and in the process Markdoc tags are interleaved in the resulting token graph at the appropriate locations This removes the legacy config of the @astrojs/markdoc integration entirely (suggested by @bholmesdev) and introduces a new type for options to be specified in the astro config, initially, with just the new "enableHTML" option When "enableHTML" is *not* enabled (the default), the behavior of the entire @astrojs/markdoc integration should remain functionally equivalent to before this change * 7576 - fixed issues with whitespace preservation also: * cleaned up " to ' for astro project preferred linting * made the html rendering test fixture use a dynamic path * 7576 - detailed nested HTML test coverage * 7576 - component + HTML interleaved tests * 7576 - fix lint problems from previous changes * 7576 - some commentary * 7576 - file naming, refactor html under imports, package.json exports definition for html * 7576 * move out of extensions dir, remove export * cdata handling changes * 7576 * inline license from third party code * cleanup test class copy of HTML output * remove // third party indicators for imports (clarification: not third party code, just a indicator this group of imports is third party) * 7576 - fixed test before/after for DRY'ness * 7576 - no need to React-ify HTML attribute case * 7576 - rename "enableHTML" option to "allowHTML" * Added Markdoc allowHTML feature changeset * 7576 - updated README with allowHTML info * 7576 - fixed changeset typo * 7576 - minor edits based on PR feedback for docs * 7576 - minor edits based on PR feedback for docs
2023-07-24 23:34:06 +00:00
};