diff --git a/.changeset/metal-cameras-bow.md b/.changeset/metal-cameras-bow.md
new file mode 100644
index 000000000..2275c4804
--- /dev/null
+++ b/.changeset/metal-cameras-bow.md
@@ -0,0 +1,42 @@
+---
+'@astrojs/markdoc': minor
+'astro': patch
+---
+
+Simplify Markdoc configuration with a new `markdoc.config.mjs` file. This lets you import Astro components directly to render as Markdoc tags and nodes, without the need for the previous `components` property. This new configuration also unlocks passing variables to your Markdoc from the `Content` component ([see the new docs](https://docs.astro.build/en/guides/integrations-guide/markdoc/#pass-markdoc-variables)).
+
+## Migration
+
+Move any existing Markdoc config from your `astro.config` to a new `markdoc.config.mjs` file at the root of your project. This should be applied as a default export, with the optional `defineMarkdocConfig()` helper for autocomplete in your editor.
+
+This example configures an `aside` Markdoc tag. Note that components should be imported and applied to the `render` attribute _directly,_ instead of passing the name as a string:
+
+```js
+// markdoc.config.mjs
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
+import Aside from './src/components/Aside.astro';
+
+export default defineMarkdocConfig({
+ tags: {
+ aside: {
+ render: Aside,
+ }
+ }
+});
+```
+
+You should also remove the `components` prop from your `Content` components. Since components are imported into your config directly, this is no longer needed.
+
+```diff
+---
+- import Aside from '../components/Aside.astro';
+import { getEntryBySlug } from 'astro:content';
+
+const entry = await getEntryBySlug('docs', 'why-markdoc');
+const { Content } = await entry.render();
+---
+
+
+```
diff --git a/examples/with-markdoc/README.md b/examples/with-markdoc/README.md
index 62f7cbfc8..b5adbf27b 100644
--- a/examples/with-markdoc/README.md
+++ b/examples/with-markdoc/README.md
@@ -23,23 +23,20 @@ Inside of your Astro project, you'll see the following folders and files:
└── docs/
│ └── intro.mdoc
| └── config.ts
-│ └── components/
-| ├── Aside.astro
-│ └── DocsContent.astro
-│ └── layouts/
-│ └── Layout.astro
-│ └── pages/
-│ └── index.astro
+│ └── components/Aside.astro
+│ └── layouts/Layout.astro
+│ └── pages/index.astro
| └── env.d.ts
├── astro.config.mjs
+├── markdoc.config.mjs
├── README.md
├── package.json
└── tsconfig.json
```
-Markdoc (`.mdoc`) files can be used in content collections to author your Markdown content alongside Astro and server-rendered UI framework components (React, Vue, Svelte, and more). See `src/content/docs/` for an example file.
+Markdoc (`.mdoc`) files can be used in content collections. See `src/content/docs/` for an example file.
-You can also apply Astro components and server-rendered UI components (React, Vue, Svelte, etc) to your Markdoc files. See `src/content/DocsContent.astro` for an example.
+You can also render Astro components from your Markdoc files using [tags](https://markdoc.dev/docs/tags). See the `markdoc.config.mjs` file for an example configuration.
## 🧞 Commands
diff --git a/examples/with-markdoc/astro.config.mjs b/examples/with-markdoc/astro.config.mjs
index d88ed2098..29d846359 100644
--- a/examples/with-markdoc/astro.config.mjs
+++ b/examples/with-markdoc/astro.config.mjs
@@ -3,17 +3,5 @@ import markdoc from '@astrojs/markdoc';
// https://astro.build/config
export default defineConfig({
- integrations: [
- markdoc({
- tags: {
- aside: {
- render: 'Aside',
- attributes: {
- type: { type: String },
- title: { type: String },
- },
- },
- },
- }),
- ],
+ integrations: [markdoc()],
});
diff --git a/examples/with-markdoc/markdoc.config.mjs b/examples/with-markdoc/markdoc.config.mjs
new file mode 100644
index 000000000..0ae63d4ee
--- /dev/null
+++ b/examples/with-markdoc/markdoc.config.mjs
@@ -0,0 +1,14 @@
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
+import Aside from './src/components/Aside.astro';
+
+export default defineMarkdocConfig({
+ tags: {
+ aside: {
+ render: Aside,
+ attributes: {
+ type: { type: String },
+ title: { type: String },
+ },
+ },
+ },
+});
diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json
index 9ca562fa3..f31392840 100644
--- a/examples/with-markdoc/package.json
+++ b/examples/with-markdoc/package.json
@@ -12,6 +12,7 @@
},
"dependencies": {
"@astrojs/markdoc": "^0.0.5",
- "astro": "^2.1.7"
+ "astro": "^2.1.7",
+ "kleur": "^4.1.5"
}
}
diff --git a/examples/with-markdoc/src/components/DocsContent.astro b/examples/with-markdoc/src/components/DocsContent.astro
deleted file mode 100644
index 162c1fc6d..000000000
--- a/examples/with-markdoc/src/components/DocsContent.astro
+++ /dev/null
@@ -1,32 +0,0 @@
----
-import Aside from './Aside.astro';
-import type { CollectionEntry } from 'astro:content';
-
-type Props = {
- entry: CollectionEntry<'docs'>;
-};
-
-const { entry } = Astro.props;
-const { Content } = await entry.render();
----
-
-
-
-
diff --git a/examples/with-markdoc/src/pages/index.astro b/examples/with-markdoc/src/pages/index.astro
index 01412cce1..7efcbeda8 100644
--- a/examples/with-markdoc/src/pages/index.astro
+++ b/examples/with-markdoc/src/pages/index.astro
@@ -1,18 +1,25 @@
---
import { getEntryBySlug } from 'astro:content';
-import DocsContent from '../components/DocsContent.astro';
import Layout from '../layouts/Layout.astro';
const intro = await getEntryBySlug('docs', 'intro');
+const { Content } = await intro.render();
---
{intro.data.title}
-
-
-
-
-
+
+
+
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index a496a5aa0..e1d817607 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1056,6 +1056,7 @@ export interface ContentEntryType {
getRenderModule?(
this: rollup.PluginContext,
params: {
+ viteId: string;
entry: ContentEntryModule;
}
): rollup.LoadResult | Promise;
diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts
index 7ad71b31e..4437f4fa0 100644
--- a/packages/astro/src/content/vite-plugin-content-imports.ts
+++ b/packages/astro/src/content/vite-plugin-content-imports.ts
@@ -139,7 +139,7 @@ export const _internal = {
});
}
- return contentRenderer.bind(this)({ entry });
+ return contentRenderer.bind(this)({ entry, viteId });
},
});
}
diff --git a/packages/integrations/markdoc/README.md b/packages/integrations/markdoc/README.md
index 76291e763..85a657ea4 100644
--- a/packages/integrations/markdoc/README.md
+++ b/packages/integrations/markdoc/README.md
@@ -99,53 +99,46 @@ const { Content } = await entry.render();
### Using components
-You can add Astro and UI framework components (React, Vue, Svelte, etc.) to your Markdoc using both [Markdoc tags][markdoc-tags] and HTML element [nodes][markdoc-nodes].
+You can add Astro components to your Markdoc using both [Markdoc tags][markdoc-tags] and HTML element [nodes][markdoc-nodes].
#### Render Markdoc tags as Astro components
-You may configure [Markdoc tags][markdoc-tags] that map to components. You can configure a new tag from your `astro.config` using the `tags` attribute.
+You may configure [Markdoc tags][markdoc-tags] that map to components. You can configure a new tag by creating a `markdoc.config.mjs|ts` file at the root of your project and configuring the `tag` attribute.
+This example renders an `Aside` component, and allows a `type` prop to be passed as a string:
```js
-// astro.config.mjs
-import { defineConfig } from 'astro/config';
-import markdoc from '@astrojs/markdoc';
+// markdoc.config.mjs
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
+import Aside from './src/components/Aside.astro';
-// https://astro.build/config
-export default defineConfig({
- integrations: [
- markdoc({
- tags: {
- aside: {
- render: 'Aside',
- attributes: {
- // Component props as attribute definitions
- // See Markdoc's documentation on defining attributes
- // https://markdoc.dev/docs/attributes#defining-attributes
- type: { type: String },
- }
- },
- },
- }),
- ],
-});
+export default defineMarkdocConfig({
+ tags: {
+ aside: {
+ render: Aside,
+ attributes: {
+ // Markdoc requires type defs for each attribute.
+ // These should mirror the `Props` type of the component
+ // you are rendering.
+ // See Markdoc's documentation on defining attributes
+ // https://markdoc.dev/docs/attributes#defining-attributes
+ type: { type: String },
+ }
+ },
+ },
+})
```
-Then, you can wire this render name (`'Aside'`) to a component from the `components` prop via the `` component. Note the object key name (`Aside` in this case) should match the render name:
+This component can now be used in your Markdoc files with the `{% aside %}` tag. Children will be passed to your component's default slot:
+```md
+# Welcome to Markdoc 👋
-```astro
----
-import { getEntryBySlug } from 'astro:content';
-import Aside from '../components/Aside.astro';
+{% aside type="tip" %}
-const entry = await getEntryBySlug('docs', 'why-markdoc');
-const { Content } = await entry.render();
----
+Use tags like this fancy "aside" to add some *flair* to your docs.
-
+{% /aside %}
```
#### Render Markdoc nodes / HTML elements as Astro components
@@ -153,46 +146,22 @@ const { Content } = await entry.render();
You may also want to map standard HTML elements like headings and paragraphs to components. For this, you can configure a custom [Markdoc node][markdoc-nodes]. This example overrides Markdoc's `heading` node to render a `Heading` component, passing the built-in `level` attribute as a prop:
```js
-// astro.config.mjs
-import { defineConfig } from 'astro/config';
-import markdoc from '@astrojs/markdoc';
+// markdoc.config.mjs
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
+import Heading from './src/components/Heading.astro';
-// https://astro.build/config
-export default defineConfig({
- integrations: [
- markdoc({
- nodes: {
- heading: {
- render: 'Heading',
- // Markdoc requires type defs for each attribute.
- // These should mirror the `Props` type of the component
- // you are rendering.
- // See Markdoc's documentation on defining attributes
- // https://markdoc.dev/docs/attributes#defining-attributes
- attributes: {
- level: { type: String },
- }
- },
- },
- }),
- ],
-});
-```
-
-Now, you can map the string passed to render (`'Heading'` in this example) to a component import. This is configured from the `` component used to render your Markdoc using the `components` prop:
-
-```astro
----
-import { getEntryBySlug } from 'astro:content';
-import Heading from '../components/Heading.astro';
-
-const entry = await getEntryBySlug('docs', 'why-markdoc');
-const { Content } = await entry.render();
----
-
-
+export default defineMarkdocConfig({
+ nodes: {
+ heading: {
+ render: Heading,
+ attributes: {
+ // Pass the attributes from Markdoc's default heading node
+ // as component props.
+ level: { type: String },
+ }
+ },
+ },
+})
```
Now, all Markdown headings will render with the `Heading.astro` component. This example uses a level 3 heading, automatically passing `level: 3` as the component prop:
@@ -215,26 +184,26 @@ This example wraps a `Aside.tsx` component with a `ClientAside.astro` wrapper:
import Aside from './Aside';
---
-
+
```
-This component [can be applied via the `components` prop](#render-markdoc-nodes--html-elements-as-astro-components):
+This component can be passed to the `render` prop for any [tag][markdoc-tags] or [node][markdoc-nodes] in your config:
-```astro
----
-// src/pages/why-markdoc.astro
-import { getEntryBySlug } from 'astro:content';
-import ClientAside from '../components/ClientAside.astro';
+```js
+// markdoc.config.mjs
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
+import Aside from './src/components/Aside.astro';
-const entry = await getEntryBySlug('docs', 'why-markdoc');
-const { Content } = await entry.render();
----
-
-
+export default defineMarkdocConfig({
+ tags: {
+ aside: {
+ render: Aside,
+ attributes: {
+ type: { type: String },
+ }
+ },
+ },
+})
```
### Access frontmatter and content collection information from your templates
@@ -253,35 +222,29 @@ The `$entry` object matches [the `CollectionEntry` type](https://docs.astro.buil
### Markdoc config
-The Markdoc integration accepts [all Markdoc configuration options](https://markdoc.dev/docs/config), including [tags](https://markdoc.dev/docs/tags) and [functions](https://markdoc.dev/docs/functions).
+The `markdoc.config.mjs|ts` file accepts [all Markdoc configuration options](https://markdoc.dev/docs/config), including [tags](https://markdoc.dev/docs/tags) and [functions](https://markdoc.dev/docs/functions).
-You can pass these options from the `markdoc()` integration in your `astro.config`. This example adds a global `getCountryEmoji` function:
+You can pass these options from the default export in your `markdoc.config.mjs|ts` file:
```js
-// astro.config.mjs
-import { defineConfig } from 'astro/config';
-import markdoc from '@astrojs/markdoc';
+// markdoc.config.mjs
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
-// https://astro.build/config
-export default defineConfig({
- integrations: [
- markdoc({
- functions: {
- getCountryEmoji: {
- transform(parameters) {
- const [country] = Object.values(parameters);
- const countryToEmojiMap = {
- japan: '🇯🇵',
- spain: '🇪🇸',
- france: '🇫🇷',
- }
- return countryToEmojiMap[country] ?? '🏳'
- },
- },
+export default defineMarkdocConfig({
+ functions: {
+ getCountryEmoji: {
+ transform(parameters) {
+ const [country] = Object.values(parameters);
+ const countryToEmojiMap = {
+ japan: '🇯🇵',
+ spain: '🇪🇸',
+ france: '🇫🇷',
+ }
+ return countryToEmojiMap[country] ?? '🏳'
},
- }),
- ],
-});
+ },
+ },
+})
```
Now, you can call this function from any Markdoc content entry:
@@ -290,47 +253,46 @@ Now, you can call this function from any Markdoc content entry:
¡Hola {% getCountryEmoji("spain") %}!
```
-:::note
-These options will be applied during [the Markdoc "transform" phase](https://markdoc.dev/docs/render#transform). This is run **at build time** (rather than server request time) both for static and SSR Astro projects. If you need to define configuration at runtime (ex. SSR variables), [see the next section](#define-markdoc-configuration-at-runtime).
-:::
-
📚 [See the Markdoc documentation](https://markdoc.dev/docs/functions#creating-a-custom-function) for more on using variables or functions in your content.
-### Define Markdoc configuration at runtime
+### Pass Markdoc variables
-You may need to define Markdoc configuration at the component level, rather than the `astro.config.mjs` level. This is useful when mapping props and SSR parameters to [Markdoc variables](https://markdoc.dev/docs/variables).
+You may need to pass [variables][markdoc-variables] to your content. This is useful when passing SSR parameters like A/B tests.
-Astro recommends running the Markdoc transform step manually. This allows you to define your configuration and call Markdoc's rendering functions in a `.astro` file directly, ignoring any Markdoc config in your `astro.config.mjs`.
-
-You will need to install the `@markdoc/markdoc` package into your project first:
-
-```sh
-# Using NPM
-npm install @markdoc/markdoc
-# Using Yarn
-yarn add @markdoc/markdoc
-# Using PNPM
-pnpm add @markdoc/markdoc
-```
-
-Now, you can define Markdoc configuration options using `Markdock.transform()`.
-
-This example defines an `abTestGroup` Markdoc variable based on an SSR param, transforming the raw entry `body`. The result is rendered using the `Renderer` component provided by `@astrojs/markdoc`:
+Variables can be passed as props via the `Content` component:
```astro
---
-import Markdoc from '@markdoc/markdoc';
-import { Renderer } from '@astrojs/markdoc/components';
import { getEntryBySlug } from 'astro:content';
-const { body } = await getEntryBySlug('docs', 'with-ab-test');
-const ast = Markdoc.parse(body);
-const content = Markdoc.transform({
- variables: { abTestGroup: Astro.params.abTestGroup },
-}, ast);
+const entry = await getEntryBySlug('docs', 'why-markdoc');
+const { Content } = await entry.render();
---
-
+
+
+```
+
+Now, `abTestGroup` is available as a variable in `docs/why-markdoc.mdoc`:
+
+```md
+{% if $abTestGroup === 'image-optimization-lover' %}
+
+Let me tell you about image optimization...
+
+{% /if %}
+```
+
+To make a variable global to all Markdoc files, you can use the `variables` attribute from your `markdoc.config.mjs|ts`:
+
+```js
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
+
+export default defineMarkdocConfig({
+ variables: {
+ environment: process.env.IS_PROD ? 'prod' : 'dev',
+ }
+})
```
## Examples
@@ -360,3 +322,5 @@ See [CHANGELOG.md](https://github.com/withastro/astro/tree/main/packages/integra
[markdoc-tags]: https://markdoc.dev/docs/tags
[markdoc-nodes]: https://markdoc.dev/docs/nodes
+
+[markdoc-variables]: https://markdoc.dev/docs/variables
diff --git a/packages/integrations/markdoc/components/Renderer.astro b/packages/integrations/markdoc/components/Renderer.astro
index 6ae8ee850..5e2b6833a 100644
--- a/packages/integrations/markdoc/components/Renderer.astro
+++ b/packages/integrations/markdoc/components/Renderer.astro
@@ -1,20 +1,17 @@
---
-import type { RenderableTreeNode } from '@markdoc/markdoc';
-import type { AstroInstance } from 'astro';
-import { validateComponentsProp } from '../dist/utils.js';
+import type { Config } from '@markdoc/markdoc';
+import Markdoc from '@markdoc/markdoc';
import { ComponentNode, createTreeNode } from './TreeNode.js';
type Props = {
- content: RenderableTreeNode;
- components?: Record;
+ config: Config;
+ stringifiedAst: string;
};
-const { content, components } = Astro.props as Props;
+const { stringifiedAst, config } = Astro.props as Props;
-// Will throw if components is invalid
-if (components) {
- validateComponentsProp(components);
-}
+const ast = Markdoc.Ast.fromJSON(stringifiedAst);
+const content = Markdoc.transform(ast, config);
---
-
+
diff --git a/packages/integrations/markdoc/components/TreeNode.ts b/packages/integrations/markdoc/components/TreeNode.ts
index f46355d5c..8a3158589 100644
--- a/packages/integrations/markdoc/components/TreeNode.ts
+++ b/packages/integrations/markdoc/components/TreeNode.ts
@@ -1,10 +1,7 @@
import type { AstroInstance } from 'astro';
import type { RenderableTreeNode } from '@markdoc/markdoc';
-import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js';
-// @ts-expect-error Cannot find module 'astro:markdoc-assets' or its corresponding type declarations
-import { Image } from 'astro:markdoc-assets';
import Markdoc from '@markdoc/markdoc';
-import { MarkdocError, isCapitalized } from '../dist/utils.js';
+import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js';
export type TreeNode =
| {
@@ -47,26 +44,17 @@ export const ComponentNode = createComponent({
propagation: 'none',
});
-const builtInComponents: Record = {
- Image,
-};
-
-export function createTreeNode(
- node: RenderableTreeNode,
- userComponents: Record = {}
-): TreeNode {
- const components = { ...userComponents, ...builtInComponents };
-
+export function createTreeNode(node: RenderableTreeNode): TreeNode {
if (typeof node === 'string' || typeof node === 'number') {
return { type: 'text', content: String(node) };
} else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) {
return { type: 'text', content: '' };
}
- if (node.name in components) {
- const component = components[node.name];
+ if (typeof node.name === 'function') {
+ const component = node.name;
const props = node.attributes;
- const children = node.children.map((child) => createTreeNode(child, components));
+ const children = node.children.map((child) => createTreeNode(child));
return {
type: 'component',
@@ -74,17 +62,12 @@ export function createTreeNode(
props,
children,
};
- } else if (isCapitalized(node.name)) {
- throw new MarkdocError({
- message: `Unable to render ${JSON.stringify(node.name)}.`,
- hint: 'Did you add this to the "components" prop on your component?',
- });
} else {
return {
type: 'element',
tag: node.name,
attributes: node.attributes,
- children: node.children.map((child) => createTreeNode(child, components)),
+ children: node.children.map((child) => createTreeNode(child)),
};
}
}
diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json
index 67b311c65..5fb778b27 100644
--- a/packages/integrations/markdoc/package.json
+++ b/packages/integrations/markdoc/package.json
@@ -21,6 +21,9 @@
"exports": {
".": "./dist/index.js",
"./components": "./components/index.ts",
+ "./default-config": "./dist/default-config.js",
+ "./config": "./dist/config.js",
+ "./experimental-assets-config": "./dist/experimental-assets-config.js",
"./package.json": "./package.json"
},
"scripts": {
@@ -32,17 +35,19 @@
},
"dependencies": {
"@markdoc/markdoc": "^0.2.2",
+ "esbuild": "^0.17.12",
"gray-matter": "^4.0.3",
+ "kleur": "^4.1.5",
"zod": "^3.17.3"
},
"peerDependencies": {
"astro": "workspace:*"
},
"devDependencies": {
- "astro": "workspace:*",
"@types/chai": "^4.3.1",
"@types/html-escaper": "^3.0.0",
"@types/mocha": "^9.1.1",
+ "astro": "workspace:*",
"astro-scripts": "workspace:*",
"chai": "^4.3.6",
"devalue": "^4.2.0",
diff --git a/packages/integrations/markdoc/src/config.ts b/packages/integrations/markdoc/src/config.ts
new file mode 100644
index 000000000..4c20e311f
--- /dev/null
+++ b/packages/integrations/markdoc/src/config.ts
@@ -0,0 +1,5 @@
+import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc';
+
+export function defineMarkdocConfig(config: MarkdocConfig): MarkdocConfig {
+ return config;
+}
diff --git a/packages/integrations/markdoc/src/default-config.ts b/packages/integrations/markdoc/src/default-config.ts
new file mode 100644
index 000000000..16bd2c41f
--- /dev/null
+++ b/packages/integrations/markdoc/src/default-config.ts
@@ -0,0 +1,18 @@
+import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc';
+import type { ContentEntryModule } from 'astro';
+
+export function applyDefaultConfig(
+ config: MarkdocConfig,
+ ctx: {
+ entry: ContentEntryModule;
+ }
+): MarkdocConfig {
+ return {
+ ...config,
+ variables: {
+ entry: ctx.entry,
+ ...config.variables,
+ },
+ // TODO: heading ID calculation, Shiki syntax highlighting
+ };
+}
diff --git a/packages/integrations/markdoc/src/experimental-assets-config.ts b/packages/integrations/markdoc/src/experimental-assets-config.ts
new file mode 100644
index 000000000..962755355
--- /dev/null
+++ b/packages/integrations/markdoc/src/experimental-assets-config.ts
@@ -0,0 +1,29 @@
+import type { Config as MarkdocConfig } from '@markdoc/markdoc';
+import Markdoc from '@markdoc/markdoc';
+//@ts-expect-error Cannot find module 'astro:assets' or its corresponding type declarations.
+import { Image } from 'astro:assets';
+
+// Separate module to only import `astro:assets` when
+// `experimental.assets` flag is set in a project.
+// TODO: merge with `./default-config.ts` when `experimental.assets` is baselined.
+export const experimentalAssetsConfig: MarkdocConfig = {
+ nodes: {
+ image: {
+ attributes: {
+ ...Markdoc.nodes.image.attributes,
+ __optimizedSrc: { type: 'Object' },
+ },
+ transform(node, config) {
+ const attributes = node.transformAttributes(config);
+ const children = node.transformChildren(config);
+
+ if (node.type === 'image' && '__optimizedSrc' in node.attributes) {
+ const { __optimizedSrc, ...rest } = node.attributes;
+ return new Markdoc.Tag(Image, { ...rest, src: __optimizedSrc }, children);
+ } else {
+ return new Markdoc.Tag('img', attributes, children);
+ }
+ },
+ },
+ },
+};
diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts
index ebfc09ba7..feb9a501c 100644
--- a/packages/integrations/markdoc/src/index.ts
+++ b/packages/integrations/markdoc/src/index.ts
@@ -1,23 +1,15 @@
-import type {
- Config as ReadonlyMarkdocConfig,
- ConfigType as MarkdocConfig,
- Node,
-} from '@markdoc/markdoc';
+import type { Node } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
-import type * as rollup from 'rollup';
-import {
- getAstroConfigPath,
- isValidUrl,
- MarkdocError,
- parseFrontmatter,
- prependForwardSlash,
-} from './utils.js';
+import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from './utils.js';
// @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations.
import { emitESMImage } from 'astro/assets';
-import type { Plugin as VitePlugin } from 'vite';
+import { loadMarkdocConfig } from './load-config.js';
+import { applyDefaultConfig } from './default-config.js';
+import { bold, red } from 'kleur/colors';
+import type * as rollup from 'rollup';
type SetupHookParams = HookParameters<'astro:config:setup'> & {
// `contentEntryType` is not a public API
@@ -25,24 +17,24 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & {
addContentEntryType: (contentEntryType: ContentEntryType) => void;
};
-export default function markdocIntegration(
- userMarkdocConfig: ReadonlyMarkdocConfig = {}
-): AstroIntegration {
+export default function markdocIntegration(legacyConfig: any): AstroIntegration {
+ if (legacyConfig) {
+ // eslint-disable-next-line no-console
+ console.log(
+ `${red(
+ bold('[Markdoc]')
+ )} Passing Markdoc config from your \`astro.config\` is no longer supported. Configuration should be exported from a \`markdoc.config.mjs\` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration`
+ );
+ process.exit(0);
+ }
return {
name: '@astrojs/markdoc',
hooks: {
'astro:config:setup': async (params) => {
- const {
- updateConfig,
- config: astroConfig,
- addContentEntryType,
- } = params as SetupHookParams;
+ const { config: astroConfig, addContentEntryType } = params as SetupHookParams;
- updateConfig({
- vite: {
- plugins: [safeAssetsVirtualModulePlugin({ astroConfig })],
- },
- });
+ const configLoadResult = await loadMarkdocConfig(astroConfig);
+ const userMarkdocConfig = configLoadResult?.config ?? {};
function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
@@ -56,49 +48,63 @@ export default function markdocIntegration(
addContentEntryType({
extensions: ['.mdoc'],
getEntryInfo,
- async getRenderModule({ entry }) {
- validateRenderProperties(userMarkdocConfig, astroConfig);
+ async getRenderModule({ entry, viteId }) {
const ast = Markdoc.parse(entry.body);
const pluginContext = this;
- const markdocConfig: MarkdocConfig = {
- ...userMarkdocConfig,
- variables: {
- ...userMarkdocConfig.variables,
- entry,
- },
- };
+ const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry });
- if (astroConfig.experimental?.assets) {
+ const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
+ // Ignore `variable-undefined` errors.
+ // Variables can be configured at runtime,
+ // so we cannot validate them at build time.
+ return e.error.id !== 'variable-undefined';
+ });
+ if (validationErrors.length) {
+ throw new MarkdocError({
+ message: [
+ `**${String(entry.collection)} → ${String(entry.id)}** failed to validate:`,
+ ...validationErrors.map((e) => e.error.id),
+ ].join('\n'),
+ });
+ }
+
+ if (astroConfig.experimental.assets) {
await emitOptimizedImages(ast.children, {
astroConfig,
pluginContext,
filePath: entry._internal.filePath,
});
-
- markdocConfig.nodes ??= {};
- markdocConfig.nodes.image = {
- ...Markdoc.nodes.image,
- transform(node, config) {
- const attributes = node.transformAttributes(config);
- const children = node.transformChildren(config);
-
- if (node.type === 'image' && '__optimizedSrc' in node.attributes) {
- const { __optimizedSrc, ...rest } = node.attributes;
- return new Markdoc.Tag('Image', { ...rest, src: __optimizedSrc }, children);
- } else {
- return new Markdoc.Tag('img', attributes, children);
- }
- },
- };
}
- const content = Markdoc.transform(ast, markdocConfig);
-
- return {
- code: `import { jsx as h } from 'astro/jsx-runtime';\nimport { Renderer } from '@astrojs/markdoc/components';\nconst transformedContent = ${JSON.stringify(
- content
- )};\nexport async function Content ({ components }) { return h(Renderer, { content: transformedContent, components }); }\nContent[Symbol.for('astro.needsHeadRendering')] = true;`,
+ const code = {
+ code: `import { jsx as h } from 'astro/jsx-runtime';
+import { applyDefaultConfig } from '@astrojs/markdoc/default-config';
+import { Renderer } from '@astrojs/markdoc/components';
+import * as entry from ${JSON.stringify(viteId + '?astroContent')};${
+ configLoadResult
+ ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};`
+ : ''
+ }${
+ astroConfig.experimental.assets
+ ? `\nimport { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';`
+ : ''
+ }
+const stringifiedAst = ${JSON.stringify(
+ /* Double stringify to encode *as* stringified JSON */ JSON.stringify(ast)
+ )};
+export async function Content (props) {
+ const config = applyDefaultConfig(${
+ configLoadResult
+ ? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }'
+ : '{ variables: props }'
+ }, { entry });${
+ astroConfig.experimental.assets
+ ? `\nconfig.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };`
+ : ''
+ }
+ return h(Renderer, { stringifiedAst, config }); };`,
};
+ return code;
},
contentModuleTypes: await fs.promises.readFile(
new URL('../template/content-module-types.d.ts', import.meta.url),
@@ -156,87 +162,3 @@ function shouldOptimizeImage(src: string) {
// Optimize anything that is NOT external or an absolute path to `public/`
return !isValidUrl(src) && !src.startsWith('/');
}
-
-function validateRenderProperties(markdocConfig: ReadonlyMarkdocConfig, astroConfig: AstroConfig) {
- const tags = markdocConfig.tags ?? {};
- const nodes = markdocConfig.nodes ?? {};
-
- for (const [name, config] of Object.entries(tags)) {
- validateRenderProperty({ type: 'tag', name, config, astroConfig });
- }
- for (const [name, config] of Object.entries(nodes)) {
- validateRenderProperty({ type: 'node', name, config, astroConfig });
- }
-}
-
-function validateRenderProperty({
- name,
- config,
- type,
- astroConfig,
-}: {
- name: string;
- config: { render?: string };
- type: 'node' | 'tag';
- astroConfig: Pick;
-}) {
- if (typeof config.render === 'string' && config.render.length === 0) {
- throw new Error(
- `Invalid ${type} configuration: ${JSON.stringify(
- name
- )}. The "render" property cannot be an empty string.`
- );
- }
- if (typeof config.render === 'string' && !isCapitalized(config.render)) {
- const astroConfigPath = getAstroConfigPath(fs, fileURLToPath(astroConfig.root));
- throw new MarkdocError({
- message: `Invalid ${type} configuration: ${JSON.stringify(
- name
- )}. The "render" property must reference a capitalized component name.`,
- hint: 'If you want to render to an HTML element, see our docs on rendering Markdoc manually: https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components',
- location: astroConfigPath
- ? {
- file: astroConfigPath,
- }
- : undefined,
- });
- }
-}
-
-function isCapitalized(str: string) {
- return str.length > 0 && str[0] === str[0].toUpperCase();
-}
-
-/**
- * TODO: remove when `experimental.assets` is baselined.
- *
- * `astro:assets` will fail to resolve if the `experimental.assets` flag is not enabled.
- * This ensures a fallback for the Markdoc renderer to safely import at the top level.
- * @see ../components/TreeNode.ts
- */
-function safeAssetsVirtualModulePlugin({
- astroConfig,
-}: {
- astroConfig: Pick;
-}): VitePlugin {
- const virtualModuleId = 'astro:markdoc-assets';
- const resolvedVirtualModuleId = '\0' + virtualModuleId;
-
- return {
- name: 'astro:markdoc-safe-assets-virtual-module',
- resolveId(id) {
- if (id === virtualModuleId) {
- return resolvedVirtualModuleId;
- }
- },
- load(id) {
- if (id !== resolvedVirtualModuleId) return;
-
- if (astroConfig.experimental?.assets) {
- return `export { Image } from 'astro:assets';`;
- } else {
- return `export const Image = () => { throw new Error('Cannot use the Image component without the \`experimental.assets\` flag.'); }`;
- }
- },
- };
-}
diff --git a/packages/integrations/markdoc/src/load-config.ts b/packages/integrations/markdoc/src/load-config.ts
new file mode 100644
index 000000000..db36edf25
--- /dev/null
+++ b/packages/integrations/markdoc/src/load-config.ts
@@ -0,0 +1,102 @@
+import type { AstroConfig } from 'astro';
+import type { Config as MarkdocConfig } from '@markdoc/markdoc';
+import { build as esbuild } from 'esbuild';
+import { fileURLToPath } from 'node:url';
+import * as fs from 'node:fs';
+
+const SUPPORTED_MARKDOC_CONFIG_FILES = [
+ 'markdoc.config.js',
+ 'markdoc.config.mjs',
+ 'markdoc.config.mts',
+ 'markdoc.config.ts',
+];
+
+export async function loadMarkdocConfig(astroConfig: Pick) {
+ let markdocConfigUrl: URL | undefined;
+ for (const filename of SUPPORTED_MARKDOC_CONFIG_FILES) {
+ const filePath = new URL(filename, astroConfig.root);
+ if (!fs.existsSync(filePath)) continue;
+
+ markdocConfigUrl = filePath;
+ break;
+ }
+ if (!markdocConfigUrl) return;
+
+ const { code, dependencies } = await bundleConfigFile({
+ markdocConfigUrl,
+ astroConfig,
+ });
+ const config: MarkdocConfig = await loadConfigFromBundledFile(astroConfig.root, code);
+
+ return {
+ config,
+ fileUrl: markdocConfigUrl,
+ };
+}
+
+/**
+ * Forked from Vite's `bundleConfigFile` function
+ * with added handling for `.astro` imports,
+ * and removed unused Deno patches.
+ * @see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/config.ts#L961
+ */
+async function bundleConfigFile({
+ markdocConfigUrl,
+ astroConfig,
+}: {
+ markdocConfigUrl: URL;
+ astroConfig: Pick;
+}): Promise<{ code: string; dependencies: string[] }> {
+ const result = await esbuild({
+ absWorkingDir: fileURLToPath(astroConfig.root),
+ entryPoints: [fileURLToPath(markdocConfigUrl)],
+ outfile: 'out.js',
+ write: false,
+ target: ['node16'],
+ platform: 'node',
+ packages: 'external',
+ bundle: true,
+ format: 'esm',
+ sourcemap: 'inline',
+ metafile: true,
+ plugins: [
+ {
+ name: 'stub-astro-imports',
+ setup(build) {
+ build.onResolve({ filter: /.*\.astro$/ }, () => {
+ return {
+ // Stub with an unused default export
+ path: 'data:text/javascript,export default true',
+ external: true,
+ };
+ });
+ },
+ },
+ ],
+ });
+ const { text } = result.outputFiles[0];
+ return {
+ code: text,
+ dependencies: result.metafile ? Object.keys(result.metafile.inputs) : [],
+ };
+}
+
+/**
+ * Forked from Vite config loader, replacing CJS-based path concat
+ * with ESM only
+ * @see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/config.ts#L1074
+ */
+async function loadConfigFromBundledFile(root: URL, code: string): Promise {
+ // Write it to disk, load it with native Node ESM, then delete the file.
+ const tmpFileUrl = new URL(`markdoc.config.timestamp-${Date.now()}.mjs`, root);
+ fs.writeFileSync(tmpFileUrl, code);
+ try {
+ return (await import(tmpFileUrl.pathname)).default;
+ } finally {
+ try {
+ fs.unlinkSync(tmpFileUrl);
+ } catch {
+ // already removed if this function is called twice simultaneously
+ }
+ }
+}
diff --git a/packages/integrations/markdoc/src/utils.ts b/packages/integrations/markdoc/src/utils.ts
index 9d6e5af26..95f84700c 100644
--- a/packages/integrations/markdoc/src/utils.ts
+++ b/packages/integrations/markdoc/src/utils.ts
@@ -1,5 +1,3 @@
-import type { AstroInstance } from 'astro';
-import z from 'astro/zod';
import matter from 'gray-matter';
import type fsMod from 'node:fs';
import path from 'node:path';
@@ -85,28 +83,6 @@ interface ErrorProperties {
frame?: string;
}
-/**
- * Matches `search` function used for resolving `astro.config` files.
- * Used by Markdoc for error handling.
- * @see 'astro/src/core/config/config.ts'
- */
-export function getAstroConfigPath(fs: typeof fsMod, root: string): string | undefined {
- const paths = [
- 'astro.config.mjs',
- 'astro.config.js',
- 'astro.config.ts',
- 'astro.config.mts',
- 'astro.config.cjs',
- 'astro.config.cts',
- ].map((p) => path.join(root, p));
-
- for (const file of paths) {
- if (fs.existsSync(file)) {
- return file;
- }
- }
-}
-
/**
* @see 'astro/src/core/path.ts'
*/
@@ -114,38 +90,6 @@ export function prependForwardSlash(str: string) {
return str[0] === '/' ? str : '/' + str;
}
-export function validateComponentsProp(components: Record) {
- try {
- componentsPropValidator.parse(components);
- } catch (e) {
- throw new MarkdocError({
- message:
- e instanceof z.ZodError
- ? e.issues[0].message
- : 'Invalid `components` prop. Ensure you are passing an object of components to ',
- });
- }
-}
-
-const componentsPropValidator = z.record(
- z
- .string()
- .min(1, 'Invalid `components` prop. Component names cannot be empty!')
- .refine(
- (value) => isCapitalized(value),
- (value) => ({
- message: `Invalid \`components\` prop: ${JSON.stringify(
- value
- )}. Component name must be capitalized. If you want to render HTML elements as components, try using a Markdoc node (https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components)`,
- })
- ),
- z.any()
-);
-
-export function isCapitalized(str: string) {
- return str.length > 0 && str[0] === str[0].toUpperCase();
-}
-
export function isValidUrl(str: string): boolean {
try {
new URL(str);
diff --git a/packages/integrations/markdoc/template/content-module-types.d.ts b/packages/integrations/markdoc/template/content-module-types.d.ts
index 87c17af24..7b82eb18a 100644
--- a/packages/integrations/markdoc/template/content-module-types.d.ts
+++ b/packages/integrations/markdoc/template/content-module-types.d.ts
@@ -1,9 +1,7 @@
declare module 'astro:content' {
interface Render {
'.mdoc': Promise<{
- Content(props: {
- components?: Record;
- }): import('astro').MarkdownInstance<{}>['Content'];
+ Content(props: Record): import('astro').MarkdownInstance<{}>['Content'];
}>;
}
}
diff --git a/packages/integrations/markdoc/test/content-collections.test.js b/packages/integrations/markdoc/test/content-collections.test.js
index 5822c181a..aad389e0c 100644
--- a/packages/integrations/markdoc/test/content-collections.test.js
+++ b/packages/integrations/markdoc/test/content-collections.test.js
@@ -1,4 +1,3 @@
-import { parseHTML } from 'linkedom';
import { parse as parseDevalue } from 'devalue';
import { expect } from 'chai';
import { loadFixture, fixLineEndings } from '../../../astro/test/test-utils.js';
@@ -37,70 +36,20 @@ describe('Markdoc - Content Collections', () => {
it('loads entry', async () => {
const res = await baseFixture.fetch('/entry.json');
const post = parseDevalue(await res.text());
- expect(formatPost(post)).to.deep.equal(simplePostEntry);
+ expect(formatPost(post)).to.deep.equal(post1Entry);
});
it('loads collection', async () => {
const res = await baseFixture.fetch('/collection.json');
const posts = parseDevalue(await res.text());
expect(posts).to.not.be.null;
+
expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([
- simplePostEntry,
- withComponentsEntry,
- withConfigEntry,
+ post1Entry,
+ post2Entry,
+ post3Entry,
]);
});
-
- it('renders content - simple', async () => {
- const res = await baseFixture.fetch('/content-simple');
- const html = await res.text();
- const { document } = parseHTML(html);
- const h2 = document.querySelector('h2');
- expect(h2.textContent).to.equal('Simple post');
- const p = document.querySelector('p');
- expect(p.textContent).to.equal('This is a simple Markdoc post.');
- });
-
- it('renders content - with config', async () => {
- const fixture = await getFixtureWithConfig();
- const server = await fixture.startDevServer();
-
- const res = await fixture.fetch('/content-with-config');
- const html = await res.text();
- const { document } = parseHTML(html);
- const h2 = document.querySelector('h2');
- expect(h2.textContent).to.equal('Post with config');
- const textContent = html;
-
- expect(textContent).to.not.include('Hello');
- expect(textContent).to.include('Hola');
- expect(textContent).to.include(`Konnichiwa`);
-
- await server.stop();
- });
-
- it('renders content - with components', async () => {
- const fixture = await getFixtureWithComponents();
- const server = await fixture.startDevServer();
-
- const res = await fixture.fetch('/content-with-components');
- const html = await res.text();
- const { document } = parseHTML(html);
- const h2 = document.querySelector('h2');
- expect(h2.textContent).to.equal('Post with components');
-
- // Renders custom shortcode component
- const marquee = document.querySelector('marquee');
- expect(marquee).to.not.be.null;
- expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true);
-
- // Renders Astro Code component
- const pre = document.querySelector('pre');
- expect(pre).to.not.be.null;
- expect(pre.className).to.equal('astro-code');
-
- await server.stop();
- });
});
describe('build', () => {
@@ -111,7 +60,7 @@ describe('Markdoc - Content Collections', () => {
it('loads entry', async () => {
const res = await baseFixture.readFile('/entry.json');
const post = parseDevalue(res);
- expect(formatPost(post)).to.deep.equal(simplePostEntry);
+ expect(formatPost(post)).to.deep.equal(post1Entry);
});
it('loads collection', async () => {
@@ -119,140 +68,43 @@ describe('Markdoc - Content Collections', () => {
const posts = parseDevalue(res);
expect(posts).to.not.be.null;
expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([
- simplePostEntry,
- withComponentsEntry,
- withConfigEntry,
+ post1Entry,
+ post2Entry,
+ post3Entry,
]);
});
-
- it('renders content - simple', async () => {
- const html = await baseFixture.readFile('/content-simple/index.html');
- const { document } = parseHTML(html);
- const h2 = document.querySelector('h2');
- expect(h2.textContent).to.equal('Simple post');
- const p = document.querySelector('p');
- expect(p.textContent).to.equal('This is a simple Markdoc post.');
- });
-
- it('renders content - with config', async () => {
- const fixture = await getFixtureWithConfig();
- await fixture.build();
-
- const html = await fixture.readFile('/content-with-config/index.html');
- const { document } = parseHTML(html);
- const h2 = document.querySelector('h2');
- expect(h2.textContent).to.equal('Post with config');
- const textContent = html;
-
- expect(textContent).to.not.include('Hello');
- expect(textContent).to.include('Hola');
- expect(textContent).to.include(`Konnichiwa`);
- });
-
- it('renders content - with components', async () => {
- const fixture = await getFixtureWithComponents();
- await fixture.build();
-
- const html = await fixture.readFile('/content-with-components/index.html');
- const { document } = parseHTML(html);
- const h2 = document.querySelector('h2');
- expect(h2.textContent).to.equal('Post with components');
-
- // Renders custom shortcode component
- const marquee = document.querySelector('marquee');
- expect(marquee).to.not.be.null;
- expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true);
-
- // Renders Astro Code component
- const pre = document.querySelector('pre');
- expect(pre).to.not.be.null;
- expect(pre.className).to.equal('astro-code');
- });
});
});
-function getFixtureWithConfig() {
- return loadFixture({
- root,
- integrations: [
- markdoc({
- variables: {
- countries: ['ES', 'JP'],
- },
- functions: {
- includes: {
- transform(parameters) {
- const [array, value] = Object.values(parameters);
- return Array.isArray(array) ? array.includes(value) : false;
- },
- },
- },
- }),
- ],
- });
-}
-
-function getFixtureWithComponents() {
- return loadFixture({
- root,
- integrations: [
- markdoc({
- nodes: {
- fence: {
- render: 'Code',
- attributes: {
- language: { type: String },
- content: { type: String },
- },
- },
- },
- tags: {
- mq: {
- render: 'CustomMarquee',
- attributes: {
- direction: {
- type: String,
- default: 'left',
- matches: ['left', 'right', 'up', 'down'],
- errorLevel: 'critical',
- },
- },
- },
- },
- }),
- ],
- });
-}
-
-const simplePostEntry = {
- id: 'simple.mdoc',
- slug: 'simple',
+const post1Entry = {
+ id: 'post-1.mdoc',
+ slug: 'post-1',
collection: 'blog',
data: {
schemaWorks: true,
- title: 'Simple post',
+ title: 'Post 1',
},
- body: '\n## Simple post\n\nThis is a simple Markdoc post.\n',
+ body: '\n## Post 1\n\nThis is the contents of post 1.\n',
};
-const withComponentsEntry = {
- id: 'with-components.mdoc',
- slug: 'with-components',
+const post2Entry = {
+ id: 'post-2.mdoc',
+ slug: 'post-2',
collection: 'blog',
data: {
schemaWorks: true,
- title: 'Post with components',
+ title: 'Post 2',
},
- body: '\n## Post with components\n\nThis uses a custom marquee component with a shortcode:\n\n{% mq direction="right" %}\nI\'m a marquee too!\n{% /mq %}\n\nAnd a code component for code blocks:\n\n```js\nconst isRenderedWithShiki = true;\n```\n',
+ body: '\n## Post 2\n\nThis is the contents of post 2.\n',
};
-const withConfigEntry = {
- id: 'with-config.mdoc',
- slug: 'with-config',
+const post3Entry = {
+ id: 'post-3.mdoc',
+ slug: 'post-3',
collection: 'blog',
data: {
schemaWorks: true,
- title: 'Post with config',
+ title: 'Post 3',
},
- body: '\n## Post with config\n\n{% if includes($countries, "EN") %} Hello {% /if %}\n{% if includes($countries, "ES") %} Hola {% /if %}\n{% if includes($countries, "JP") %} Konnichiwa {% /if %}\n',
+ body: '\n## Post 3\n\nThis is the contents of post 3.\n',
};
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/package.json b/packages/integrations/markdoc/test/fixtures/content-collections/package.json
index a6403f49b..370b87957 100644
--- a/packages/integrations/markdoc/test/fixtures/content-collections/package.json
+++ b/packages/integrations/markdoc/test/fixtures/content-collections/package.json
@@ -4,10 +4,6 @@
"private": true,
"dependencies": {
"@astrojs/markdoc": "workspace:*",
- "@markdoc/markdoc": "^0.2.2",
"astro": "workspace:*"
- },
- "devDependencies": {
- "shiki": "^0.11.1"
}
}
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-1.mdoc b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-1.mdoc
new file mode 100644
index 000000000..06c900963
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-1.mdoc
@@ -0,0 +1,7 @@
+---
+title: Post 1
+---
+
+## Post 1
+
+This is the contents of post 1.
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-2.mdoc b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-2.mdoc
new file mode 100644
index 000000000..cf4dc162f
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-2.mdoc
@@ -0,0 +1,7 @@
+---
+title: Post 2
+---
+
+## Post 2
+
+This is the contents of post 2.
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-3.mdoc b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-3.mdoc
new file mode 100644
index 000000000..6c601eb65
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-3.mdoc
@@ -0,0 +1,7 @@
+---
+title: Post 3
+---
+
+## Post 3
+
+This is the contents of post 3.
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js
index 842291826..7899a757a 100644
--- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js
+++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js
@@ -3,7 +3,7 @@ import { stringify } from 'devalue';
import { stripRenderFn } from '../../utils.js';
export async function get() {
- const post = await getEntryBySlug('blog', 'simple');
+ const post = await getEntryBySlug('blog', 'post-1');
return {
body: stringify(stripRenderFn(post)),
};
diff --git a/packages/integrations/markdoc/test/fixtures/render-simple/astro.config.mjs b/packages/integrations/markdoc/test/fixtures/render-simple/astro.config.mjs
new file mode 100644
index 000000000..29d846359
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/render-simple/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/packages/integrations/markdoc/test/fixtures/render-simple/package.json b/packages/integrations/markdoc/test/fixtures/render-simple/package.json
new file mode 100644
index 000000000..9354cdc58
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/render-simple/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/markdoc-render-simple",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/markdoc": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/simple.mdoc b/packages/integrations/markdoc/test/fixtures/render-simple/src/content/blog/simple.mdoc
similarity index 100%
rename from packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/simple.mdoc
rename to packages/integrations/markdoc/test/fixtures/render-simple/src/content/blog/simple.mdoc
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-simple.astro b/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro
similarity index 92%
rename from packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-simple.astro
rename to packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro
index effbbee1c..940eef154 100644
--- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-simple.astro
+++ b/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro
@@ -1,5 +1,6 @@
---
import { getEntryBySlug } from "astro:content";
+
const post = await getEntryBySlug('blog', 'simple');
const { Content } = await post.render();
---
@@ -10,7 +11,7 @@ const { Content } = await post.render();
- Content - Simple
+ Content
diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/astro.config.mjs b/packages/integrations/markdoc/test/fixtures/render-with-components/astro.config.mjs
new file mode 100644
index 000000000..29d846359
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/render-with-components/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/packages/integrations/markdoc/test/fixtures/render-with-components/markdoc.config.mjs b/packages/integrations/markdoc/test/fixtures/render-with-components/markdoc.config.mjs
new file mode 100644
index 000000000..ada03f5f4
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/render-with-components/markdoc.config.mjs
@@ -0,0 +1,28 @@
+import Code from './src/components/Code.astro';
+import CustomMarquee from './src/components/CustomMarquee.astro';
+import { defineMarkdocConfig } from '@astrojs/markdoc/config';
+
+export default defineMarkdocConfig({
+ nodes: {
+ fence: {
+ render: Code,
+ attributes: {
+ language: { type: String },
+ content: { type: String },
+ },
+ },
+ },
+ tags: {
+ mq: {
+ render: CustomMarquee,
+ attributes: {
+ direction: {
+ type: String,
+ default: 'left',
+ matches: ['left', 'right', 'up', 'down'],
+ errorLevel: 'critical',
+ },
+ },
+ },
+ },
+})
diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/package.json b/packages/integrations/markdoc/test/fixtures/render-with-components/package.json
new file mode 100644
index 000000000..f14c97f0f
--- /dev/null
+++ b/packages/integrations/markdoc/test/fixtures/render-with-components/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@test/markdoc-render-with-components",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/markdoc": "workspace:*",
+ "astro": "workspace:*"
+ },
+ "devDependencies": {
+ "shiki": "^0.11.1"
+ }
+}
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/components/Code.astro b/packages/integrations/markdoc/test/fixtures/render-with-components/src/components/Code.astro
similarity index 100%
rename from packages/integrations/markdoc/test/fixtures/content-collections/src/components/Code.astro
rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/components/Code.astro
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/components/CustomMarquee.astro b/packages/integrations/markdoc/test/fixtures/render-with-components/src/components/CustomMarquee.astro
similarity index 100%
rename from packages/integrations/markdoc/test/fixtures/content-collections/src/components/CustomMarquee.astro
rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/components/CustomMarquee.astro
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/with-components.mdoc b/packages/integrations/markdoc/test/fixtures/render-with-components/src/content/blog/with-components.mdoc
similarity index 100%
rename from packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/with-components.mdoc
rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/content/blog/with-components.mdoc
diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-components.astro b/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro
similarity index 65%
rename from packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-components.astro
rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro
index dfb9b1de5..52239acce 100644
--- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-components.astro
+++ b/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro
@@ -1,7 +1,5 @@
---
import { getEntryBySlug } from "astro:content";
-import Code from '../components/Code.astro';
-import CustomMarquee from '../components/CustomMarquee.astro';
const post = await getEntryBySlug('blog', 'with-components');
const { Content } = await post.render();
@@ -13,11 +11,9 @@ const { Content } = await post.render();
- Content - with components
+ Content
-
+