@astrojs/markdoc 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -97,13 +97,9 @@ const { Content } = await entry.render();
97
97
 
98
98
  `@astrojs/markdoc` offers configuration options to use all of Markdoc's features and connect UI components to your content.
99
99
 
100
- ### Using components
100
+ ### Use Astro components as Markdoc tags
101
101
 
102
- You can add Astro components to your Markdoc using both [Markdoc tags][markdoc-tags] and HTML element [nodes][markdoc-nodes].
103
-
104
- #### Render Markdoc tags as Astro components
105
-
106
- 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.
102
+ You can configure [Markdoc tags][markdoc-tags] that map to `.astro` components. You can add a new tag by creating a `markdoc.config.mjs|ts` file at the root of your project and configuring the `tag` attribute.
107
103
 
108
104
  This example renders an `Aside` component, and allows a `type` prop to be passed as a string:
109
105
 
@@ -141,9 +137,11 @@ Use tags like this fancy "aside" to add some *flair* to your docs.
141
137
  {% /aside %}
142
138
  ```
143
139
 
144
- #### Render Markdoc nodes / HTML elements as Astro components
140
+ ### Custom headings
141
+
142
+ `@astrojs/markdoc` automatically adds anchor links to your headings, and [generates a list of `headings` via the content collections API](https://docs.astro.build/en/guides/content-collections/#rendering-content-to-html). To further customize how headings are rendered, you can apply an Astro component [as a Markdoc node][markdoc-nodes].
145
143
 
146
- 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, and passes through Astro's default heading properties to define attributes and generate heading ids / slugs:
144
+ This example renders a `Heading.astro` component using the `render` property:
147
145
 
148
146
  ```js
149
147
  // markdoc.config.mjs
@@ -154,26 +152,115 @@ export default defineMarkdocConfig({
154
152
  nodes: {
155
153
  heading: {
156
154
  render: Heading,
155
+ // Preserve default anchor link generation
157
156
  ...nodes.heading,
158
157
  },
159
158
  },
160
159
  })
161
160
  ```
162
161
 
163
- All Markdown headings will render the `Heading.astro` component and pass `attributes` as component props. For headings, Astro provides the following attributes by default:
162
+ All Markdown headings will render the `Heading.astro` component and pass the following `attributes` as component props:
164
163
 
165
164
  - `level: number` The heading level 1 - 6
166
165
  - `id: string` An `id` generated from the heading's text contents. This corresponds to the `slug` generated by the [content `render()` function](https://docs.astro.build/en/guides/content-collections/#rendering-content-to-html).
167
166
 
168
167
  For example, the heading `### Level 3 heading!` will pass `level: 3` and `id: 'level-3-heading'` as component props.
169
168
 
169
+ ### Syntax highlighting
170
+
171
+ `@astrojs/markdoc` provides [Shiki](https://github.com/shikijs/shiki) and [Prism](https://github.com/PrismJS) extensions to highlight your code blocks.
172
+
173
+ #### Shiki
174
+
175
+ Apply the `shiki()` extension to your Markdoc config using the `extends` property. You can optionally pass a shiki configuration object:
176
+
177
+ ```js
178
+ // markdoc.config.mjs
179
+ import { defineMarkdocConfig } from '@astrojs/markdoc/config';
180
+ import shiki from '@astrojs/markdoc/shiki';
181
+
182
+ export default defineMarkdocConfig({
183
+ extends: [
184
+ shiki({
185
+ // Choose from Shiki's built-in themes (or add your own)
186
+ // Default: 'github-dark'
187
+ // https://github.com/shikijs/shiki/blob/main/docs/themes.md
188
+ theme: 'dracula',
189
+ // Enable word wrap to prevent horizontal scrolling
190
+ // Default: false
191
+ wrap: true,
192
+ // Pass custom languages
193
+ // Note: Shiki has countless langs built-in, including `.astro`!
194
+ // https://github.com/shikijs/shiki/blob/main/docs/languages.md
195
+ langs: [],
196
+ })
197
+ ],
198
+ })
199
+ ```
200
+
201
+ #### Prism
202
+
203
+ Apply the `prism()` extension to your Markdoc config using the `extends` property.
204
+
205
+ ```js
206
+ // markdoc.config.mjs
207
+ import { defineMarkdocConfig } from '@astrojs/markdoc/config';
208
+ import prism from '@astrojs/markdoc/prism';
209
+
210
+ export default defineMarkdocConfig({
211
+ extends: [prism()],
212
+ })
213
+ ```
214
+
215
+ 📚 To learn about configuring Prism stylesheets, [see our syntax highlighting guide](https://docs.astro.build/en/guides/markdown-content/#prism-configuration).
216
+
217
+ ### Set the root HTML element
218
+
219
+ Markdoc wraps documents with an `<article>` tag by default. This can be changed from the `document` Markdoc node. This accepts an HTML element name or `null` if you prefer to remove the wrapper element:
220
+
221
+ ```js
222
+ // markdoc.config.mjs
223
+ import { defineMarkdocConfig, nodes } from '@astrojs/markdoc/config';
224
+
225
+ export default defineMarkdocConfig({
226
+ nodes: {
227
+ document: {
228
+ render: null, // default 'article'
229
+ ...nodes.document, // Apply defaults for other options
230
+ },
231
+ },
232
+ })
233
+ ```
234
+
235
+ ### Custom Markdoc nodes / elements
236
+
237
+ You may want to render standard Markdown elements, such as paragraphs and bolded text, as Astro components. For this, you can configure a [Markdoc node][markdoc-nodes]. If a given node receives attributes, they will be available as component props.
238
+
239
+ This example renders blockquotes with a custom `Quote.astro` component:
240
+
241
+ ```js
242
+ // markdoc.config.mjs
243
+ import { defineMarkdocConfig, nodes } from '@astrojs/markdoc/config';
244
+ import Quote from './src/components/Quote.astro';
245
+
246
+ export default defineMarkdocConfig({
247
+ nodes: {
248
+ blockquote: {
249
+ render: Quote,
250
+ // Apply Markdoc's defaults for other options
251
+ ...nodes.blockquote,
252
+ },
253
+ },
254
+ })
255
+ ```
256
+
170
257
  📚 [Find all of Markdoc's built-in nodes and node attributes on their documentation.](https://markdoc.dev/docs/nodes#built-in-nodes)
171
258
 
172
- #### Use client-side UI components
259
+ ### Use client-side UI components
173
260
 
174
- Today, the `components` prop does not support the `client:` directive for hydrating components. To embed client-side components, create a wrapper `.astro` file to import your component and apply a `client:` directive manually.
261
+ Tags and nodes are restricted to `.astro` files. To embed client-side UI components in Markdoc, [use a wrapper `.astro` component that renders a framework component](/en/core-concepts/framework-components/#nesting-framework-components) with your desired `client:` directive.
175
262
 
176
- This example wraps a `Aside.tsx` component with a `ClientAside.astro` wrapper:
263
+ This example wraps a React `Aside.tsx` component with a `ClientAside.astro` component:
177
264
 
178
265
  ```astro
179
266
  ---
@@ -184,17 +271,17 @@ import Aside from './Aside';
184
271
  <Aside {...Astro.props} client:load />
185
272
  ```
186
273
 
187
- This component can be passed to the `render` prop for any [tag][markdoc-tags] or [node][markdoc-nodes] in your config:
274
+ This Astro component can now be passed to the `render` prop for any [tag][markdoc-tags] or [node][markdoc-nodes] in your config:
188
275
 
189
276
  ```js
190
277
  // markdoc.config.mjs
191
278
  import { defineMarkdocConfig } from '@astrojs/markdoc/config';
192
- import Aside from './src/components/Aside.astro';
279
+ import ClientAside from './src/components/ClientAside.astro';
193
280
 
194
281
  export default defineMarkdocConfig({
195
282
  tags: {
196
283
  aside: {
197
- render: Aside,
284
+ render: ClientAside,
198
285
  attributes: {
199
286
  type: { type: String },
200
287
  }
@@ -203,20 +290,6 @@ export default defineMarkdocConfig({
203
290
  })
204
291
  ```
205
292
 
206
- ### Access frontmatter and content collection information from your templates
207
-
208
- You can access content collection information from your Markdoc templates using the `$entry` variable. This includes the entry `slug`, `collection` name, and frontmatter `data` parsed by your content collection schema (if any). This example renders the `title` frontmatter property as a heading:
209
-
210
- ```md
211
- ---
212
- title: Welcome to Markdoc 👋
213
- ---
214
-
215
- # {% $entry.data.title %}
216
- ```
217
-
218
- The `$entry` object matches [the `CollectionEntry` type](https://docs.astro.build/en/reference/api-reference/#collection-entry-type), excluding the `.render()` property.
219
-
220
293
  ### Markdoc config
221
294
 
222
295
  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).
@@ -292,6 +365,23 @@ export default defineMarkdocConfig({
292
365
  })
293
366
  ```
294
367
 
368
+ ### Access frontmatter from your Markdoc content
369
+
370
+ To access frontmatter, you can pass the entry `data` property [as a variable](#pass-markdoc-variables) where you render your content:
371
+
372
+ ```astro
373
+ ---
374
+ import { getEntry } from 'astro:content';
375
+
376
+ const entry = await getEntry('docs', 'why-markdoc');
377
+ const { Content } = await entry.render();
378
+ ---
379
+
380
+ <Content frontmatter={entry.data} />
381
+ ```
382
+
383
+ This can now be accessed as `$frontmatter` in your Markdoc.
384
+
295
385
  ## Examples
296
386
 
297
387
  * The [Astro Markdoc starter template](https://github.com/withastro/astro/tree/latest/examples/with-markdoc) shows how to use Markdoc files in your Astro project.
@@ -2,12 +2,18 @@ import type { AstroInstance } from 'astro';
2
2
  import { Fragment } from 'astro/jsx-runtime';
3
3
  import type { RenderableTreeNode } from '@markdoc/markdoc';
4
4
  import Markdoc from '@markdoc/markdoc';
5
- import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js';
5
+ import {
6
+ createComponent,
7
+ renderComponent,
8
+ render,
9
+ HTMLString,
10
+ isHTMLString,
11
+ } from 'astro/runtime/server/index.js';
6
12
 
7
13
  export type TreeNode =
8
14
  | {
9
15
  type: 'text';
10
- content: string;
16
+ content: string | HTMLString;
11
17
  }
12
18
  | {
13
19
  type: 'component';
@@ -25,6 +31,7 @@ export type TreeNode =
25
31
  export const ComponentNode = createComponent({
26
32
  factory(result: any, { treeNode }: { treeNode: TreeNode }) {
27
33
  if (treeNode.type === 'text') return render`${treeNode.content}`;
34
+
28
35
  const slots = {
29
36
  default: () =>
30
37
  render`${treeNode.children.map((child) =>
@@ -46,7 +53,9 @@ export const ComponentNode = createComponent({
46
53
  });
47
54
 
48
55
  export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]): TreeNode {
49
- if (typeof node === 'string' || typeof node === 'number') {
56
+ if (isHTMLString(node)) {
57
+ return { type: 'text', content: node as HTMLString };
58
+ } else if (typeof node === 'string' || typeof node === 'number') {
50
59
  return { type: 'text', content: String(node) };
51
60
  } else if (Array.isArray(node)) {
52
61
  return {
package/dist/config.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc';
2
2
  import _Markdoc from '@markdoc/markdoc';
3
+ export type AstroMarkdocConfig<C extends Record<string, any> = Record<string, any>> = MarkdocConfig & {
4
+ ctx?: C;
5
+ extends?: ResolvedAstroMarkdocConfig[];
6
+ };
7
+ export type ResolvedAstroMarkdocConfig = Omit<AstroMarkdocConfig, 'extends'>;
3
8
  export declare const Markdoc: typeof _Markdoc;
4
9
  export declare const nodes: {
5
10
  heading: import("@markdoc/markdoc").Schema;
@@ -37,4 +42,4 @@ export declare const nodes: {
37
42
  error: {};
38
43
  node: {};
39
44
  };
40
- export declare function defineMarkdocConfig(config: MarkdocConfig): MarkdocConfig;
45
+ export declare function defineMarkdocConfig(config: AstroMarkdocConfig): AstroMarkdocConfig;
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import _Markdoc from "@markdoc/markdoc";
2
- import { nodes as astroNodes } from "./nodes/index.js";
2
+ import { heading } from "./heading-ids.js";
3
3
  const Markdoc = _Markdoc;
4
- const nodes = { ...Markdoc.nodes, ...astroNodes };
4
+ const nodes = { ...Markdoc.nodes, heading };
5
5
  function defineMarkdocConfig(config) {
6
6
  return config;
7
7
  }
@@ -0,0 +1,2 @@
1
+ import { type AstroMarkdocConfig } from '../config.js';
2
+ export default function prism(): AstroMarkdocConfig;
@@ -0,0 +1,21 @@
1
+ import { unescapeHTML } from "astro/runtime/server/index.js";
2
+ import { runHighlighterWithAstro } from "@astrojs/prism/dist/highlighter";
3
+ import { Markdoc } from "../config.js";
4
+ function prism() {
5
+ return {
6
+ nodes: {
7
+ fence: {
8
+ attributes: Markdoc.nodes.fence.attributes,
9
+ transform({ attributes: { language, content } }) {
10
+ const { html, classLanguage } = runHighlighterWithAstro(language, content);
11
+ return unescapeHTML(
12
+ `<pre class="${classLanguage}"><code class="${classLanguage}">${html}</code></pre>`
13
+ );
14
+ }
15
+ }
16
+ }
17
+ };
18
+ }
19
+ export {
20
+ prism as default
21
+ };
@@ -0,0 +1,3 @@
1
+ import type { ShikiConfig } from 'astro';
2
+ import type { AstroMarkdocConfig } from '../config.js';
3
+ export default function shiki({ langs, theme, wrap, }?: ShikiConfig): Promise<AstroMarkdocConfig>;
@@ -0,0 +1,100 @@
1
+ import { unescapeHTML } from "astro/runtime/server/index.js";
2
+ import Markdoc from "@markdoc/markdoc";
3
+ import { getHighlighter } from "shiki";
4
+ const compatThemes = {
5
+ "material-darker": "material-theme-darker",
6
+ "material-default": "material-theme",
7
+ "material-lighter": "material-theme-lighter",
8
+ "material-ocean": "material-theme-ocean",
9
+ "material-palenight": "material-theme-palenight"
10
+ };
11
+ const normalizeTheme = (theme) => {
12
+ if (typeof theme === "string") {
13
+ return compatThemes[theme] || theme;
14
+ } else if (compatThemes[theme.name]) {
15
+ return { ...theme, name: compatThemes[theme.name] };
16
+ } else {
17
+ return theme;
18
+ }
19
+ };
20
+ const ASTRO_COLOR_REPLACEMENTS = {
21
+ "#000001": "var(--astro-code-color-text)",
22
+ "#000002": "var(--astro-code-color-background)",
23
+ "#000004": "var(--astro-code-token-constant)",
24
+ "#000005": "var(--astro-code-token-string)",
25
+ "#000006": "var(--astro-code-token-comment)",
26
+ "#000007": "var(--astro-code-token-keyword)",
27
+ "#000008": "var(--astro-code-token-parameter)",
28
+ "#000009": "var(--astro-code-token-function)",
29
+ "#000010": "var(--astro-code-token-string-expression)",
30
+ "#000011": "var(--astro-code-token-punctuation)",
31
+ "#000012": "var(--astro-code-token-link)"
32
+ };
33
+ const PRE_SELECTOR = /<pre class="(.*?)shiki(.*?)"/;
34
+ const LINE_SELECTOR = /<span class="line"><span style="(.*?)">([\+|\-])/g;
35
+ const INLINE_STYLE_SELECTOR = /style="(.*?)"/;
36
+ const highlighterCache = /* @__PURE__ */ new Map();
37
+ async function shiki({
38
+ langs = [],
39
+ theme = "github-dark",
40
+ wrap = false
41
+ } = {}) {
42
+ theme = normalizeTheme(theme);
43
+ const cacheID = typeof theme === "string" ? theme : theme.name;
44
+ if (!highlighterCache.has(cacheID)) {
45
+ highlighterCache.set(
46
+ cacheID,
47
+ await getHighlighter({ theme }).then((hl) => {
48
+ hl.setColorReplacements(ASTRO_COLOR_REPLACEMENTS);
49
+ return hl;
50
+ })
51
+ );
52
+ }
53
+ const highlighter = highlighterCache.get(cacheID);
54
+ for (const lang of langs) {
55
+ await highlighter.loadLanguage(lang);
56
+ }
57
+ return {
58
+ nodes: {
59
+ fence: {
60
+ attributes: Markdoc.nodes.fence.attributes,
61
+ transform({ attributes }) {
62
+ let lang;
63
+ if (typeof attributes.language === "string") {
64
+ const langExists = highlighter.getLoadedLanguages().includes(attributes.language);
65
+ if (langExists) {
66
+ lang = attributes.language;
67
+ } else {
68
+ console.warn(
69
+ `[Shiki highlighter] The language "${attributes.language}" doesn't exist, falling back to plaintext.`
70
+ );
71
+ lang = "plaintext";
72
+ }
73
+ } else {
74
+ lang = "plaintext";
75
+ }
76
+ let html = highlighter.codeToHtml(attributes.content, { lang });
77
+ html = html.replace(PRE_SELECTOR, `<pre class="$1astro-code$2"`);
78
+ if (attributes.language === "diff") {
79
+ html = html.replace(
80
+ LINE_SELECTOR,
81
+ '<span class="line"><span style="$1"><span style="user-select: none;">$2</span>'
82
+ );
83
+ }
84
+ if (wrap === false) {
85
+ html = html.replace(INLINE_STYLE_SELECTOR, 'style="$1; overflow-x: auto;"');
86
+ } else if (wrap === true) {
87
+ html = html.replace(
88
+ INLINE_STYLE_SELECTOR,
89
+ 'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
90
+ );
91
+ }
92
+ return unescapeHTML(html);
93
+ }
94
+ }
95
+ }
96
+ };
97
+ }
98
+ export {
99
+ shiki as default
100
+ };
@@ -0,0 +1,9 @@
1
+ import { type Schema } from '@markdoc/markdoc';
2
+ import Slugger from 'github-slugger';
3
+ import type { AstroMarkdocConfig } from './config.js';
4
+ type HeadingIdConfig = AstroMarkdocConfig<{
5
+ headingSlugger: Slugger;
6
+ }>;
7
+ export declare const heading: Schema;
8
+ export declare function setupHeadingConfig(): HeadingIdConfig;
9
+ export {};
@@ -1,6 +1,7 @@
1
1
  import Markdoc from "@markdoc/markdoc";
2
2
  import Slugger from "github-slugger";
3
- import { getTextContent } from "../runtime.js";
3
+ import { getTextContent } from "./runtime.js";
4
+ import { MarkdocError } from "./utils.js";
4
5
  function getSlug(attributes, children, headingSlugger) {
5
6
  if (attributes.id && typeof attributes.id === "string") {
6
7
  return attributes.id;
@@ -18,11 +19,16 @@ const heading = {
18
19
  level: { type: Number, required: true, default: 1 }
19
20
  },
20
21
  transform(node, config) {
21
- var _a, _b;
22
+ var _a, _b, _c;
22
23
  const { level, ...attributes } = node.transformAttributes(config);
23
24
  const children = node.transformChildren(config);
25
+ if (!((_a = config.ctx) == null ? void 0 : _a.headingSlugger)) {
26
+ throw new MarkdocError({
27
+ message: "Unexpected problem adding heading IDs to Markdoc file. Did you modify the `ctx.headingSlugger` property in your Markdoc config?"
28
+ });
29
+ }
24
30
  const slug = getSlug(attributes, children, config.ctx.headingSlugger);
25
- const render = ((_b = (_a = config.nodes) == null ? void 0 : _a.heading) == null ? void 0 : _b.render) ?? `h${level}`;
31
+ const render = ((_c = (_b = config.nodes) == null ? void 0 : _b.heading) == null ? void 0 : _c.render) ?? `h${level}`;
26
32
  const tagProps = (
27
33
  // For components, pass down `level` as a prop,
28
34
  // alongside `__collectHeading` for our `headings` collector.
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from "./utils.js";
5
5
  import { emitESMImage } from "astro/assets";
6
6
  import { bold, red, yellow } from "kleur/colors";
7
+ import path from "node:path";
7
8
  import { loadMarkdocConfig } from "./load-config.js";
8
9
  import { setupConfig } from "./runtime.js";
9
10
  function markdocIntegration(legacyConfig) {
@@ -20,7 +21,18 @@ function markdocIntegration(legacyConfig) {
20
21
  name: "@astrojs/markdoc",
21
22
  hooks: {
22
23
  "astro:config:setup": async (params) => {
23
- const { config: astroConfig, addContentEntryType } = params;
24
+ const {
25
+ config: astroConfig,
26
+ updateConfig,
27
+ addContentEntryType
28
+ } = params;
29
+ updateConfig({
30
+ vite: {
31
+ ssr: {
32
+ external: ["@astrojs/markdoc/prism", "@astrojs/markdoc/shiki"]
33
+ }
34
+ }
35
+ });
24
36
  markdocConfigResult = await loadMarkdocConfig(astroConfig);
25
37
  const userMarkdocConfig = (markdocConfigResult == null ? void 0 : markdocConfigResult.config) ?? {};
26
38
  function getEntryInfo({ fileUrl, contents }) {
@@ -35,10 +47,12 @@ function markdocIntegration(legacyConfig) {
35
47
  addContentEntryType({
36
48
  extensions: [".mdoc"],
37
49
  getEntryInfo,
38
- async getRenderModule({ entry, viteId }) {
50
+ async getRenderModule({ contents, fileUrl, viteId }) {
51
+ const entry = getEntryInfo({ contents, fileUrl });
39
52
  const ast = Markdoc.parse(entry.body);
40
53
  const pluginContext = this;
41
- const markdocConfig = setupConfig(userMarkdocConfig, entry);
54
+ const markdocConfig = await setupConfig(userMarkdocConfig);
55
+ const filePath = fileURLToPath(fileUrl);
42
56
  const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
43
57
  return (
44
58
  // Ignore `variable-undefined` errors.
@@ -48,10 +62,11 @@ function markdocIntegration(legacyConfig) {
48
62
  );
49
63
  });
50
64
  if (validationErrors.length) {
51
- const frontmatterBlockOffset = entry._internal.rawData.split("\n").length + 2;
65
+ const frontmatterBlockOffset = entry.rawData.split("\n").length + 2;
66
+ const rootRelativePath = path.relative(fileURLToPath(astroConfig.root), filePath);
52
67
  throw new MarkdocError({
53
68
  message: [
54
- `**${String(entry.collection)} \u2192 ${String(entry.id)}** contains invalid content:`,
69
+ `**${String(rootRelativePath)}** contains invalid content:`,
55
70
  ...validationErrors.map((e) => `- ${e.error.message}`)
56
71
  ].join("\n"),
57
72
  location: {
@@ -66,12 +81,12 @@ function markdocIntegration(legacyConfig) {
66
81
  await emitOptimizedImages(ast.children, {
67
82
  astroConfig,
68
83
  pluginContext,
69
- filePath: entry._internal.filePath
84
+ filePath
70
85
  });
71
86
  }
72
87
  const res = `import { jsx as h } from 'astro/jsx-runtime';
73
88
  import { Renderer } from '@astrojs/markdoc/components';
74
- import { collectHeadings, setupConfig, Markdoc } from '@astrojs/markdoc/runtime';
89
+ import { collectHeadings, setupConfig, setupConfigSync, Markdoc } from '@astrojs/markdoc/runtime';
75
90
  import * as entry from ${JSON.stringify(viteId + "?astroContentCollectionEntry")};
76
91
  ${markdocConfigResult ? `import _userConfig from ${JSON.stringify(
77
92
  markdocConfigResult.fileUrl.pathname
@@ -89,13 +104,13 @@ export function getHeadings() {
89
104
  instead of the Content component. Would remove double-transform and unlock variable resolution in heading slugs. */
90
105
  ""}
91
106
  const headingConfig = userConfig.nodes?.heading;
92
- const config = setupConfig(headingConfig ? { nodes: { heading: headingConfig } } : {}, entry);
107
+ const config = setupConfigSync(headingConfig ? { nodes: { heading: headingConfig } } : {}, entry);
93
108
  const ast = Markdoc.Ast.fromJSON(stringifiedAst);
94
109
  const content = Markdoc.transform(ast, config);
95
110
  return collectHeadings(Array.isArray(content) ? content : content.children);
96
111
  }
97
112
  export async function Content (props) {
98
- const config = setupConfig({
113
+ const config = await setupConfig({
99
114
  ...userConfig,
100
115
  variables: { ...userConfig.variables, ...props },
101
116
  }, entry);
package/dist/runtime.d.ts CHANGED
@@ -1,13 +1,17 @@
1
1
  import type { MarkdownHeading } from '@astrojs/markdown-remark';
2
- import { type ConfigType as MarkdocConfig, type RenderableTreeNode } from '@markdoc/markdoc';
2
+ import { type RenderableTreeNode } from '@markdoc/markdoc';
3
3
  import type { ContentEntryModule } from 'astro';
4
+ import type { AstroMarkdocConfig } from './config.js';
4
5
  /** Used to call `Markdoc.transform()` and `Markdoc.Ast` in runtime modules */
5
6
  export { default as Markdoc } from '@markdoc/markdoc';
6
7
  /**
7
8
  * Merge user config with default config and set up context (ex. heading ID slugger)
8
- * Called on each file's individual transform
9
+ * Called on each file's individual transform.
10
+ * TODO: virtual module to merge configs per-build instead of per-file?
9
11
  */
10
- export declare function setupConfig(userConfig: MarkdocConfig, entry: ContentEntryModule): MarkdocConfig;
12
+ export declare function setupConfig(userConfig: AstroMarkdocConfig): Promise<Omit<AstroMarkdocConfig, 'extends'>>;
13
+ /** Used for synchronous `getHeadings()` function */
14
+ export declare function setupConfigSync(userConfig: AstroMarkdocConfig, entry: ContentEntryModule): Omit<AstroMarkdocConfig, 'extends'>;
11
15
  /**
12
16
  * Get text content as a string from a Markdoc transform AST
13
17
  */
package/dist/runtime.js CHANGED
@@ -1,9 +1,20 @@
1
1
  import Markdoc from "@markdoc/markdoc";
2
- import { setupHeadingConfig } from "./nodes/index.js";
2
+ import { setupHeadingConfig } from "./heading-ids.js";
3
3
  import { default as default2 } from "@markdoc/markdoc";
4
- function setupConfig(userConfig, entry) {
5
- const defaultConfig = {
6
- // `setupXConfig()` could become a "plugin" convention as well?
4
+ async function setupConfig(userConfig) {
5
+ let defaultConfig = setupHeadingConfig();
6
+ if (userConfig.extends) {
7
+ for (let extension of userConfig.extends) {
8
+ if (extension instanceof Promise) {
9
+ extension = await extension;
10
+ }
11
+ defaultConfig = mergeConfig(defaultConfig, extension);
12
+ }
13
+ }
14
+ return mergeConfig(defaultConfig, userConfig);
15
+ }
16
+ function setupConfigSync(userConfig, entry) {
17
+ let defaultConfig = {
7
18
  ...setupHeadingConfig(),
8
19
  variables: { entry }
9
20
  };
@@ -13,6 +24,10 @@ function mergeConfig(configA, configB) {
13
24
  return {
14
25
  ...configA,
15
26
  ...configB,
27
+ ctx: {
28
+ ...configA.ctx,
29
+ ...configB.ctx
30
+ },
16
31
  tags: {
17
32
  ...configA.tags,
18
33
  ...configB.tags
@@ -73,5 +88,6 @@ export {
73
88
  default2 as Markdoc,
74
89
  collectHeadings,
75
90
  getTextContent,
76
- setupConfig
91
+ setupConfig,
92
+ setupConfigSync
77
93
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/markdoc",
3
- "description": "Add support for Markdoc pages in your Astro site",
4
- "version": "0.2.3",
3
+ "description": "Add support for Markdoc in your Astro site",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -19,6 +19,8 @@
19
19
  "bugs": "https://github.com/withastro/astro/issues",
20
20
  "homepage": "https://docs.astro.build/en/guides/integrations-guide/markdoc/",
21
21
  "exports": {
22
+ "./prism": "./dist/extensions/prism.js",
23
+ "./shiki": "./dist/extensions/shiki.js",
22
24
  ".": "./dist/index.js",
23
25
  "./components": "./components/index.ts",
24
26
  "./runtime": "./dist/runtime.js",
@@ -32,7 +34,9 @@
32
34
  "template"
33
35
  ],
34
36
  "dependencies": {
35
- "@markdoc/markdoc": "^0.2.2",
37
+ "shiki": "^0.14.1",
38
+ "@astrojs/prism": "^2.1.2",
39
+ "@markdoc/markdoc": "^0.3.0",
36
40
  "esbuild": "^0.17.12",
37
41
  "github-slugger": "^2.0.0",
38
42
  "gray-matter": "^4.0.3",
@@ -40,7 +44,7 @@
40
44
  "zod": "^3.17.3"
41
45
  },
42
46
  "peerDependencies": {
43
- "astro": "^2.5.5"
47
+ "astro": "^2.5.6"
44
48
  },
45
49
  "devDependencies": {
46
50
  "@astrojs/markdown-remark": "^2.2.1",
@@ -53,7 +57,7 @@
53
57
  "mocha": "^9.2.2",
54
58
  "rollup": "^3.20.1",
55
59
  "vite": "^4.3.1",
56
- "astro": "2.5.5",
60
+ "astro": "2.5.6",
57
61
  "astro-scripts": "0.0.14"
58
62
  },
59
63
  "engines": {
@@ -1,10 +0,0 @@
1
- import { type ConfigType, type Schema } from '@markdoc/markdoc';
2
- import Slugger from 'github-slugger';
3
- type ConfigTypeWithCtx = ConfigType & {
4
- ctx: {
5
- headingSlugger: Slugger;
6
- };
7
- };
8
- export declare const heading: Schema;
9
- export declare function setupHeadingConfig(): ConfigTypeWithCtx;
10
- export {};
@@ -1,4 +0,0 @@
1
- export { setupHeadingConfig } from './heading.js';
2
- export declare const nodes: {
3
- heading: import("@markdoc/markdoc").Schema;
4
- };
@@ -1,7 +0,0 @@
1
- import { heading } from "./heading.js";
2
- import { setupHeadingConfig } from "./heading.js";
3
- const nodes = { heading };
4
- export {
5
- nodes,
6
- setupHeadingConfig
7
- };