@astrojs/markdoc 0.3.3 → 0.4.1
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 +27 -32
- package/components/TreeNode.ts +4 -1
- package/dist/config.d.ts +9 -2
- package/dist/config.js +14 -0
- package/dist/content-entry-type.d.ts +6 -0
- package/dist/content-entry-type.js +198 -0
- package/dist/index.js +14 -222
- package/dist/load-config.d.ts +1 -0
- package/dist/load-config.js +11 -2
- package/dist/runtime.d.ts +12 -6
- package/dist/runtime.js +54 -8
- package/dist/utils.d.ts +4 -7
- package/dist/utils.js +7 -31
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -42,9 +42,8 @@ npm install @astrojs/markdoc
|
|
|
42
42
|
|
|
43
43
|
Then, apply this integration to your `astro.config.*` file using the `integrations` property:
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```js ins={2} "markdoc()"
|
|
45
|
+
```js ins={3} "markdoc()"
|
|
46
|
+
// astro.config.mjs
|
|
48
47
|
import { defineConfig } from 'astro/config';
|
|
49
48
|
import markdoc from '@astrojs/markdoc';
|
|
50
49
|
|
|
@@ -54,14 +53,15 @@ export default defineConfig({
|
|
|
54
53
|
});
|
|
55
54
|
```
|
|
56
55
|
|
|
57
|
-
|
|
58
56
|
### Editor Integration
|
|
59
57
|
|
|
60
58
|
[VS Code](https://code.visualstudio.com/) supports Markdown by default. However, for Markdoc editor support, you may wish to add the following setting in your VSCode config. This ensures authoring Markdoc files provides a Markdown-like editor experience.
|
|
61
59
|
|
|
62
60
|
```json title=".vscode/settings.json"
|
|
63
|
-
|
|
61
|
+
{
|
|
62
|
+
"files.associations": {
|
|
64
63
|
"*.mdoc": "markdown"
|
|
64
|
+
}
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
@@ -113,16 +113,16 @@ export default defineMarkdocConfig({
|
|
|
113
113
|
aside: {
|
|
114
114
|
render: Aside,
|
|
115
115
|
attributes: {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
// Markdoc requires type defs for each attribute.
|
|
117
|
+
// These should mirror the `Props` type of the component
|
|
118
|
+
// you are rendering.
|
|
119
|
+
// See Markdoc's documentation on defining attributes
|
|
120
|
+
// https://markdoc.dev/docs/attributes#defining-attributes
|
|
121
121
|
type: { type: String },
|
|
122
|
-
}
|
|
122
|
+
},
|
|
123
123
|
},
|
|
124
124
|
},
|
|
125
|
-
})
|
|
125
|
+
});
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
This component can now be used in your Markdoc files with the `{% aside %}` tag. Children will be passed to your component's default slot:
|
|
@@ -132,7 +132,7 @@ This component can now be used in your Markdoc files with the `{% aside %}` tag.
|
|
|
132
132
|
|
|
133
133
|
{% aside type="tip" %}
|
|
134
134
|
|
|
135
|
-
Use tags like this fancy "aside" to add some
|
|
135
|
+
Use tags like this fancy "aside" to add some _flair_ to your docs.
|
|
136
136
|
|
|
137
137
|
{% /aside %}
|
|
138
138
|
```
|
|
@@ -155,7 +155,7 @@ export default defineMarkdocConfig({
|
|
|
155
155
|
render: Heading,
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
|
-
})
|
|
158
|
+
});
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
All Markdown headings will render the `Heading.astro` component and pass the following `attributes` as component props:
|
|
@@ -192,9 +192,9 @@ export default defineMarkdocConfig({
|
|
|
192
192
|
// Note: Shiki has countless langs built-in, including `.astro`!
|
|
193
193
|
// https://github.com/shikijs/shiki/blob/main/docs/languages.md
|
|
194
194
|
langs: [],
|
|
195
|
-
})
|
|
195
|
+
}),
|
|
196
196
|
],
|
|
197
|
-
})
|
|
197
|
+
});
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
#### Prism
|
|
@@ -208,7 +208,7 @@ import prism from '@astrojs/markdoc/prism';
|
|
|
208
208
|
|
|
209
209
|
export default defineMarkdocConfig({
|
|
210
210
|
extends: [prism()],
|
|
211
|
-
})
|
|
211
|
+
});
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
📚 To learn about configuring Prism stylesheets, [see our syntax highlighting guide](https://docs.astro.build/en/guides/markdown-content/#prism-configuration).
|
|
@@ -228,7 +228,7 @@ export default defineMarkdocConfig({
|
|
|
228
228
|
render: null, // default 'article'
|
|
229
229
|
},
|
|
230
230
|
},
|
|
231
|
-
})
|
|
231
|
+
});
|
|
232
232
|
```
|
|
233
233
|
|
|
234
234
|
### Custom Markdoc nodes / elements
|
|
@@ -249,7 +249,7 @@ export default defineMarkdocConfig({
|
|
|
249
249
|
render: Quote,
|
|
250
250
|
},
|
|
251
251
|
},
|
|
252
|
-
})
|
|
252
|
+
});
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
📚 [Find all of Markdoc's built-in nodes and node attributes on their documentation.](https://markdoc.dev/docs/nodes#built-in-nodes)
|
|
@@ -282,10 +282,10 @@ export default defineMarkdocConfig({
|
|
|
282
282
|
render: ClientAside,
|
|
283
283
|
attributes: {
|
|
284
284
|
type: { type: String },
|
|
285
|
-
}
|
|
285
|
+
},
|
|
286
286
|
},
|
|
287
287
|
},
|
|
288
|
-
})
|
|
288
|
+
});
|
|
289
289
|
```
|
|
290
290
|
|
|
291
291
|
### Markdoc config
|
|
@@ -307,12 +307,12 @@ export default defineMarkdocConfig({
|
|
|
307
307
|
japan: '🇯🇵',
|
|
308
308
|
spain: '🇪🇸',
|
|
309
309
|
france: '🇫🇷',
|
|
310
|
-
}
|
|
311
|
-
return countryToEmojiMap[country] ?? '🏳'
|
|
310
|
+
};
|
|
311
|
+
return countryToEmojiMap[country] ?? '🏳';
|
|
312
312
|
},
|
|
313
313
|
},
|
|
314
314
|
},
|
|
315
|
-
})
|
|
315
|
+
});
|
|
316
316
|
```
|
|
317
317
|
|
|
318
318
|
Now, you can call this function from any Markdoc content entry:
|
|
@@ -359,8 +359,8 @@ import { defineMarkdocConfig } from '@astrojs/markdoc/config';
|
|
|
359
359
|
export default defineMarkdocConfig({
|
|
360
360
|
variables: {
|
|
361
361
|
environment: process.env.IS_PROD ? 'prod' : 'dev',
|
|
362
|
-
}
|
|
363
|
-
})
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
364
|
```
|
|
365
365
|
|
|
366
366
|
### Access frontmatter from your Markdoc content
|
|
@@ -382,7 +382,7 @@ This can now be accessed as `$frontmatter` in your Markdoc.
|
|
|
382
382
|
|
|
383
383
|
## Examples
|
|
384
384
|
|
|
385
|
-
|
|
385
|
+
- 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.
|
|
386
386
|
|
|
387
387
|
## Troubleshooting
|
|
388
388
|
|
|
@@ -399,13 +399,8 @@ This package is maintained by Astro's Core team. You're welcome to submit an iss
|
|
|
399
399
|
See [CHANGELOG.md](https://github.com/withastro/astro/tree/main/packages/integrations/markdoc/CHANGELOG.md) for a history of changes to this integration.
|
|
400
400
|
|
|
401
401
|
[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/
|
|
402
|
-
|
|
403
402
|
[astro-components]: https://docs.astro.build/en/core-concepts/astro-components/
|
|
404
|
-
|
|
405
403
|
[astro-content-collections]: https://docs.astro.build/en/guides/content-collections/
|
|
406
|
-
|
|
407
404
|
[markdoc-tags]: https://markdoc.dev/docs/tags
|
|
408
|
-
|
|
409
405
|
[markdoc-nodes]: https://markdoc.dev/docs/nodes
|
|
410
|
-
|
|
411
406
|
[markdoc-variables]: https://markdoc.dev/docs/variables
|
package/components/TreeNode.ts
CHANGED
|
@@ -89,7 +89,10 @@ export const ComponentNode = createComponent({
|
|
|
89
89
|
);
|
|
90
90
|
|
|
91
91
|
// Let the runtime know that this component is being used.
|
|
92
|
-
result.propagators.
|
|
92
|
+
// `result.propagators` has been moved to `result._metadata.propagators`
|
|
93
|
+
// TODO: remove this fallback in the next markdoc integration major
|
|
94
|
+
const propagators = result._metadata.propagators || result.propagators;
|
|
95
|
+
propagators.set(
|
|
93
96
|
{},
|
|
94
97
|
{
|
|
95
98
|
init() {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { Config, ConfigType as MarkdocConfig, MaybePromise, NodeType, Schema } from '@markdoc/markdoc';
|
|
2
2
|
import _Markdoc from '@markdoc/markdoc';
|
|
3
3
|
import type { AstroInstance } from 'astro';
|
|
4
|
-
|
|
4
|
+
import { componentConfigSymbol } from './utils.js';
|
|
5
|
+
export type Render = ComponentConfig | AstroInstance['default'] | string;
|
|
6
|
+
export type ComponentConfig = {
|
|
7
|
+
type: 'package' | 'local';
|
|
8
|
+
path: string;
|
|
9
|
+
namedExport?: string;
|
|
10
|
+
[componentConfigSymbol]: true;
|
|
11
|
+
};
|
|
5
12
|
export type AstroMarkdocConfig<C extends Record<string, any> = Record<string, any>> = Omit<MarkdocConfig, 'tags' | 'nodes'> & Partial<{
|
|
6
13
|
tags: Record<string, Schema<Config, Render>>;
|
|
7
14
|
nodes: Partial<Record<NodeType, Schema<Config, Render>>>;
|
|
@@ -47,4 +54,4 @@ export declare const nodes: {
|
|
|
47
54
|
node: {};
|
|
48
55
|
};
|
|
49
56
|
export declare function defineMarkdocConfig(config: AstroMarkdocConfig): AstroMarkdocConfig;
|
|
50
|
-
export
|
|
57
|
+
export declare function component(pathnameOrPkgName: string, namedExport?: string): ComponentConfig;
|
package/dist/config.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
|
+
import { isRelativePath } from "@astrojs/internal-helpers/path";
|
|
1
2
|
import _Markdoc from "@markdoc/markdoc";
|
|
2
3
|
import { heading } from "./heading-ids.js";
|
|
4
|
+
import { componentConfigSymbol } from "./utils.js";
|
|
3
5
|
const Markdoc = _Markdoc;
|
|
4
6
|
const nodes = { ...Markdoc.nodes, heading };
|
|
5
7
|
function defineMarkdocConfig(config) {
|
|
6
8
|
return config;
|
|
7
9
|
}
|
|
10
|
+
function component(pathnameOrPkgName, namedExport) {
|
|
11
|
+
return {
|
|
12
|
+
type: isNpmPackageName(pathnameOrPkgName) ? "package" : "local",
|
|
13
|
+
path: pathnameOrPkgName,
|
|
14
|
+
namedExport,
|
|
15
|
+
[componentConfigSymbol]: true
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function isNpmPackageName(pathname) {
|
|
19
|
+
return !isRelativePath(pathname) && !pathname.startsWith("/");
|
|
20
|
+
}
|
|
8
21
|
export {
|
|
9
22
|
Markdoc,
|
|
23
|
+
component,
|
|
10
24
|
defineMarkdocConfig,
|
|
11
25
|
nodes
|
|
12
26
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AstroConfig, ContentEntryType } from 'astro';
|
|
2
|
+
import type { MarkdocConfigResult } from './load-config.js';
|
|
3
|
+
export declare function getContentEntryType({ markdocConfigResult, astroConfig, }: {
|
|
4
|
+
astroConfig: AstroConfig;
|
|
5
|
+
markdocConfigResult?: MarkdocConfigResult;
|
|
6
|
+
}): Promise<ContentEntryType>;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import Markdoc from "@markdoc/markdoc";
|
|
2
|
+
import matter from "gray-matter";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { isComponentConfig, isValidUrl, MarkdocError, prependForwardSlash } from "./utils.js";
|
|
6
|
+
import { emitESMImage } from "astro/assets";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { setupConfig } from "./runtime.js";
|
|
9
|
+
async function getContentEntryType({
|
|
10
|
+
markdocConfigResult,
|
|
11
|
+
astroConfig
|
|
12
|
+
}) {
|
|
13
|
+
return {
|
|
14
|
+
extensions: [".mdoc"],
|
|
15
|
+
getEntryInfo,
|
|
16
|
+
handlePropagation: true,
|
|
17
|
+
async getRenderModule({ contents, fileUrl, viteId }) {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
const entry = getEntryInfo({ contents, fileUrl });
|
|
20
|
+
const tokens = markdocTokenizer.tokenize(entry.body);
|
|
21
|
+
const ast = Markdoc.parse(tokens);
|
|
22
|
+
const usedTags = getUsedTags(ast);
|
|
23
|
+
const userMarkdocConfig = (markdocConfigResult == null ? void 0 : markdocConfigResult.config) ?? {};
|
|
24
|
+
const markdocConfigUrl = markdocConfigResult == null ? void 0 : markdocConfigResult.fileUrl;
|
|
25
|
+
let componentConfigByTagMap = {};
|
|
26
|
+
for (const tag of usedTags) {
|
|
27
|
+
const render = (_b = (_a = userMarkdocConfig.tags) == null ? void 0 : _a[tag]) == null ? void 0 : _b.render;
|
|
28
|
+
if (isComponentConfig(render)) {
|
|
29
|
+
componentConfigByTagMap[tag] = render;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let componentConfigByNodeMap = {};
|
|
33
|
+
for (const [nodeType, schema] of Object.entries(userMarkdocConfig.nodes ?? {})) {
|
|
34
|
+
const render = schema == null ? void 0 : schema.render;
|
|
35
|
+
if (isComponentConfig(render)) {
|
|
36
|
+
componentConfigByNodeMap[nodeType] = render;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const pluginContext = this;
|
|
40
|
+
const markdocConfig = await setupConfig(userMarkdocConfig);
|
|
41
|
+
const filePath = fileURLToPath(fileUrl);
|
|
42
|
+
const validationErrors = Markdoc.validate(
|
|
43
|
+
ast,
|
|
44
|
+
/* Raised generics issue with Markdoc core https://github.com/markdoc/markdoc/discussions/400 */
|
|
45
|
+
markdocConfig
|
|
46
|
+
).filter((e) => {
|
|
47
|
+
return (
|
|
48
|
+
// Ignore `variable-undefined` errors.
|
|
49
|
+
// Variables can be configured at runtime,
|
|
50
|
+
// so we cannot validate them at build time.
|
|
51
|
+
e.error.id !== "variable-undefined" && (e.error.level === "error" || e.error.level === "critical")
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
if (validationErrors.length) {
|
|
55
|
+
const frontmatterBlockOffset = entry.rawData.split("\n").length + 2;
|
|
56
|
+
const rootRelativePath = path.relative(fileURLToPath(astroConfig.root), filePath);
|
|
57
|
+
throw new MarkdocError({
|
|
58
|
+
message: [
|
|
59
|
+
`**${String(rootRelativePath)}** contains invalid content:`,
|
|
60
|
+
...validationErrors.map((e) => `- ${e.error.message}`)
|
|
61
|
+
].join("\n"),
|
|
62
|
+
location: {
|
|
63
|
+
// Error overlay does not support multi-line or ranges.
|
|
64
|
+
// Just point to the first line.
|
|
65
|
+
line: frontmatterBlockOffset + validationErrors[0].lines[0],
|
|
66
|
+
file: viteId
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (astroConfig.experimental.assets) {
|
|
71
|
+
await emitOptimizedImages(ast.children, {
|
|
72
|
+
astroConfig,
|
|
73
|
+
pluginContext,
|
|
74
|
+
filePath
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const res = `import { Renderer } from '@astrojs/markdoc/components';
|
|
78
|
+
import { createGetHeadings, createContentComponent } from '@astrojs/markdoc/runtime';
|
|
79
|
+
${markdocConfigUrl ? `import markdocConfig from ${JSON.stringify(markdocConfigUrl.pathname)};` : "const markdocConfig = {};"}${astroConfig.experimental.assets ? `
|
|
80
|
+
import { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';
|
|
81
|
+
markdocConfig.nodes = { ...experimentalAssetsConfig.nodes, ...markdocConfig.nodes };` : ""}
|
|
82
|
+
|
|
83
|
+
${getStringifiedImports(componentConfigByTagMap, "Tag", astroConfig.root)}
|
|
84
|
+
${getStringifiedImports(componentConfigByNodeMap, "Node", astroConfig.root)}
|
|
85
|
+
|
|
86
|
+
const tagComponentMap = ${getStringifiedMap(componentConfigByTagMap, "Tag")};
|
|
87
|
+
const nodeComponentMap = ${getStringifiedMap(componentConfigByNodeMap, "Node")};
|
|
88
|
+
|
|
89
|
+
const stringifiedAst = ${JSON.stringify(
|
|
90
|
+
/* Double stringify to encode *as* stringified JSON */
|
|
91
|
+
JSON.stringify(ast)
|
|
92
|
+
)};
|
|
93
|
+
|
|
94
|
+
export const getHeadings = createGetHeadings(stringifiedAst, markdocConfig);
|
|
95
|
+
export const Content = createContentComponent(
|
|
96
|
+
Renderer,
|
|
97
|
+
stringifiedAst,
|
|
98
|
+
markdocConfig,
|
|
99
|
+
tagComponentMap,
|
|
100
|
+
nodeComponentMap,
|
|
101
|
+
)`;
|
|
102
|
+
return { code: res };
|
|
103
|
+
},
|
|
104
|
+
contentModuleTypes: await fs.promises.readFile(
|
|
105
|
+
new URL("../template/content-module-types.d.ts", import.meta.url),
|
|
106
|
+
"utf-8"
|
|
107
|
+
)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const markdocTokenizer = new Markdoc.Tokenizer({
|
|
111
|
+
// Strip <!-- comments --> from rendered output
|
|
112
|
+
// Without this, they're rendered as strings!
|
|
113
|
+
allowComments: true
|
|
114
|
+
});
|
|
115
|
+
function getUsedTags(markdocAst) {
|
|
116
|
+
const tags = /* @__PURE__ */ new Set();
|
|
117
|
+
const validationErrors = Markdoc.validate(markdocAst);
|
|
118
|
+
for (const { error } of validationErrors) {
|
|
119
|
+
if (error.id === "tag-undefined") {
|
|
120
|
+
const [, tagName] = error.message.match(/Undefined tag: '(.*)'/) ?? [];
|
|
121
|
+
tags.add(tagName);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return tags;
|
|
125
|
+
}
|
|
126
|
+
function getEntryInfo({ fileUrl, contents }) {
|
|
127
|
+
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
128
|
+
return {
|
|
129
|
+
data: parsed.data,
|
|
130
|
+
body: parsed.content,
|
|
131
|
+
slug: parsed.data.slug,
|
|
132
|
+
rawData: parsed.matter
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function emitOptimizedImages(nodeChildren, ctx) {
|
|
136
|
+
for (const node of nodeChildren) {
|
|
137
|
+
if (node.type === "image" && typeof node.attributes.src === "string" && shouldOptimizeImage(node.attributes.src)) {
|
|
138
|
+
const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath);
|
|
139
|
+
if ((resolved == null ? void 0 : resolved.id) && fs.existsSync(new URL(prependForwardSlash(resolved.id), "file://"))) {
|
|
140
|
+
const src = await emitESMImage(
|
|
141
|
+
resolved.id,
|
|
142
|
+
ctx.pluginContext.meta.watchMode,
|
|
143
|
+
ctx.pluginContext.emitFile,
|
|
144
|
+
{ config: ctx.astroConfig }
|
|
145
|
+
);
|
|
146
|
+
node.attributes.__optimizedSrc = src;
|
|
147
|
+
} else {
|
|
148
|
+
throw new MarkdocError({
|
|
149
|
+
message: `Could not resolve image ${JSON.stringify(
|
|
150
|
+
node.attributes.src
|
|
151
|
+
)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
await emitOptimizedImages(node.children, ctx);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function shouldOptimizeImage(src) {
|
|
159
|
+
return !isValidUrl(src) && !src.startsWith("/");
|
|
160
|
+
}
|
|
161
|
+
function getStringifiedImports(componentConfigMap, componentNamePrefix, root) {
|
|
162
|
+
let stringifiedComponentImports = "";
|
|
163
|
+
for (const [key, config] of Object.entries(componentConfigMap)) {
|
|
164
|
+
const importName = config.namedExport ? `{ ${config.namedExport} as ${componentNamePrefix + key} }` : componentNamePrefix + key;
|
|
165
|
+
const resolvedPath = config.type === "local" ? new URL(config.path, root).pathname : config.path;
|
|
166
|
+
stringifiedComponentImports += `import ${importName} from ${JSON.stringify(resolvedPath)};
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
return stringifiedComponentImports;
|
|
170
|
+
}
|
|
171
|
+
function getStringifiedMap(componentConfigMap, componentNamePrefix) {
|
|
172
|
+
let stringifiedComponentMap = "{";
|
|
173
|
+
for (const key in componentConfigMap) {
|
|
174
|
+
stringifiedComponentMap += `${key}: ${componentNamePrefix + key},
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
stringifiedComponentMap += "}";
|
|
178
|
+
return stringifiedComponentMap;
|
|
179
|
+
}
|
|
180
|
+
function parseFrontmatter(fileContents, filePath) {
|
|
181
|
+
try {
|
|
182
|
+
matter.clearCache();
|
|
183
|
+
return matter(fileContents);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
if (e.name === "YAMLException") {
|
|
186
|
+
const err = e;
|
|
187
|
+
err.id = filePath;
|
|
188
|
+
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
189
|
+
err.message = e.reason;
|
|
190
|
+
throw err;
|
|
191
|
+
} else {
|
|
192
|
+
throw e;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export {
|
|
197
|
+
getContentEntryType
|
|
198
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
1
|
+
import { bold, red } from "kleur/colors";
|
|
2
|
+
import { getContentEntryType } from "./content-entry-type.js";
|
|
5
3
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
parseFrontmatter,
|
|
10
|
-
prependForwardSlash,
|
|
11
|
-
PROPAGATED_ASSET_FLAG
|
|
12
|
-
} from "./utils.js";
|
|
13
|
-
import { emitESMImage } from "astro/assets";
|
|
14
|
-
import { bold, red, yellow } from "kleur/colors";
|
|
15
|
-
import path from "node:path";
|
|
16
|
-
import { normalizePath } from "vite";
|
|
17
|
-
import { loadMarkdocConfig } from "./load-config.js";
|
|
18
|
-
import { setupConfig } from "./runtime.js";
|
|
19
|
-
const markdocTokenizer = new Markdoc.Tokenizer({
|
|
20
|
-
// Strip <!-- comments --> from rendered output
|
|
21
|
-
// Without this, they're rendered as strings!
|
|
22
|
-
allowComments: true
|
|
23
|
-
});
|
|
4
|
+
loadMarkdocConfig,
|
|
5
|
+
SUPPORTED_MARKDOC_CONFIG_FILES
|
|
6
|
+
} from "./load-config.js";
|
|
24
7
|
function markdocIntegration(legacyConfig) {
|
|
25
8
|
if (legacyConfig) {
|
|
26
9
|
console.log(
|
|
@@ -31,224 +14,33 @@ function markdocIntegration(legacyConfig) {
|
|
|
31
14
|
process.exit(0);
|
|
32
15
|
}
|
|
33
16
|
let markdocConfigResult;
|
|
34
|
-
let
|
|
17
|
+
let astroConfig;
|
|
35
18
|
return {
|
|
36
19
|
name: "@astrojs/markdoc",
|
|
37
20
|
hooks: {
|
|
38
21
|
"astro:config:setup": async (params) => {
|
|
39
|
-
const {
|
|
40
|
-
|
|
41
|
-
updateConfig,
|
|
42
|
-
addContentEntryType
|
|
43
|
-
} = params;
|
|
22
|
+
const { updateConfig, addContentEntryType } = params;
|
|
23
|
+
astroConfig = params.config;
|
|
44
24
|
markdocConfigResult = await loadMarkdocConfig(astroConfig);
|
|
45
|
-
|
|
46
|
-
markdocConfigResultId = normalizePath(fileURLToPath(markdocConfigResult.fileUrl));
|
|
47
|
-
}
|
|
48
|
-
const userMarkdocConfig = (markdocConfigResult == null ? void 0 : markdocConfigResult.config) ?? {};
|
|
49
|
-
function getEntryInfo({ fileUrl, contents }) {
|
|
50
|
-
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
51
|
-
return {
|
|
52
|
-
data: parsed.data,
|
|
53
|
-
body: parsed.content,
|
|
54
|
-
slug: parsed.data.slug,
|
|
55
|
-
rawData: parsed.matter
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
addContentEntryType({
|
|
59
|
-
extensions: [".mdoc"],
|
|
60
|
-
getEntryInfo,
|
|
61
|
-
// Markdoc handles script / style propagation
|
|
62
|
-
// for Astro components internally
|
|
63
|
-
handlePropagation: false,
|
|
64
|
-
async getRenderModule({ contents, fileUrl, viteId }) {
|
|
65
|
-
const entry = getEntryInfo({ contents, fileUrl });
|
|
66
|
-
const tokens = markdocTokenizer.tokenize(entry.body);
|
|
67
|
-
const ast = Markdoc.parse(tokens);
|
|
68
|
-
const pluginContext = this;
|
|
69
|
-
const markdocConfig = await setupConfig(userMarkdocConfig);
|
|
70
|
-
const filePath = fileURLToPath(fileUrl);
|
|
71
|
-
const validationErrors = Markdoc.validate(
|
|
72
|
-
ast,
|
|
73
|
-
/* Raised generics issue with Markdoc core https://github.com/markdoc/markdoc/discussions/400 */
|
|
74
|
-
markdocConfig
|
|
75
|
-
).filter((e) => {
|
|
76
|
-
return (
|
|
77
|
-
// Ignore `variable-undefined` errors.
|
|
78
|
-
// Variables can be configured at runtime,
|
|
79
|
-
// so we cannot validate them at build time.
|
|
80
|
-
e.error.id !== "variable-undefined" && (e.error.level === "error" || e.error.level === "critical")
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
if (validationErrors.length) {
|
|
84
|
-
const frontmatterBlockOffset = entry.rawData.split("\n").length + 2;
|
|
85
|
-
const rootRelativePath = path.relative(fileURLToPath(astroConfig.root), filePath);
|
|
86
|
-
throw new MarkdocError({
|
|
87
|
-
message: [
|
|
88
|
-
`**${String(rootRelativePath)}** contains invalid content:`,
|
|
89
|
-
...validationErrors.map((e) => `- ${e.error.message}`)
|
|
90
|
-
].join("\n"),
|
|
91
|
-
location: {
|
|
92
|
-
// Error overlay does not support multi-line or ranges.
|
|
93
|
-
// Just point to the first line.
|
|
94
|
-
line: frontmatterBlockOffset + validationErrors[0].lines[0],
|
|
95
|
-
file: viteId
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
if (astroConfig.experimental.assets) {
|
|
100
|
-
await emitOptimizedImages(ast.children, {
|
|
101
|
-
astroConfig,
|
|
102
|
-
pluginContext,
|
|
103
|
-
filePath
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
const res = `import {
|
|
107
|
-
createComponent,
|
|
108
|
-
renderComponent,
|
|
109
|
-
} from 'astro/runtime/server/index.js';
|
|
110
|
-
import { Renderer } from '@astrojs/markdoc/components';
|
|
111
|
-
import { collectHeadings, setupConfig, setupConfigSync, Markdoc } from '@astrojs/markdoc/runtime';
|
|
112
|
-
${markdocConfigResult ? `import _userConfig from ${JSON.stringify(
|
|
113
|
-
markdocConfigResultId
|
|
114
|
-
)};
|
|
115
|
-
const userConfig = _userConfig ?? {};` : "const userConfig = {};"}${astroConfig.experimental.assets ? `
|
|
116
|
-
import { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';
|
|
117
|
-
userConfig.nodes = { ...experimentalAssetsConfig.nodes, ...userConfig.nodes };` : ""}
|
|
118
|
-
const stringifiedAst = ${JSON.stringify(
|
|
119
|
-
/* Double stringify to encode *as* stringified JSON */
|
|
120
|
-
JSON.stringify(ast)
|
|
121
|
-
)};
|
|
122
|
-
export function getHeadings() {
|
|
123
|
-
${/* Yes, we are transforming twice (once from `getHeadings()` and again from <Content /> in case of variables).
|
|
124
|
-
TODO: propose new `render()` API to allow Markdoc variable passing to `render()` itself,
|
|
125
|
-
instead of the Content component. Would remove double-transform and unlock variable resolution in heading slugs. */
|
|
126
|
-
""}
|
|
127
|
-
const headingConfig = userConfig.nodes?.heading;
|
|
128
|
-
const config = setupConfigSync(headingConfig ? { nodes: { heading: headingConfig } } : {});
|
|
129
|
-
const ast = Markdoc.Ast.fromJSON(stringifiedAst);
|
|
130
|
-
const content = Markdoc.transform(ast, config);
|
|
131
|
-
return collectHeadings(Array.isArray(content) ? content : content.children);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export const Content = createComponent({
|
|
135
|
-
async factory(result, props) {
|
|
136
|
-
const config = await setupConfig({
|
|
137
|
-
...userConfig,
|
|
138
|
-
variables: { ...userConfig.variables, ...props },
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
return renderComponent(
|
|
142
|
-
result,
|
|
143
|
-
Renderer.name,
|
|
144
|
-
Renderer,
|
|
145
|
-
{ stringifiedAst, config },
|
|
146
|
-
{}
|
|
147
|
-
);
|
|
148
|
-
},
|
|
149
|
-
propagation: 'self',
|
|
150
|
-
});`;
|
|
151
|
-
return { code: res };
|
|
152
|
-
},
|
|
153
|
-
contentModuleTypes: await fs.promises.readFile(
|
|
154
|
-
new URL("../template/content-module-types.d.ts", import.meta.url),
|
|
155
|
-
"utf-8"
|
|
156
|
-
)
|
|
157
|
-
});
|
|
158
|
-
let rollupOptions = {};
|
|
159
|
-
if (markdocConfigResult) {
|
|
160
|
-
rollupOptions = {
|
|
161
|
-
output: {
|
|
162
|
-
// Split Astro components from your `markdoc.config`
|
|
163
|
-
// to only inject component styles and scripts at runtime.
|
|
164
|
-
manualChunks(id, { getModuleInfo }) {
|
|
165
|
-
var _a, _b;
|
|
166
|
-
if (markdocConfigResult && hasContentFlag(id, PROPAGATED_ASSET_FLAG) && ((_b = (_a = getModuleInfo(id)) == null ? void 0 : _a.importers) == null ? void 0 : _b.includes(markdocConfigResultId))) {
|
|
167
|
-
return createNameHash(id, [id]);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
}
|
|
25
|
+
addContentEntryType(await getContentEntryType({ markdocConfigResult, astroConfig }));
|
|
173
26
|
updateConfig({
|
|
174
27
|
vite: {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
build: {
|
|
181
|
-
rollupOptions
|
|
182
|
-
},
|
|
183
|
-
plugins: [
|
|
184
|
-
{
|
|
185
|
-
name: "@astrojs/markdoc:astro-propagated-assets",
|
|
186
|
-
enforce: "pre",
|
|
187
|
-
// Astro component styles and scripts should only be injected
|
|
188
|
-
// When a given Markdoc file actually uses that component.
|
|
189
|
-
// Add the `astroPropagatedAssets` flag to inject only when rendered.
|
|
190
|
-
resolveId(id, importer) {
|
|
191
|
-
if (importer === markdocConfigResultId && id.endsWith(".astro")) {
|
|
192
|
-
return this.resolve(id + "?astroPropagatedAssets", importer, {
|
|
193
|
-
skipSelf: true
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
]
|
|
28
|
+
ssr: {
|
|
29
|
+
external: ["@astrojs/markdoc/prism", "@astrojs/markdoc/shiki"]
|
|
30
|
+
}
|
|
199
31
|
}
|
|
200
32
|
});
|
|
201
33
|
},
|
|
202
34
|
"astro:server:setup": async ({ server }) => {
|
|
203
35
|
server.watcher.on("all", (event, entry) => {
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
yellow(
|
|
207
|
-
`${bold("[Markdoc]")} Restart the dev server for config changes to take effect.`
|
|
208
|
-
)
|
|
209
|
-
);
|
|
36
|
+
if (SUPPORTED_MARKDOC_CONFIG_FILES.some((f) => entry.endsWith(f))) {
|
|
37
|
+
server.restart();
|
|
210
38
|
}
|
|
211
39
|
});
|
|
212
40
|
}
|
|
213
41
|
}
|
|
214
42
|
};
|
|
215
43
|
}
|
|
216
|
-
async function emitOptimizedImages(nodeChildren, ctx) {
|
|
217
|
-
for (const node of nodeChildren) {
|
|
218
|
-
if (node.type === "image" && typeof node.attributes.src === "string" && shouldOptimizeImage(node.attributes.src)) {
|
|
219
|
-
const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath);
|
|
220
|
-
if ((resolved == null ? void 0 : resolved.id) && fs.existsSync(new URL(prependForwardSlash(resolved.id), "file://"))) {
|
|
221
|
-
const src = await emitESMImage(
|
|
222
|
-
resolved.id,
|
|
223
|
-
ctx.pluginContext.meta.watchMode,
|
|
224
|
-
ctx.pluginContext.emitFile,
|
|
225
|
-
{ config: ctx.astroConfig }
|
|
226
|
-
);
|
|
227
|
-
node.attributes.__optimizedSrc = src;
|
|
228
|
-
} else {
|
|
229
|
-
throw new MarkdocError({
|
|
230
|
-
message: `Could not resolve image ${JSON.stringify(
|
|
231
|
-
node.attributes.src
|
|
232
|
-
)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
await emitOptimizedImages(node.children, ctx);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function shouldOptimizeImage(src) {
|
|
240
|
-
return !isValidUrl(src) && !src.startsWith("/");
|
|
241
|
-
}
|
|
242
|
-
function createNameHash(baseId, hashIds) {
|
|
243
|
-
const baseName = baseId ? path.parse(baseId).name : "index";
|
|
244
|
-
const hash = crypto.createHash("sha256");
|
|
245
|
-
for (const id of hashIds) {
|
|
246
|
-
hash.update(id, "utf-8");
|
|
247
|
-
}
|
|
248
|
-
const h = hash.digest("hex").slice(0, 8);
|
|
249
|
-
const proposedName = baseName + "." + h;
|
|
250
|
-
return proposedName;
|
|
251
|
-
}
|
|
252
44
|
export {
|
|
253
45
|
markdocIntegration as default
|
|
254
46
|
};
|
package/dist/load-config.d.ts
CHANGED
package/dist/load-config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { build as esbuild } from "esbuild";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { MarkdocError } from "./utils.js";
|
|
4
5
|
const SUPPORTED_MARKDOC_CONFIG_FILES = [
|
|
5
6
|
"markdoc.config.js",
|
|
6
7
|
"markdoc.config.mjs",
|
|
@@ -18,7 +19,7 @@ async function loadMarkdocConfig(astroConfig) {
|
|
|
18
19
|
}
|
|
19
20
|
if (!markdocConfigUrl)
|
|
20
21
|
return;
|
|
21
|
-
const { code
|
|
22
|
+
const { code } = await bundleConfigFile({
|
|
22
23
|
markdocConfigUrl,
|
|
23
24
|
astroConfig
|
|
24
25
|
});
|
|
@@ -32,6 +33,7 @@ async function bundleConfigFile({
|
|
|
32
33
|
markdocConfigUrl,
|
|
33
34
|
astroConfig
|
|
34
35
|
}) {
|
|
36
|
+
let markdocError;
|
|
35
37
|
const result = await esbuild({
|
|
36
38
|
absWorkingDir: fileURLToPath(astroConfig.root),
|
|
37
39
|
entryPoints: [fileURLToPath(markdocConfigUrl)],
|
|
@@ -49,8 +51,12 @@ async function bundleConfigFile({
|
|
|
49
51
|
name: "stub-astro-imports",
|
|
50
52
|
setup(build) {
|
|
51
53
|
build.onResolve({ filter: /.*\.astro$/ }, () => {
|
|
54
|
+
markdocError = new MarkdocError({
|
|
55
|
+
message: "`.astro` files are no longer supported in the Markdoc config.",
|
|
56
|
+
hint: "Use the `component()` utility to specify a component path instead."
|
|
57
|
+
});
|
|
52
58
|
return {
|
|
53
|
-
// Stub with an unused default export
|
|
59
|
+
// Stub with an unused default export.
|
|
54
60
|
path: "data:text/javascript,export default true",
|
|
55
61
|
external: true
|
|
56
62
|
};
|
|
@@ -59,6 +65,8 @@ async function bundleConfigFile({
|
|
|
59
65
|
}
|
|
60
66
|
]
|
|
61
67
|
});
|
|
68
|
+
if (markdocError)
|
|
69
|
+
throw markdocError;
|
|
62
70
|
const { text } = result.outputFiles[0];
|
|
63
71
|
return {
|
|
64
72
|
code: text,
|
|
@@ -78,5 +86,6 @@ async function loadConfigFromBundledFile(root, code) {
|
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
export {
|
|
89
|
+
SUPPORTED_MARKDOC_CONFIG_FILES,
|
|
81
90
|
loadMarkdocConfig
|
|
82
91
|
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { MarkdownHeading } from '@astrojs/markdown-remark';
|
|
2
|
-
import { type RenderableTreeNode } from '@markdoc/markdoc';
|
|
2
|
+
import { type NodeType, type RenderableTreeNode } from '@markdoc/markdoc';
|
|
3
|
+
import type { AstroInstance } from 'astro';
|
|
3
4
|
import type { AstroMarkdocConfig } from './config.js';
|
|
4
|
-
/** Used to call `Markdoc.transform()` and `Markdoc.Ast` in runtime modules */
|
|
5
|
-
export { default as Markdoc } from '@markdoc/markdoc';
|
|
6
5
|
/**
|
|
7
6
|
* Merge user config with default config and set up context (ex. heading ID slugger)
|
|
8
7
|
* Called on each file's individual transform.
|
|
9
8
|
* TODO: virtual module to merge configs per-build instead of per-file?
|
|
10
9
|
*/
|
|
11
|
-
export declare function setupConfig(userConfig
|
|
10
|
+
export declare function setupConfig(userConfig?: AstroMarkdocConfig): Promise<MergedConfig>;
|
|
12
11
|
/** Used for synchronous `getHeadings()` function */
|
|
13
|
-
export declare function setupConfigSync(userConfig
|
|
12
|
+
export declare function setupConfigSync(userConfig?: AstroMarkdocConfig): MergedConfig;
|
|
13
|
+
type MergedConfig = Required<Omit<AstroMarkdocConfig, 'extends'>>;
|
|
14
|
+
/** Merge function from `@markdoc/markdoc` internals */
|
|
15
|
+
export declare function mergeConfig(configA: AstroMarkdocConfig, configB: AstroMarkdocConfig): MergedConfig;
|
|
16
|
+
export declare function resolveComponentImports(markdocConfig: Required<Pick<AstroMarkdocConfig, 'tags' | 'nodes'>>, tagComponentMap: Record<string, AstroInstance['default']>, nodeComponentMap: Record<NodeType, AstroInstance['default']>): Required<Pick<AstroMarkdocConfig, "tags" | "nodes">>;
|
|
14
17
|
/**
|
|
15
18
|
* Get text content as a string from a Markdoc transform AST
|
|
16
19
|
*/
|
|
@@ -19,4 +22,7 @@ export declare function getTextContent(childNodes: RenderableTreeNode[]): string
|
|
|
19
22
|
* Collect headings from Markdoc transform AST
|
|
20
23
|
* for `headings` result on `render()` return value
|
|
21
24
|
*/
|
|
22
|
-
export declare function collectHeadings(children: RenderableTreeNode[]
|
|
25
|
+
export declare function collectHeadings(children: RenderableTreeNode[], collectedHeadings: MarkdownHeading[]): void;
|
|
26
|
+
export declare function createGetHeadings(stringifiedAst: string, userConfig: AstroMarkdocConfig): () => MarkdownHeading[];
|
|
27
|
+
export declare function createContentComponent(Renderer: AstroInstance['default'], stringifiedAst: string, userConfig: AstroMarkdocConfig, tagComponentMap: Record<string, AstroInstance['default']>, nodeComponentMap: Record<NodeType, AstroInstance['default']>): any;
|
|
28
|
+
export {};
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Markdoc from "@markdoc/markdoc";
|
|
2
|
+
import { createComponent, renderComponent } from "astro/runtime/server/index.js";
|
|
2
3
|
import { setupHeadingConfig } from "./heading-ids.js";
|
|
3
|
-
|
|
4
|
-
async function setupConfig(userConfig) {
|
|
4
|
+
async function setupConfig(userConfig = {}) {
|
|
5
5
|
let defaultConfig = setupHeadingConfig();
|
|
6
6
|
if (userConfig.extends) {
|
|
7
7
|
for (let extension of userConfig.extends) {
|
|
@@ -13,7 +13,7 @@ async function setupConfig(userConfig) {
|
|
|
13
13
|
}
|
|
14
14
|
return mergeConfig(defaultConfig, userConfig);
|
|
15
15
|
}
|
|
16
|
-
function setupConfigSync(userConfig) {
|
|
16
|
+
function setupConfigSync(userConfig = {}) {
|
|
17
17
|
const defaultConfig = setupHeadingConfig();
|
|
18
18
|
return mergeConfig(defaultConfig, userConfig);
|
|
19
19
|
}
|
|
@@ -40,9 +40,30 @@ function mergeConfig(configA, configB) {
|
|
|
40
40
|
variables: {
|
|
41
41
|
...configA.variables,
|
|
42
42
|
...configB.variables
|
|
43
|
+
},
|
|
44
|
+
partials: {
|
|
45
|
+
...configA.partials,
|
|
46
|
+
...configB.partials
|
|
47
|
+
},
|
|
48
|
+
validation: {
|
|
49
|
+
...configA.validation,
|
|
50
|
+
...configB.validation
|
|
43
51
|
}
|
|
44
52
|
};
|
|
45
53
|
}
|
|
54
|
+
function resolveComponentImports(markdocConfig, tagComponentMap, nodeComponentMap) {
|
|
55
|
+
for (const [tag, render] of Object.entries(tagComponentMap)) {
|
|
56
|
+
const config = markdocConfig.tags[tag];
|
|
57
|
+
if (config)
|
|
58
|
+
config.render = render;
|
|
59
|
+
}
|
|
60
|
+
for (const [node, render] of Object.entries(nodeComponentMap)) {
|
|
61
|
+
const config = markdocConfig.nodes[node];
|
|
62
|
+
if (config)
|
|
63
|
+
config.render = render;
|
|
64
|
+
}
|
|
65
|
+
return markdocConfig;
|
|
66
|
+
}
|
|
46
67
|
function getTextContent(childNodes) {
|
|
47
68
|
let text = "";
|
|
48
69
|
for (const node of childNodes) {
|
|
@@ -55,8 +76,7 @@ function getTextContent(childNodes) {
|
|
|
55
76
|
return text;
|
|
56
77
|
}
|
|
57
78
|
const headingLevels = [1, 2, 3, 4, 5, 6];
|
|
58
|
-
function collectHeadings(children) {
|
|
59
|
-
let collectedHeadings = [];
|
|
79
|
+
function collectHeadings(children, collectedHeadings) {
|
|
60
80
|
for (const node of children) {
|
|
61
81
|
if (typeof node !== "object" || !Markdoc.Tag.isTag(node))
|
|
62
82
|
continue;
|
|
@@ -77,14 +97,40 @@ function collectHeadings(children) {
|
|
|
77
97
|
});
|
|
78
98
|
}
|
|
79
99
|
}
|
|
80
|
-
|
|
100
|
+
collectHeadings(node.children, collectedHeadings);
|
|
81
101
|
}
|
|
82
|
-
|
|
102
|
+
}
|
|
103
|
+
function createGetHeadings(stringifiedAst, userConfig) {
|
|
104
|
+
return function getHeadings() {
|
|
105
|
+
const config = setupConfigSync(userConfig);
|
|
106
|
+
const ast = Markdoc.Ast.fromJSON(stringifiedAst);
|
|
107
|
+
const content = Markdoc.transform(ast, config);
|
|
108
|
+
let collectedHeadings = [];
|
|
109
|
+
collectHeadings(Array.isArray(content) ? content : [content], collectedHeadings);
|
|
110
|
+
return collectedHeadings;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function createContentComponent(Renderer, stringifiedAst, userConfig, tagComponentMap, nodeComponentMap) {
|
|
114
|
+
return createComponent({
|
|
115
|
+
async factory(result, props) {
|
|
116
|
+
const withVariables = mergeConfig(userConfig, { variables: props });
|
|
117
|
+
const config = resolveComponentImports(
|
|
118
|
+
await setupConfig(withVariables),
|
|
119
|
+
tagComponentMap,
|
|
120
|
+
nodeComponentMap
|
|
121
|
+
);
|
|
122
|
+
return renderComponent(result, Renderer.name, Renderer, { stringifiedAst, config }, {});
|
|
123
|
+
},
|
|
124
|
+
propagation: "self"
|
|
125
|
+
});
|
|
83
126
|
}
|
|
84
127
|
export {
|
|
85
|
-
default2 as Markdoc,
|
|
86
128
|
collectHeadings,
|
|
129
|
+
createContentComponent,
|
|
130
|
+
createGetHeadings,
|
|
87
131
|
getTextContent,
|
|
132
|
+
mergeConfig,
|
|
133
|
+
resolveComponentImports,
|
|
88
134
|
setupConfig,
|
|
89
135
|
setupConfigSync
|
|
90
136
|
};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
/**
|
|
3
|
-
* Match YAML exception handling from Astro core errors
|
|
4
|
-
* @see 'astro/src/core/errors.ts'
|
|
5
|
-
*/
|
|
6
|
-
export declare function parseFrontmatter(fileContents: string, filePath: string): matter.GrayMatterFile<string>;
|
|
1
|
+
import type { ComponentConfig } from './config.js';
|
|
7
2
|
/**
|
|
8
3
|
* Matches AstroError object with types like error codes stubbed out
|
|
9
4
|
* @see 'astro/src/core/errors/errors.ts'
|
|
10
5
|
*/
|
|
11
6
|
export declare class MarkdocError extends Error {
|
|
12
|
-
errorCode: number;
|
|
13
7
|
loc: ErrorLocation | undefined;
|
|
14
8
|
title: string | undefined;
|
|
15
9
|
hint: string | undefined;
|
|
@@ -46,4 +40,7 @@ export declare const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
|
|
|
46
40
|
* @see 'packages/astro/src/content/utils.ts'
|
|
47
41
|
*/
|
|
48
42
|
export declare function hasContentFlag(viteId: string, flag: string): boolean;
|
|
43
|
+
/** Identifier for components imports passed as `tags` or `nodes` configuration. */
|
|
44
|
+
export declare const componentConfigSymbol: unique symbol;
|
|
45
|
+
export declare function isComponentConfig(value: unknown): value is ComponentConfig;
|
|
49
46
|
export {};
|
package/dist/utils.js
CHANGED
|
@@ -1,37 +1,8 @@
|
|
|
1
|
-
import matter from "gray-matter";
|
|
2
|
-
function parseFrontmatter(fileContents, filePath) {
|
|
3
|
-
try {
|
|
4
|
-
matter.clearCache();
|
|
5
|
-
return matter(fileContents);
|
|
6
|
-
} catch (e) {
|
|
7
|
-
if (e.name === "YAMLException") {
|
|
8
|
-
const err = e;
|
|
9
|
-
err.id = filePath;
|
|
10
|
-
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
11
|
-
err.message = e.reason;
|
|
12
|
-
throw err;
|
|
13
|
-
} else {
|
|
14
|
-
throw e;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
1
|
class MarkdocError extends Error {
|
|
19
2
|
constructor(props, ...params) {
|
|
20
3
|
super(...params);
|
|
21
4
|
this.type = "MarkdocError";
|
|
22
|
-
const {
|
|
23
|
-
// Use default code for unknown errors in Astro core
|
|
24
|
-
// We don't have a best practice for integration error codes yet
|
|
25
|
-
code = 99999,
|
|
26
|
-
name,
|
|
27
|
-
title = "MarkdocError",
|
|
28
|
-
message,
|
|
29
|
-
stack,
|
|
30
|
-
location,
|
|
31
|
-
hint,
|
|
32
|
-
frame
|
|
33
|
-
} = props;
|
|
34
|
-
this.errorCode = code;
|
|
5
|
+
const { title = "MarkdocError", message, stack, location, hint, frame } = props;
|
|
35
6
|
this.title = title;
|
|
36
7
|
if (message)
|
|
37
8
|
this.message = message;
|
|
@@ -57,11 +28,16 @@ function hasContentFlag(viteId, flag) {
|
|
|
57
28
|
const flags = new URLSearchParams(viteId.split("?")[1] ?? "");
|
|
58
29
|
return flags.has(flag);
|
|
59
30
|
}
|
|
31
|
+
const componentConfigSymbol = Symbol.for("@astrojs/markdoc/component-config");
|
|
32
|
+
function isComponentConfig(value) {
|
|
33
|
+
return typeof value === "object" && value !== null && componentConfigSymbol in value;
|
|
34
|
+
}
|
|
60
35
|
export {
|
|
61
36
|
MarkdocError,
|
|
62
37
|
PROPAGATED_ASSET_FLAG,
|
|
38
|
+
componentConfigSymbol,
|
|
63
39
|
hasContentFlag,
|
|
40
|
+
isComponentConfig,
|
|
64
41
|
isValidUrl,
|
|
65
|
-
parseFrontmatter,
|
|
66
42
|
prependForwardSlash
|
|
67
43
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/markdoc",
|
|
3
3
|
"description": "Add support for Markdoc in your Astro site",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"author": "withastro",
|
|
@@ -56,9 +56,10 @@
|
|
|
56
56
|
"template"
|
|
57
57
|
],
|
|
58
58
|
"dependencies": {
|
|
59
|
+
"@astrojs/internal-helpers": "^0.1.0",
|
|
59
60
|
"@astrojs/prism": "^2.1.2",
|
|
60
61
|
"@markdoc/markdoc": "^0.3.0",
|
|
61
|
-
"esbuild": "^0.17.
|
|
62
|
+
"esbuild": "^0.17.19",
|
|
62
63
|
"github-slugger": "^2.0.0",
|
|
63
64
|
"gray-matter": "^4.0.3",
|
|
64
65
|
"kleur": "^4.1.5",
|
|
@@ -66,20 +67,20 @@
|
|
|
66
67
|
"zod": "^3.17.3"
|
|
67
68
|
},
|
|
68
69
|
"peerDependencies": {
|
|
69
|
-
"astro": "^2.
|
|
70
|
+
"astro": "^2.8.0"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
73
|
"@astrojs/markdown-remark": "^2.2.1",
|
|
73
|
-
"@types/chai": "^4.3.
|
|
74
|
+
"@types/chai": "^4.3.5",
|
|
74
75
|
"@types/html-escaper": "^3.0.0",
|
|
75
76
|
"@types/mocha": "^9.1.1",
|
|
76
|
-
"chai": "^4.3.
|
|
77
|
+
"chai": "^4.3.7",
|
|
77
78
|
"devalue": "^4.3.2",
|
|
78
|
-
"linkedom": "^0.14.
|
|
79
|
+
"linkedom": "^0.14.26",
|
|
79
80
|
"mocha": "^9.2.2",
|
|
80
|
-
"rollup": "^3.
|
|
81
|
-
"vite": "^4.3.
|
|
82
|
-
"astro": "2.
|
|
81
|
+
"rollup": "^3.25.1",
|
|
82
|
+
"vite": "^4.3.9",
|
|
83
|
+
"astro": "2.8.0",
|
|
83
84
|
"astro-scripts": "0.0.14"
|
|
84
85
|
},
|
|
85
86
|
"engines": {
|