diff options
author | 2023-06-27 15:05:17 -0400 | |
---|---|---|
committer | 2023-06-27 15:05:17 -0400 | |
commit | fb7af551148f5ca6c4f98a4e556c8948c5690919 (patch) | |
tree | 7c65c758ead74ee129bc958279a6cf02e931cb4d /packages/integrations/markdoc/src/runtime.ts | |
parent | 8821f0504845b351247ce6c1e2ae581a71806209 (diff) | |
download | astro-fb7af551148f5ca6c4f98a4e556c8948c5690919.tar.gz astro-fb7af551148f5ca6c4f98a4e556c8948c5690919.tar.zst astro-fb7af551148f5ca6c4f98a4e556c8948c5690919.zip |
feat: New Markdoc `render` API (#7468)
* feat: URL support for markdoc tags
* refactor: move to separate file
* feat: support URL for markdoc nodes
* feat: support `extends` with URL
* chore: changeset
* fix: bad AstroMarkdocConfig type
* fix: experimentalAssetsConfig missing
* fix: correctly merge runtime config
* chore: formatting
* deps: astro internal helpers
* feat: component() util, new astro bundling
* chore: remove now unused code
* todo: missing hint
* fix: import.meta.url type error
* wip: test nested collection calls
* feat: resolve paths from project root
* refactor: move getHeadings() to runtime module
* fix: broken collectHeadings
* test: update fixture configs
* chore: remove suggestions. Out of scope!
* fix: throw outside esbuild
* refactor: shuffle imports around
* Revert "wip: test nested collection calls"
This reverts commit 9354b3cf9222fd65b974b0cddf4e7a95ab3cd2b2.
* chore: revert back to mjs config
* chore: add jsdocs to stringified helpers
* fix: restore updated changeset
---------
Co-authored-by: bholmesdev <bholmesdev@gmail.com>
Diffstat (limited to 'packages/integrations/markdoc/src/runtime.ts')
-rw-r--r-- | packages/integrations/markdoc/src/runtime.ts | 100 |
1 files changed, 85 insertions, 15 deletions
diff --git a/packages/integrations/markdoc/src/runtime.ts b/packages/integrations/markdoc/src/runtime.ts index b0e8f2554..d710f1bd8 100644 --- a/packages/integrations/markdoc/src/runtime.ts +++ b/packages/integrations/markdoc/src/runtime.ts @@ -1,19 +1,25 @@ import type { MarkdownHeading } from '@astrojs/markdown-remark'; -import Markdoc, { type RenderableTreeNode } from '@markdoc/markdoc'; +import type { AstroInstance } from 'astro'; +import { + createComponent, + renderComponent, + // @ts-expect-error Cannot find module 'astro/runtime/server/index.js' or its corresponding type declarations. +} from 'astro/runtime/server/index.js'; +import Markdoc, { + type ConfigType, + type Node, + type NodeType, + type RenderableTreeNode, +} from '@markdoc/markdoc'; import type { AstroMarkdocConfig } from './config.js'; import { setupHeadingConfig } from './heading-ids.js'; -/** Used to call `Markdoc.transform()` and `Markdoc.Ast` in runtime modules */ -export { default as Markdoc } from '@markdoc/markdoc'; - /** * 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? */ -export async function setupConfig( - userConfig: AstroMarkdocConfig -): Promise<Omit<AstroMarkdocConfig, 'extends'>> { +export async function setupConfig(userConfig: AstroMarkdocConfig = {}): Promise<MergedConfig> { let defaultConfig: AstroMarkdocConfig = setupHeadingConfig(); if (userConfig.extends) { @@ -30,16 +36,19 @@ export async function setupConfig( } /** Used for synchronous `getHeadings()` function */ -export function setupConfigSync( - userConfig: AstroMarkdocConfig -): Omit<AstroMarkdocConfig, 'extends'> { +export function setupConfigSync(userConfig: AstroMarkdocConfig = {}): MergedConfig { const defaultConfig: AstroMarkdocConfig = setupHeadingConfig(); return mergeConfig(defaultConfig, userConfig); } +type MergedConfig = Required<Omit<AstroMarkdocConfig, 'extends'>>; + /** Merge function from `@markdoc/markdoc` internals */ -function mergeConfig(configA: AstroMarkdocConfig, configB: AstroMarkdocConfig): AstroMarkdocConfig { +export function mergeConfig( + configA: AstroMarkdocConfig, + configB: AstroMarkdocConfig +): MergedConfig { return { ...configA, ...configB, @@ -63,9 +72,33 @@ function mergeConfig(configA: AstroMarkdocConfig, configB: AstroMarkdocConfig): ...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 */ @@ -87,8 +120,10 @@ 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[]): MarkdownHeading[] { - let collectedHeadings: MarkdownHeading[] = []; +export function collectHeadings( + children: RenderableTreeNode[], + collectedHeadings: MarkdownHeading[] +) { for (const node of children) { if (typeof node !== 'object' || !Markdoc.Tag.isTag(node)) continue; @@ -110,7 +145,42 @@ export function collectHeadings(children: RenderableTreeNode[]): MarkdownHeading }); } } - collectedHeadings.concat(collectHeadings(node.children)); + collectHeadings(node.children, collectedHeadings); } - return collectedHeadings; +} + +export function createGetHeadings(stringifiedAst: string, userConfig: AstroMarkdocConfig) { + 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. */ + const config = setupConfigSync(userConfig); + 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, + 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( + await setupConfig(withVariables), + tagComponentMap, + nodeComponentMap + ); + + return renderComponent(result, Renderer.name, Renderer, { stringifiedAst, config }, {}); + }, + propagation: 'self', + }); } |