Skip to content

Commit 59f1e5c

Browse files
committed
feat: basic Astro renderer in with-markdoc
1 parent de15a72 commit 59f1e5c

18 files changed

+407
-0
lines changed

examples/with-markdoc/.gitignore

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# build output
2+
dist/
3+
# generated types
4+
.astro/
5+
6+
# dependencies
7+
node_modules/
8+
9+
# logs
10+
npm-debug.log*
11+
yarn-debug.log*
12+
yarn-error.log*
13+
pnpm-debug.log*
14+
15+
16+
# environment variables
17+
.env
18+
.env.production
19+
20+
# macOS-specific files
21+
.DS_Store
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"recommendations": ["astro-build.astro-vscode"],
3+
"unwantedRecommendations": []
4+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"command": "./node_modules/.bin/astro dev",
6+
"name": "Development server",
7+
"request": "launch",
8+
"type": "node-terminal"
9+
}
10+
]
11+
}

examples/with-markdoc/README.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Astro Starter Kit: Minimal
2+
3+
```
4+
npm create astro@latest -- --template minimal
5+
```
6+
7+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
8+
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
9+
10+
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
11+
12+
## 🚀 Project Structure
13+
14+
Inside of your Astro project, you'll see the following folders and files:
15+
16+
```
17+
/
18+
├── public/
19+
├── src/
20+
│ └── pages/
21+
│ └── index.astro
22+
└── package.json
23+
```
24+
25+
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
26+
27+
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
28+
29+
Any static assets, like images, can be placed in the `public/` directory.
30+
31+
## 🧞 Commands
32+
33+
All commands are run from the root of the project, from a terminal:
34+
35+
| Command | Action |
36+
| :--------------------- | :----------------------------------------------- |
37+
| `npm install` | Installs dependencies |
38+
| `npm run dev` | Starts local dev server at `localhost:3000` |
39+
| `npm run build` | Build your production site to `./dist/` |
40+
| `npm run preview` | Preview your build locally, before deploying |
41+
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
42+
| `npm run astro --help` | Get help using the Astro CLI |
43+
44+
## 👀 Want to learn more?
45+
46+
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from 'astro/config';
2+
import markdoc from '@astrojs/markdoc';
3+
4+
// https://astro.build/config
5+
export default defineConfig({
6+
integrations: [markdoc()],
7+
});

examples/with-markdoc/package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "@example/with-markdoc",
3+
"type": "module",
4+
"version": "0.0.1",
5+
"private": true,
6+
"scripts": {
7+
"dev": "astro dev",
8+
"start": "astro dev",
9+
"build": "astro build",
10+
"preview": "astro preview",
11+
"astro": "astro"
12+
},
13+
"dependencies": {
14+
"@astrojs/markdoc": "^0.0.1",
15+
"astro": "^2.0.6",
16+
"html-escaper": "^3.0.3"
17+
},
18+
"devDependencies": {
19+
"@markdoc/markdoc": "^0.2.2"
20+
}
21+
}
+13
Loading
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"infiniteLoopProtection": true,
3+
"hardReloadOnChange": false,
4+
"view": "browser",
5+
"template": "node",
6+
"container": {
7+
"port": 3000,
8+
"startScript": "start",
9+
"node": "14"
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<p><slot /></p>
2+
3+
<style>
4+
p {
5+
color: red;
6+
}
7+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Hey there
2+
3+
This is a test file?
4+
5+
{% table %}
6+
* Heading 1
7+
* Heading 2
8+
---
9+
* Row 1 Cell 1
10+
* Row 1 Cell 2
11+
---
12+
* Row 2 Cell 1
13+
* Row 2 cell 2
14+
{% /table %}
15+
16+
{% if $shouldMarquee %}
17+
{% mq direction="right" %}
18+
Testing!
19+
{% /mq %}
20+
{% /if %}
21+
22+
{% link href=$href %}Link{% /link %}
23+
24+
Some `inline code` should help
25+
26+
```js
27+
const testing = true;
28+
function further() {
29+
console.log('still highlighted!')
30+
}
31+
```

examples/with-markdoc/src/env.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/// <reference path="../.astro/types.d.ts" />
2+
/// <reference types="astro/client" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
import { body } from '../components/test.mdoc';
3+
import { Markdoc } from '@astrojs/markdoc';
4+
import RenderMarkdoc from '../renderer/RenderMarkdoc.astro';
5+
import RedP from '../components/RedP.astro';
6+
import { Code } from 'astro/components';
7+
import { Tag } from '@markdoc/markdoc';
8+
import { ComponentRenderer } from '../renderer/astroNode';
9+
10+
const parsed = Markdoc.parse(body);
11+
const content = Markdoc.transform(parsed, {
12+
variables: {
13+
shouldMarquee: true,
14+
href: 'https://astro.build',
15+
},
16+
tags: {
17+
mq: {
18+
render: 'marquee',
19+
attributes: {
20+
direction: {
21+
type: String,
22+
default: 'left',
23+
matches: ['left', 'right', 'up', 'down'],
24+
errorLevel: 'critical',
25+
},
26+
},
27+
},
28+
link: {
29+
render: 'a',
30+
attributes: {
31+
href: {
32+
type: String,
33+
required: true,
34+
},
35+
},
36+
},
37+
},
38+
});
39+
40+
const code: ComponentRenderer = {
41+
component: Code,
42+
props({ attributes, getTreeNode }) {
43+
return {
44+
...attributes,
45+
lang: attributes.lang ?? attributes['data-language'],
46+
code: attributes.code ?? Markdoc.renderers.html(getTreeNode().children),
47+
};
48+
},
49+
};
50+
---
51+
52+
<html lang="en">
53+
<head>
54+
<meta charset="utf-8" />
55+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
56+
<meta name="viewport" content="width=device-width" />
57+
<meta name="generator" content={Astro.generator} />
58+
<title>Astro</title>
59+
</head>
60+
<body>
61+
<h1>Astro</h1>
62+
<article>
63+
<RenderMarkdoc
64+
content={content}
65+
components={{
66+
p: RedP,
67+
code,
68+
pre: code,
69+
}}
70+
/>
71+
</article>
72+
</body>
73+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
import type { RenderableTreeNode } from '@markdoc/markdoc';
3+
import { ComponentRenderer, createAstroNode } from './astroNode';
4+
import RenderNode from './RenderNode.astro';
5+
6+
type Props = {
7+
content: RenderableTreeNode;
8+
components: Record<string, ComponentRenderer>;
9+
};
10+
11+
const { content, components } = Astro.props as Props;
12+
---
13+
14+
<RenderNode node={createAstroNode(content, components)} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
import type { AstroNode } from './astroNode';
3+
4+
type Props = {
5+
node: AstroNode;
6+
};
7+
8+
const Node = (Astro.props as Props).node;
9+
---
10+
11+
{
12+
typeof Node === 'string' ? (
13+
<Fragment set:text={Node} />
14+
) : 'component' in Node ? (
15+
<Node.component {...Node.props}>
16+
{Node.children.map((child) => (
17+
<Astro.self node={child} />
18+
))}
19+
</Node.component>
20+
) : (
21+
<Fragment>
22+
<Fragment set:html={`<${Node.tag}>`} />
23+
{Node.children.map((child) => (
24+
<Astro.self node={child} />
25+
))}
26+
<Fragment set:html={`</${Node.tag}>`} />
27+
</Fragment>
28+
)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { RenderableTreeNode, Tag, renderers, NodeType } from '@markdoc/markdoc';
2+
import { escape } from 'html-escaper';
3+
4+
// TODO: expose `AstroComponentFactory` type from core
5+
type AstroComponentFactory = (props: Record<string, any>) => any & {
6+
isAstroComponentFactory: true;
7+
};
8+
9+
export type ComponentRenderer =
10+
| AstroComponentFactory
11+
| {
12+
component: AstroComponentFactory;
13+
props?(params: { attributes: Record<string, any>; getTreeNode(): Tag }): Record<string, any>;
14+
};
15+
16+
export type AstroNode =
17+
| string
18+
| {
19+
component: AstroComponentFactory;
20+
props: Record<string, any>;
21+
children: AstroNode[];
22+
}
23+
| {
24+
tag: string;
25+
attributes: Record<string, any>;
26+
children: AstroNode[];
27+
};
28+
29+
export function createAstroNode(
30+
node: RenderableTreeNode,
31+
components: Record<string, ComponentRenderer> = {}
32+
): AstroNode {
33+
if (typeof node === 'string' || typeof node === 'number') {
34+
return escape(String(node));
35+
} else if (node === null || typeof node !== 'object' || !Tag.isTag(node)) {
36+
return '';
37+
}
38+
39+
if (Object.hasOwn(components, node.name)) {
40+
const componentRenderer = components[node.name];
41+
const component =
42+
'Component' in componentRenderer ? componentRenderer.component : componentRenderer;
43+
const props =
44+
'props' in componentRenderer
45+
? componentRenderer.props({
46+
attributes: node.attributes,
47+
getTreeNode() {
48+
return node;
49+
},
50+
})
51+
: node.attributes;
52+
53+
const children = node.children.map((child) => createAstroNode(child, components));
54+
55+
return {
56+
component,
57+
props,
58+
children,
59+
};
60+
} else {
61+
return {
62+
tag: node.name,
63+
attributes: node.attributes,
64+
children: node.children.map((child) => createAstroNode(child, components)),
65+
};
66+
}
67+
}

examples/with-markdoc/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "astro/tsconfigs/base"
3+
}

packages/integrations/markdoc/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"test:match": "mocha --timeout 20000 -g"
3131
},
3232
"dependencies": {
33+
"@markdoc/markdoc": "^0.2.2"
3334
},
3435
"devDependencies": {
3536
"@types/chai": "^4.3.1",

0 commit comments

Comments
 (0)