Skip to content

Commit

Permalink
Fix markdoc render code block in if tag
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy committed Jan 8, 2025
1 parent 0770810 commit f721911
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 75 deletions.
5 changes: 5 additions & 0 deletions .changeset/twelve-cobras-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/markdoc': patch
---

Fixes rendering code blocks within if tags
17 changes: 8 additions & 9 deletions packages/integrations/markdoc/components/Renderer.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
//! astro-head-inject
import type { Config } from '@markdoc/markdoc';
import type { Config, RenderableTreeNodes } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import { ComponentNode, createTreeNode } from './TreeNode.js';
Expand All @@ -12,13 +12,12 @@ type Props = {
const { stringifiedAst, config } = Astro.props as Props;
const ast = Markdoc.Ast.fromJSON(stringifiedAst);
const content = await Markdoc.transform(ast, config);
// The AST may be an array, and `transform` has overloads for arrays and non-array cases,
// However TypeScript seems to struggle to combine both overloads into a single signature.
// Also, `transform` returns a promise here but the types don't reflect that.
// @ts-expect-error
const content = (await Markdoc.transform(ast, config)) as RenderableTreeNodes;
const treeNode = await createTreeNode(content);
---

{
Array.isArray(content) ? (
content.map(async (c) => <ComponentNode treeNode={await createTreeNode(c)} />)
) : (
<ComponentNode treeNode={await createTreeNode(content)} />
)
}
<ComponentNode treeNode={treeNode} />
146 changes: 80 additions & 66 deletions packages/integrations/markdoc/components/TreeNode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RenderableTreeNode } from '@markdoc/markdoc';
import type { RenderableTreeNodes } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import type { AstroInstance } from 'astro';
import type { AstroInstance, SSRResult } from 'astro';
import type { HTMLString } from 'astro/runtime/server/index.js';
import {
createComponent,
Expand All @@ -15,6 +15,9 @@ import {
} from 'astro/runtime/server/index.js';

export type TreeNode =
// Markdoc `if` tag often returns an array of nodes in the AST, which gets translated
// here as an array of `TreeNode`s, which we'll render sequentially without a wrapper.
| TreeNode[]
| {
type: 'text';
content: string | HTMLString;
Expand All @@ -35,75 +38,86 @@ export type TreeNode =
children: TreeNode[];
};

export const ComponentNode = createComponent({
factory(result: any, { treeNode }: { treeNode: TreeNode }) {
if (treeNode.type === 'text') return render`${treeNode.content}`;

const slots = {
default: () =>
render`${treeNode.children.map((child) =>
renderComponent(result, 'ComponentNode', ComponentNode, { treeNode: child }),
)}`,
};
if (treeNode.type === 'component') {
let styles = '',
links = '',
scripts = '';
if (Array.isArray(treeNode.collectedStyles)) {
styles = treeNode.collectedStyles
.map((style: any) =>
renderUniqueStylesheet(result, {
type: 'inline',
content: style,
}),
)
.join('');
}
if (Array.isArray(treeNode.collectedLinks)) {
links = treeNode.collectedLinks
.map((link: any) => {
return renderUniqueStylesheet(result, {
type: 'external',
src: 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._metadata.propagators.add({
init() {
return headAndContent;
},
});

return headAndContent;
function renderTreeNodeToFactoryResult(result: SSRResult, treeNode: TreeNode) {
if (Array.isArray(treeNode)) {
return Promise.all(treeNode.map((node) => renderTreeNodeToFactoryResult(result, node)));
}

if (treeNode.type === 'text') return render`${treeNode.content}`;

const slots = {
default: () =>
render`${treeNode.children.map((child) =>
renderComponent(result, 'ComponentNode', ComponentNode, { treeNode: child }),
)}`,
};
if (treeNode.type === 'component') {
let styles = '',
links = '',
scripts = '';
if (Array.isArray(treeNode.collectedStyles)) {
styles = treeNode.collectedStyles
.map((style: any) =>
renderUniqueStylesheet(result, {
type: 'inline',
content: style,
}),
)
.join('');
}
if (Array.isArray(treeNode.collectedLinks)) {
links = treeNode.collectedLinks
.map((link: any) => {
return renderUniqueStylesheet(result, {
type: 'external',
src: link[0] === '/' ? link : '/' + link,
});
})
.join('');
}
return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots);
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.
// @ts-expect-error Astro only uses `init()` so specify it only (plus `_metadata` is internal)
result._metadata.propagators.add({
init() {
return headAndContent;
},
});

return headAndContent;
}
return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots);
}

export const ComponentNode = createComponent({
factory(result: SSRResult, { treeNode }: { treeNode: TreeNode | TreeNode[] }) {
return renderTreeNodeToFactoryResult(result, treeNode);
},
propagation: 'self',
});

export async function createTreeNode(node: RenderableTreeNode): Promise<TreeNode> {
if (isHTMLString(node)) {
export async function createTreeNode(node: RenderableTreeNodes): Promise<TreeNode> {
if (Array.isArray(node)) {
return Promise.all(node.map((child) => createTreeNode(child)));
} else if (isHTMLString(node)) {
return { type: 'text', content: node as HTMLString };
} else if (typeof node === 'string' || typeof node === 'number') {
return { type: 'text', content: String(node) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ And a code component for code blocks:
```js
const isRenderedWithShiki = true;
```

{% if equals("true", "true") %}
Inside truthy

```js
const isRenderedWithShikiInside = true;
```

{% /if %}
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ And a code component for code blocks:
```js
const isRenderedWithShiki = true;
```

{% if equals("true", "true") %}
Inside truthy

```js
const isRenderedWithShikiInside = true;
```

{% /if %}
5 changes: 5 additions & 0 deletions packages/integrations/markdoc/test/render-components.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ function renderComponentsChecks(html) {
const pre = document.querySelector('pre');
assert.notEqual(pre, null);
assert.equal(pre.className, 'astro-code github-dark');

// Renders 2nd Astro Code component inside if tag
const pre2 = document.querySelectorAll('pre')[1];
assert.notEqual(pre2, null);
assert.equal(pre2.className, 'astro-code github-dark');
}

/** @param {string} html */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ function renderComponentsChecks(html) {
const pre = document.querySelector('pre');
assert.notEqual(pre, null);
assert.equal(pre.className, 'astro-code github-dark');

// Renders 2nd Astro Code component inside if tag
const pre2 = document.querySelectorAll('pre')[1];
assert.notEqual(pre2, null);
assert.equal(pre2.className, 'astro-code github-dark');
}
21 changes: 21 additions & 0 deletions packages/integrations/markdoc/test/syntax-highlighting.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@ describe('Markdoc - syntax highlighting', () => {
assert.equal(pre.getAttribute('style').includes('word-wrap: break-word'), true);
}
});
it('transform within if tags', async () => {
const ast = Markdoc.parse(`
{% if equals("true", "true") %}
Inside truthy
\`\`\`js
const hello = "yes";
\`\`\`
{% /if %}`);
const content = await Markdoc.transform(ast, await getConfigExtendingShiki());
assert.equal(content.children.length, 1);
assert.equal(content.children[0].length, 2);
const pTag = content.children[0][0];
assert.equal(pTag.name, 'p');
const codeBlock = content.children[0][1];
assert.equal(isHTMLString(codeBlock), true);
const pre = parsePreTag(codeBlock);
assert.equal(pre.classList.contains('astro-code'), true);
assert.equal(pre.classList.contains('github-dark'), true);
});
});

describe('prism', () => {
Expand Down

0 comments on commit f721911

Please sign in to comment.