diff --git a/examples/with-markdoc/.gitignore b/examples/with-markdoc/.gitignore
new file mode 100644
index 000000000..6240da8b1
--- /dev/null
+++ b/examples/with-markdoc/.gitignore
@@ -0,0 +1,21 @@
+# build output
+dist/
+# generated types
+.astro/
+
+# dependencies
+node_modules/
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+
+# environment variables
+.env
+.env.production
+
+# macOS-specific files
+.DS_Store
diff --git a/examples/with-markdoc/.vscode/extensions.json b/examples/with-markdoc/.vscode/extensions.json
new file mode 100644
index 000000000..22a15055d
--- /dev/null
+++ b/examples/with-markdoc/.vscode/extensions.json
@@ -0,0 +1,4 @@
+{
+ "recommendations": ["astro-build.astro-vscode"],
+ "unwantedRecommendations": []
+}
diff --git a/examples/with-markdoc/.vscode/launch.json b/examples/with-markdoc/.vscode/launch.json
new file mode 100644
index 000000000..d64220976
--- /dev/null
+++ b/examples/with-markdoc/.vscode/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "command": "./node_modules/.bin/astro dev",
+ "name": "Development server",
+ "request": "launch",
+ "type": "node-terminal"
+ }
+ ]
+}
diff --git a/examples/with-markdoc/README.md b/examples/with-markdoc/README.md
new file mode 100644
index 000000000..e14d3255b
--- /dev/null
+++ b/examples/with-markdoc/README.md
@@ -0,0 +1,46 @@
+# Astro Starter Kit: Minimal
+
+```
+npm create astro@latest -- --template minimal
+```
+
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
+[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
+
+> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
+
+## 🚀 Project Structure
+
+Inside of your Astro project, you'll see the following folders and files:
+
+```
+/
+├── public/
+├── src/
+│ └── pages/
+│ └── index.astro
+└── package.json
+```
+
+Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
+
+There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
+
+Any static assets, like images, can be placed in the `public/` directory.
+
+## 🧞 Commands
+
+All commands are run from the root of the project, from a terminal:
+
+| Command | Action |
+| :--------------------- | :----------------------------------------------- |
+| `npm install` | Installs dependencies |
+| `npm run dev` | Starts local dev server at `localhost:3000` |
+| `npm run build` | Build your production site to `./dist/` |
+| `npm run preview` | Preview your build locally, before deploying |
+| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
+| `npm run astro --help` | Get help using the Astro CLI |
+
+## 👀 Want to learn more?
+
+Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
diff --git a/examples/with-markdoc/astro.config.mjs b/examples/with-markdoc/astro.config.mjs
new file mode 100644
index 000000000..29d846359
--- /dev/null
+++ b/examples/with-markdoc/astro.config.mjs
@@ -0,0 +1,7 @@
+import { defineConfig } from 'astro/config';
+import markdoc from '@astrojs/markdoc';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [markdoc()],
+});
diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json
new file mode 100644
index 000000000..e1daefcf6
--- /dev/null
+++ b/examples/with-markdoc/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@example/with-markdoc",
+ "type": "module",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev",
+ "start": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@astrojs/markdoc": "^0.0.1",
+ "astro": "^2.0.6",
+ "html-escaper": "^3.0.3"
+ },
+ "devDependencies": {
+ "@markdoc/markdoc": "^0.2.2"
+ }
+}
diff --git a/examples/with-markdoc/public/favicon.svg b/examples/with-markdoc/public/favicon.svg
new file mode 100644
index 000000000..0f3906297
--- /dev/null
+++ b/examples/with-markdoc/public/favicon.svg
@@ -0,0 +1,13 @@
+
diff --git a/examples/with-markdoc/sandbox.config.json b/examples/with-markdoc/sandbox.config.json
new file mode 100644
index 000000000..9178af77d
--- /dev/null
+++ b/examples/with-markdoc/sandbox.config.json
@@ -0,0 +1,11 @@
+{
+ "infiniteLoopProtection": true,
+ "hardReloadOnChange": false,
+ "view": "browser",
+ "template": "node",
+ "container": {
+ "port": 3000,
+ "startScript": "start",
+ "node": "14"
+ }
+}
diff --git a/examples/with-markdoc/src/components/RedP.astro b/examples/with-markdoc/src/components/RedP.astro
new file mode 100644
index 000000000..1b4252688
--- /dev/null
+++ b/examples/with-markdoc/src/components/RedP.astro
@@ -0,0 +1,7 @@
+
+
+
diff --git a/examples/with-markdoc/src/components/test.mdoc b/examples/with-markdoc/src/components/test.mdoc
new file mode 100644
index 000000000..33ab897d2
--- /dev/null
+++ b/examples/with-markdoc/src/components/test.mdoc
@@ -0,0 +1,31 @@
+# Hey there
+
+This is a test file?
+
+{% table %}
+* Heading 1
+* Heading 2
+---
+* Row 1 Cell 1
+* Row 1 Cell 2
+---
+* Row 2 Cell 1
+* Row 2 cell 2
+{% /table %}
+
+{% if $shouldMarquee %}
+{% mq direction="right" %}
+Testing!
+{% /mq %}
+{% /if %}
+
+{% link href=$href %}Link{% /link %}
+
+Some `inline code` should help
+
+```js
+const testing = true;
+function further() {
+ console.log('still highlighted!')
+}
+```
diff --git a/examples/with-markdoc/src/env.d.ts b/examples/with-markdoc/src/env.d.ts
new file mode 100644
index 000000000..acef35f17
--- /dev/null
+++ b/examples/with-markdoc/src/env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/examples/with-markdoc/src/pages/index.astro b/examples/with-markdoc/src/pages/index.astro
new file mode 100644
index 000000000..59a5ef7bc
--- /dev/null
+++ b/examples/with-markdoc/src/pages/index.astro
@@ -0,0 +1,73 @@
+---
+import { body } from '../components/test.mdoc';
+import { Markdoc } from '@astrojs/markdoc';
+import RenderMarkdoc from '../renderer/RenderMarkdoc.astro';
+import RedP from '../components/RedP.astro';
+import { Code } from 'astro/components';
+import { Tag } from '@markdoc/markdoc';
+import { ComponentRenderer } from '../renderer/astroNode';
+
+const parsed = Markdoc.parse(body);
+const content = Markdoc.transform(parsed, {
+ variables: {
+ shouldMarquee: true,
+ href: 'https://astro.build',
+ },
+ tags: {
+ mq: {
+ render: 'marquee',
+ attributes: {
+ direction: {
+ type: String,
+ default: 'left',
+ matches: ['left', 'right', 'up', 'down'],
+ errorLevel: 'critical',
+ },
+ },
+ },
+ link: {
+ render: 'a',
+ attributes: {
+ href: {
+ type: String,
+ required: true,
+ },
+ },
+ },
+ },
+});
+
+const code: ComponentRenderer = {
+ component: Code,
+ props({ attributes, getTreeNode }) {
+ return {
+ ...attributes,
+ lang: attributes.lang ?? attributes['data-language'],
+ code: attributes.code ?? Markdoc.renderers.html(getTreeNode().children),
+ };
+ },
+};
+---
+
+
+
+
+
+
+
+ Astro
+
+
+ Astro
+
+
+
+
+
diff --git a/examples/with-markdoc/src/renderer/RenderMarkdoc.astro b/examples/with-markdoc/src/renderer/RenderMarkdoc.astro
new file mode 100644
index 000000000..00c602c7d
--- /dev/null
+++ b/examples/with-markdoc/src/renderer/RenderMarkdoc.astro
@@ -0,0 +1,14 @@
+---
+import type { RenderableTreeNode } from '@markdoc/markdoc';
+import { ComponentRenderer, createAstroNode } from './astroNode';
+import RenderNode from './RenderNode.astro';
+
+type Props = {
+ content: RenderableTreeNode;
+ components: Record;
+};
+
+const { content, components } = Astro.props as Props;
+---
+
+
diff --git a/examples/with-markdoc/src/renderer/RenderNode.astro b/examples/with-markdoc/src/renderer/RenderNode.astro
new file mode 100644
index 000000000..c5f2eb07b
--- /dev/null
+++ b/examples/with-markdoc/src/renderer/RenderNode.astro
@@ -0,0 +1,29 @@
+---
+import type { AstroNode } from './astroNode';
+
+type Props = {
+ node: AstroNode;
+};
+
+const Node = (Astro.props as Props).node;
+---
+
+{
+ typeof Node === 'string' ? (
+
+ ) : 'component' in Node ? (
+
+ {Node.children.map((child) => (
+
+ ))}
+
+ ) : (
+
+ `} />
+ {Node.children.map((child) => (
+
+ ))}
+ `} />
+
+ )
+}
diff --git a/examples/with-markdoc/src/renderer/astroNode.ts b/examples/with-markdoc/src/renderer/astroNode.ts
new file mode 100644
index 000000000..4d4acb817
--- /dev/null
+++ b/examples/with-markdoc/src/renderer/astroNode.ts
@@ -0,0 +1,67 @@
+import { RenderableTreeNode, Tag, renderers, NodeType } from '@markdoc/markdoc';
+import { escape } from 'html-escaper';
+
+// TODO: expose `AstroComponentFactory` type from core
+type AstroComponentFactory = (props: Record) => any & {
+ isAstroComponentFactory: true;
+};
+
+export type ComponentRenderer =
+ | AstroComponentFactory
+ | {
+ component: AstroComponentFactory;
+ props?(params: { attributes: Record; getTreeNode(): Tag }): Record;
+ };
+
+export type AstroNode =
+ | string
+ | {
+ component: AstroComponentFactory;
+ props: Record;
+ children: AstroNode[];
+ }
+ | {
+ tag: string;
+ attributes: Record;
+ children: AstroNode[];
+ };
+
+export function createAstroNode(
+ node: RenderableTreeNode,
+ components: Record = {}
+): AstroNode {
+ if (typeof node === 'string' || typeof node === 'number') {
+ return escape(String(node));
+ } else if (node === null || typeof node !== 'object' || !Tag.isTag(node)) {
+ return '';
+ }
+
+ if (Object.hasOwn(components, node.name)) {
+ const componentRenderer = components[node.name];
+ const component =
+ 'Component' in componentRenderer ? componentRenderer.component : componentRenderer;
+ const props =
+ 'props' in componentRenderer
+ ? componentRenderer.props({
+ attributes: node.attributes,
+ getTreeNode() {
+ return node;
+ },
+ })
+ : node.attributes;
+
+ const children = node.children.map((child) => createAstroNode(child, components));
+
+ return {
+ component,
+ props,
+ children,
+ };
+ } else {
+ return {
+ tag: node.name,
+ attributes: node.attributes,
+ children: node.children.map((child) => createAstroNode(child, components)),
+ };
+ }
+}
diff --git a/examples/with-markdoc/tsconfig.json b/examples/with-markdoc/tsconfig.json
new file mode 100644
index 000000000..d78f81ec4
--- /dev/null
+++ b/examples/with-markdoc/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "astro/tsconfigs/base"
+}
diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json
index fafebee3a..70c475f53 100644
--- a/packages/integrations/markdoc/package.json
+++ b/packages/integrations/markdoc/package.json
@@ -30,6 +30,7 @@
"test:match": "mocha --timeout 20000 -g"
},
"dependencies": {
+ "@markdoc/markdoc": "^0.2.2"
},
"devDependencies": {
"@types/chai": "^4.3.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f5a8f19ba..309656e44 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -285,6 +285,19 @@ importers:
unocss: 0.15.6
vite-imagetools: 4.0.18
+ examples/with-markdoc:
+ specifiers:
+ '@astrojs/markdoc': ^0.0.1
+ '@markdoc/markdoc': ^0.2.2
+ astro: ^2.0.6
+ html-escaper: ^3.0.3
+ dependencies:
+ '@astrojs/markdoc': link:../../packages/integrations/markdoc
+ astro: link:../../packages/astro
+ html-escaper: 3.0.3
+ devDependencies:
+ '@markdoc/markdoc': 0.2.2
+
examples/with-markdown-plugins:
specifiers:
'@astrojs/markdown-remark': ^2.0.1
@@ -2847,6 +2860,7 @@ importers:
packages/integrations/markdoc:
specifiers:
+ '@markdoc/markdoc': ^0.2.2
'@types/chai': ^4.3.1
'@types/mocha': ^9.1.1
astro: workspace:*
@@ -2855,6 +2869,8 @@ importers:
linkedom: ^0.14.12
mocha: ^9.2.2
vite: ^4.0.3
+ dependencies:
+ '@markdoc/markdoc': 0.2.2
devDependencies:
'@types/chai': 4.3.4
'@types/mocha': 9.1.1
@@ -6385,6 +6401,20 @@ packages:
- supports-color
dev: false
+ /@markdoc/markdoc/0.2.2:
+ resolution: {integrity: sha512-0TiD9jmA5h5znN4lxo7HECAu3WieU5g5vUsfByeucrdR/x88hEilpt16EydFyJwJddQ/3w5HQgW7Ovy62r4cyw==}
+ engines: {node: '>=14.7.0'}
+ peerDependencies:
+ '@types/react': '*'
+ react: '*'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ react:
+ optional: true
+ optionalDependencies:
+ '@types/markdown-it': 12.2.3
+
/@mdx-js/mdx/2.2.1:
resolution: {integrity: sha512-hZ3ex7exYLJn6FfReq8yTvA6TE53uW9UHJQM9IlSauOuS55J9y8RtA7W+dzp6Yrzr00/U1sd7q+Wf61q6SfiTQ==}
dependencies:
@@ -7189,11 +7219,27 @@ packages:
/@types/json5/0.0.30:
resolution: {integrity: sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==}
+ /@types/linkify-it/3.0.2:
+ resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==}
+ optional: true
+
+ /@types/markdown-it/12.2.3:
+ resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==}
+ requiresBuild: true
+ dependencies:
+ '@types/linkify-it': 3.0.2
+ '@types/mdurl': 1.0.2
+ optional: true
+
/@types/mdast/3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
dependencies:
'@types/unist': 2.0.6
+ /@types/mdurl/1.0.2:
+ resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
+ optional: true
+
/@types/mdx/2.0.3:
resolution: {integrity: sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==}
dev: false