@astrojs/markdoc 0.0.0-10745-20240410180016
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/LICENSE +59 -0
- package/README.md +38 -0
- package/components/Renderer.astro +24 -0
- package/components/TreeNode.ts +160 -0
- package/components/index.ts +2 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.js +26 -0
- package/dist/content-entry-type.d.ts +8 -0
- package/dist/content-entry-type.js +317 -0
- package/dist/extensions/prism.d.ts +2 -0
- package/dist/extensions/prism.js +21 -0
- package/dist/extensions/shiki.d.ts +3 -0
- package/dist/extensions/shiki.js +21 -0
- package/dist/heading-ids.d.ts +10 -0
- package/dist/heading-ids.js +55 -0
- package/dist/html/css/parse-inline-css-to-react.d.ts +1 -0
- package/dist/html/css/parse-inline-css-to-react.js +21 -0
- package/dist/html/css/parse-inline-styles.d.ts +8 -0
- package/dist/html/css/parse-inline-styles.js +153 -0
- package/dist/html/css/style-to-object.d.ts +12 -0
- package/dist/html/css/style-to-object.js +53 -0
- package/dist/html/index.d.ts +2 -0
- package/dist/html/index.js +6 -0
- package/dist/html/tagdefs/html.tag.d.ts +2 -0
- package/dist/html/tagdefs/html.tag.js +21 -0
- package/dist/html/transform/html-token-transform.d.ts +3 -0
- package/dist/html/transform/html-token-transform.js +154 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +39 -0
- package/dist/load-config.d.ts +8 -0
- package/dist/load-config.js +91 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.js +0 -0
- package/dist/runtime-assets-config.d.ts +2 -0
- package/dist/runtime-assets-config.js +25 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.js +152 -0
- package/dist/tokenizer.d.ts +3 -0
- package/dist/tokenizer.js +27 -0
- package/dist/utils.d.ts +46 -0
- package/dist/utils.js +47 -0
- package/package.json +94 -0
- package/template/content-module-types.d.ts +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Fred K. Schott
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/sveltejs/kit repository:
|
|
25
|
+
|
|
26
|
+
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/contributors)
|
|
27
|
+
|
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
29
|
+
|
|
30
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
31
|
+
|
|
32
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/vitejs/vite repository:
|
|
37
|
+
|
|
38
|
+
MIT License
|
|
39
|
+
|
|
40
|
+
Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors
|
|
41
|
+
|
|
42
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
43
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
44
|
+
in the Software without restriction, including without limitation the rights
|
|
45
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
46
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
47
|
+
furnished to do so, subject to the following conditions:
|
|
48
|
+
|
|
49
|
+
The above copyright notice and this permission notice shall be included in all
|
|
50
|
+
copies or substantial portions of the Software.
|
|
51
|
+
|
|
52
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
53
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
54
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
55
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
56
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
57
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
58
|
+
SOFTWARE.
|
|
59
|
+
"""
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @astrojs/markdoc (experimental) 📝
|
|
2
|
+
|
|
3
|
+
This **[Astro integration][astro-integration]** enables the usage of [Markdoc](https://markdoc.dev/) to create components, pages, and content collection entries.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
Read the [`@astrojs/markdoc` docs][docs]
|
|
8
|
+
|
|
9
|
+
## Support
|
|
10
|
+
|
|
11
|
+
- Get help in the [Astro Discord][discord]. Post questions in our `#support` forum, or visit our dedicated `#dev` channel to discuss current development and more!
|
|
12
|
+
|
|
13
|
+
- Check our [Astro Integration Documentation][astro-integration] for more on integrations.
|
|
14
|
+
|
|
15
|
+
- Submit bug reports and feature requests as [GitHub issues][issues].
|
|
16
|
+
|
|
17
|
+
## Contributing
|
|
18
|
+
|
|
19
|
+
This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! These links will help you get started:
|
|
20
|
+
|
|
21
|
+
- [Contributor Manual][contributing]
|
|
22
|
+
- [Code of Conduct][coc]
|
|
23
|
+
- [Community Guide][community]
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
MIT
|
|
28
|
+
|
|
29
|
+
Copyright (c) 2023–present [Astro][astro]
|
|
30
|
+
|
|
31
|
+
[astro]: https://astro.build/
|
|
32
|
+
[docs]: https://docs.astro.build/en/guides/integrations-guide/markdoc/
|
|
33
|
+
[contributing]: https://github.com/withastro/astro/blob/main/CONTRIBUTING.md
|
|
34
|
+
[coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md
|
|
35
|
+
[community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md
|
|
36
|
+
[discord]: https://astro.build/chat/
|
|
37
|
+
[issues]: https://github.com/withastro/astro/issues
|
|
38
|
+
[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
//! astro-head-inject
|
|
3
|
+
import type { Config } from '@markdoc/markdoc';
|
|
4
|
+
import Markdoc from '@markdoc/markdoc';
|
|
5
|
+
import { ComponentNode, createTreeNode } from './TreeNode.js';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
config: Config;
|
|
9
|
+
stringifiedAst: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const { stringifiedAst, config } = Astro.props as Props;
|
|
13
|
+
|
|
14
|
+
const ast = Markdoc.Ast.fromJSON(stringifiedAst);
|
|
15
|
+
const content = await Markdoc.transform(ast, config);
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
Array.isArray(content) ? (
|
|
20
|
+
content.map(async (c) => <ComponentNode treeNode={await createTreeNode(c)} />)
|
|
21
|
+
) : (
|
|
22
|
+
<ComponentNode treeNode={await createTreeNode(content)} />
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { RenderableTreeNode } from '@markdoc/markdoc';
|
|
2
|
+
import Markdoc from '@markdoc/markdoc';
|
|
3
|
+
import type { AstroInstance } from 'astro';
|
|
4
|
+
import type { HTMLString } from 'astro/runtime/server/index.js';
|
|
5
|
+
import {
|
|
6
|
+
createComponent,
|
|
7
|
+
createHeadAndContent,
|
|
8
|
+
isHTMLString,
|
|
9
|
+
render,
|
|
10
|
+
renderComponent,
|
|
11
|
+
renderScriptElement,
|
|
12
|
+
renderTemplate,
|
|
13
|
+
renderUniqueStylesheet,
|
|
14
|
+
unescapeHTML,
|
|
15
|
+
} from 'astro/runtime/server/index.js';
|
|
16
|
+
|
|
17
|
+
export type TreeNode =
|
|
18
|
+
| {
|
|
19
|
+
type: 'text';
|
|
20
|
+
content: string | HTMLString;
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
type: 'component';
|
|
24
|
+
component: AstroInstance['default'];
|
|
25
|
+
collectedLinks?: string[];
|
|
26
|
+
collectedStyles?: string[];
|
|
27
|
+
collectedScripts?: string[];
|
|
28
|
+
props: Record<string, any>;
|
|
29
|
+
children: TreeNode[];
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
type: 'element';
|
|
33
|
+
tag: string;
|
|
34
|
+
attributes: Record<string, any>;
|
|
35
|
+
children: TreeNode[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const ComponentNode = createComponent({
|
|
39
|
+
factory(result: any, { treeNode }: { treeNode: TreeNode }) {
|
|
40
|
+
if (treeNode.type === 'text') return render`${treeNode.content}`;
|
|
41
|
+
|
|
42
|
+
const slots = {
|
|
43
|
+
default: () =>
|
|
44
|
+
render`${treeNode.children.map((child) =>
|
|
45
|
+
renderComponent(result, 'ComponentNode', ComponentNode, { treeNode: child })
|
|
46
|
+
)}`,
|
|
47
|
+
};
|
|
48
|
+
if (treeNode.type === 'component') {
|
|
49
|
+
let styles = '',
|
|
50
|
+
links = '',
|
|
51
|
+
scripts = '';
|
|
52
|
+
if (Array.isArray(treeNode.collectedStyles)) {
|
|
53
|
+
styles = treeNode.collectedStyles
|
|
54
|
+
.map((style: any) =>
|
|
55
|
+
renderUniqueStylesheet(result, {
|
|
56
|
+
type: 'inline',
|
|
57
|
+
content: style,
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
.join('');
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(treeNode.collectedLinks)) {
|
|
63
|
+
links = treeNode.collectedLinks
|
|
64
|
+
.map((link: any) => {
|
|
65
|
+
return renderUniqueStylesheet(result, {
|
|
66
|
+
type: 'external',
|
|
67
|
+
src: link[0] === '/' ? link : '/' + link,
|
|
68
|
+
});
|
|
69
|
+
})
|
|
70
|
+
.join('');
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(treeNode.collectedScripts)) {
|
|
73
|
+
scripts = treeNode.collectedScripts
|
|
74
|
+
.map((script: any) => renderScriptElement(script))
|
|
75
|
+
.join('');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const head = unescapeHTML(styles + links + scripts);
|
|
79
|
+
|
|
80
|
+
let headAndContent = createHeadAndContent(
|
|
81
|
+
head,
|
|
82
|
+
renderTemplate`${renderComponent(
|
|
83
|
+
result,
|
|
84
|
+
treeNode.component.name,
|
|
85
|
+
treeNode.component,
|
|
86
|
+
treeNode.props,
|
|
87
|
+
slots
|
|
88
|
+
)}`
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Let the runtime know that this component is being used.
|
|
92
|
+
result._metadata.propagators.add({
|
|
93
|
+
init() {
|
|
94
|
+
return headAndContent;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return headAndContent;
|
|
99
|
+
}
|
|
100
|
+
return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots);
|
|
101
|
+
},
|
|
102
|
+
propagation: 'self',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export async function createTreeNode(node: RenderableTreeNode): Promise<TreeNode> {
|
|
106
|
+
if (isHTMLString(node)) {
|
|
107
|
+
return { type: 'text', content: node as HTMLString };
|
|
108
|
+
} else if (typeof node === 'string' || typeof node === 'number') {
|
|
109
|
+
return { type: 'text', content: String(node) };
|
|
110
|
+
} else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) {
|
|
111
|
+
return { type: 'text', content: '' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const children = await Promise.all(node.children.map((child) => createTreeNode(child)));
|
|
115
|
+
|
|
116
|
+
if (typeof node.name === 'function') {
|
|
117
|
+
const component = node.name;
|
|
118
|
+
const props = node.attributes;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
type: 'component',
|
|
122
|
+
component,
|
|
123
|
+
props,
|
|
124
|
+
children,
|
|
125
|
+
};
|
|
126
|
+
} else if (isPropagatedAssetsModule(node.name)) {
|
|
127
|
+
const { collectedStyles, collectedLinks, collectedScripts } = node.name;
|
|
128
|
+
const component = (await node.name.getMod()).default;
|
|
129
|
+
const props = node.attributes;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
type: 'component',
|
|
133
|
+
component,
|
|
134
|
+
collectedStyles,
|
|
135
|
+
collectedLinks,
|
|
136
|
+
collectedScripts,
|
|
137
|
+
props,
|
|
138
|
+
children,
|
|
139
|
+
};
|
|
140
|
+
} else {
|
|
141
|
+
return {
|
|
142
|
+
type: 'element',
|
|
143
|
+
tag: node.name,
|
|
144
|
+
attributes: node.attributes,
|
|
145
|
+
children,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type PropagatedAssetsModule = {
|
|
151
|
+
__astroPropagation: true;
|
|
152
|
+
getMod: () => Promise<AstroInstance>;
|
|
153
|
+
collectedStyles: string[];
|
|
154
|
+
collectedLinks: string[];
|
|
155
|
+
collectedScripts: string[];
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
function isPropagatedAssetsModule(module: any): module is PropagatedAssetsModule {
|
|
159
|
+
return typeof module === 'object' && module != null && '__astroPropagation' in module;
|
|
160
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Config, ConfigType as MarkdocConfig, MaybePromise, NodeType, Schema } from '@markdoc/markdoc';
|
|
2
|
+
import _Markdoc from '@markdoc/markdoc';
|
|
3
|
+
import type { AstroInstance } from 'astro';
|
|
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
|
+
};
|
|
12
|
+
export type AstroMarkdocConfig<C extends Record<string, any> = Record<string, any>> = Omit<MarkdocConfig, 'tags' | 'nodes'> & Partial<{
|
|
13
|
+
tags: Record<string, Schema<Config, Render>>;
|
|
14
|
+
nodes: Partial<Record<NodeType, Schema<Config, Render>>>;
|
|
15
|
+
ctx: C;
|
|
16
|
+
extends: MaybePromise<ResolvedAstroMarkdocConfig>[];
|
|
17
|
+
}>;
|
|
18
|
+
export type ResolvedAstroMarkdocConfig = Omit<AstroMarkdocConfig, 'extends'>;
|
|
19
|
+
export declare const Markdoc: typeof _Markdoc;
|
|
20
|
+
export declare const nodes: {
|
|
21
|
+
heading: Schema;
|
|
22
|
+
document: Schema;
|
|
23
|
+
paragraph: Schema;
|
|
24
|
+
image: Schema;
|
|
25
|
+
fence: Schema;
|
|
26
|
+
blockquote: Schema;
|
|
27
|
+
item: Schema;
|
|
28
|
+
list: Schema;
|
|
29
|
+
hr: Schema;
|
|
30
|
+
table: Schema;
|
|
31
|
+
td: Schema;
|
|
32
|
+
th: Schema;
|
|
33
|
+
tr: Schema;
|
|
34
|
+
tbody: Schema;
|
|
35
|
+
thead: Schema;
|
|
36
|
+
strong: Schema;
|
|
37
|
+
em: Schema;
|
|
38
|
+
s: Schema;
|
|
39
|
+
inline: Schema;
|
|
40
|
+
link: Schema;
|
|
41
|
+
code: Schema;
|
|
42
|
+
text: Schema;
|
|
43
|
+
hardbreak: Schema;
|
|
44
|
+
softbreak: Schema;
|
|
45
|
+
comment: {
|
|
46
|
+
attributes: {
|
|
47
|
+
content: {
|
|
48
|
+
type: StringConstructor;
|
|
49
|
+
required: boolean;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
error: {};
|
|
54
|
+
node: {};
|
|
55
|
+
};
|
|
56
|
+
export declare function defineMarkdocConfig(config: AstroMarkdocConfig): AstroMarkdocConfig;
|
|
57
|
+
export declare function component(pathnameOrPkgName: string, namedExport?: string): ComponentConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isRelativePath } from "@astrojs/internal-helpers/path";
|
|
2
|
+
import _Markdoc from "@markdoc/markdoc";
|
|
3
|
+
import { heading } from "./heading-ids.js";
|
|
4
|
+
import { componentConfigSymbol } from "./utils.js";
|
|
5
|
+
const Markdoc = _Markdoc;
|
|
6
|
+
const nodes = { ...Markdoc.nodes, heading };
|
|
7
|
+
function defineMarkdocConfig(config) {
|
|
8
|
+
return config;
|
|
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
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
Markdoc,
|
|
23
|
+
component,
|
|
24
|
+
defineMarkdocConfig,
|
|
25
|
+
nodes
|
|
26
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AstroConfig, ContentEntryType } from 'astro';
|
|
2
|
+
import type { MarkdocConfigResult } from './load-config.js';
|
|
3
|
+
import type { MarkdocIntegrationOptions } from './options.js';
|
|
4
|
+
export declare function getContentEntryType({ markdocConfigResult, astroConfig, options, }: {
|
|
5
|
+
astroConfig: AstroConfig;
|
|
6
|
+
markdocConfigResult?: MarkdocConfigResult;
|
|
7
|
+
options?: MarkdocIntegrationOptions;
|
|
8
|
+
}): Promise<ContentEntryType>;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import Markdoc from "@markdoc/markdoc";
|
|
5
|
+
import { emitESMImage } from "astro/assets/utils";
|
|
6
|
+
import matter from "gray-matter";
|
|
7
|
+
import { htmlTokenTransform } from "./html/transform/html-token-transform.js";
|
|
8
|
+
import { setupConfig } from "./runtime.js";
|
|
9
|
+
import { getMarkdocTokenizer } from "./tokenizer.js";
|
|
10
|
+
import { MarkdocError, isComponentConfig, isValidUrl, prependForwardSlash } from "./utils.js";
|
|
11
|
+
async function getContentEntryType({
|
|
12
|
+
markdocConfigResult,
|
|
13
|
+
astroConfig,
|
|
14
|
+
options
|
|
15
|
+
}) {
|
|
16
|
+
return {
|
|
17
|
+
extensions: [".mdoc"],
|
|
18
|
+
getEntryInfo,
|
|
19
|
+
handlePropagation: true,
|
|
20
|
+
async getRenderModule({ contents, fileUrl, viteId }) {
|
|
21
|
+
const entry = getEntryInfo({ contents, fileUrl });
|
|
22
|
+
const tokenizer = getMarkdocTokenizer(options);
|
|
23
|
+
let tokens = tokenizer.tokenize(entry.body);
|
|
24
|
+
if (options?.allowHTML) {
|
|
25
|
+
tokens = htmlTokenTransform(tokenizer, tokens);
|
|
26
|
+
}
|
|
27
|
+
const ast = Markdoc.parse(tokens);
|
|
28
|
+
const userMarkdocConfig = markdocConfigResult?.config ?? {};
|
|
29
|
+
const markdocConfigUrl = markdocConfigResult?.fileUrl;
|
|
30
|
+
const pluginContext = this;
|
|
31
|
+
const markdocConfig = await setupConfig(userMarkdocConfig, options);
|
|
32
|
+
const filePath = fileURLToPath(fileUrl);
|
|
33
|
+
raiseValidationErrors({
|
|
34
|
+
ast,
|
|
35
|
+
/* Raised generics issue with Markdoc core https://github.com/markdoc/markdoc/discussions/400 */
|
|
36
|
+
markdocConfig,
|
|
37
|
+
entry,
|
|
38
|
+
viteId,
|
|
39
|
+
astroConfig,
|
|
40
|
+
filePath
|
|
41
|
+
});
|
|
42
|
+
await resolvePartials({
|
|
43
|
+
ast,
|
|
44
|
+
markdocConfig,
|
|
45
|
+
fileUrl,
|
|
46
|
+
allowHTML: options?.allowHTML,
|
|
47
|
+
tokenizer,
|
|
48
|
+
pluginContext,
|
|
49
|
+
root: astroConfig.root,
|
|
50
|
+
raisePartialValidationErrors: (partialAst, partialPath) => {
|
|
51
|
+
raiseValidationErrors({
|
|
52
|
+
ast: partialAst,
|
|
53
|
+
markdocConfig,
|
|
54
|
+
entry,
|
|
55
|
+
viteId,
|
|
56
|
+
astroConfig,
|
|
57
|
+
filePath: partialPath
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const usedTags = getUsedTags(ast);
|
|
62
|
+
let componentConfigByTagMap = {};
|
|
63
|
+
for (const tag of usedTags) {
|
|
64
|
+
const render = userMarkdocConfig.tags?.[tag]?.render;
|
|
65
|
+
if (isComponentConfig(render)) {
|
|
66
|
+
componentConfigByTagMap[tag] = render;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let componentConfigByNodeMap = {};
|
|
70
|
+
for (const [nodeType, schema] of Object.entries(userMarkdocConfig.nodes ?? {})) {
|
|
71
|
+
const render = schema?.render;
|
|
72
|
+
if (isComponentConfig(render)) {
|
|
73
|
+
componentConfigByNodeMap[nodeType] = render;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
await emitOptimizedImages(ast.children, {
|
|
77
|
+
astroConfig,
|
|
78
|
+
pluginContext,
|
|
79
|
+
filePath
|
|
80
|
+
});
|
|
81
|
+
const res = `import { Renderer } from '@astrojs/markdoc/components';
|
|
82
|
+
import { createGetHeadings, createContentComponent } from '@astrojs/markdoc/runtime';
|
|
83
|
+
${markdocConfigUrl ? `import markdocConfig from ${JSON.stringify(fileURLToPath(markdocConfigUrl))};` : "const markdocConfig = {};"}
|
|
84
|
+
|
|
85
|
+
import { assetsConfig } from '@astrojs/markdoc/runtime-assets-config';
|
|
86
|
+
markdocConfig.nodes = { ...assetsConfig.nodes, ...markdocConfig.nodes };
|
|
87
|
+
|
|
88
|
+
${getStringifiedImports(componentConfigByTagMap, "Tag", astroConfig.root)}
|
|
89
|
+
${getStringifiedImports(componentConfigByNodeMap, "Node", astroConfig.root)}
|
|
90
|
+
|
|
91
|
+
const tagComponentMap = ${getStringifiedMap(componentConfigByTagMap, "Tag")};
|
|
92
|
+
const nodeComponentMap = ${getStringifiedMap(componentConfigByNodeMap, "Node")};
|
|
93
|
+
|
|
94
|
+
const options = ${JSON.stringify(options)};
|
|
95
|
+
|
|
96
|
+
const stringifiedAst = ${JSON.stringify(
|
|
97
|
+
/* Double stringify to encode *as* stringified JSON */
|
|
98
|
+
JSON.stringify(ast)
|
|
99
|
+
)};
|
|
100
|
+
|
|
101
|
+
export const getHeadings = createGetHeadings(stringifiedAst, markdocConfig, options);
|
|
102
|
+
export const Content = createContentComponent(
|
|
103
|
+
Renderer,
|
|
104
|
+
stringifiedAst,
|
|
105
|
+
markdocConfig,
|
|
106
|
+
options,
|
|
107
|
+
tagComponentMap,
|
|
108
|
+
nodeComponentMap,
|
|
109
|
+
)`;
|
|
110
|
+
return { code: res };
|
|
111
|
+
},
|
|
112
|
+
contentModuleTypes: await fs.promises.readFile(
|
|
113
|
+
new URL("../template/content-module-types.d.ts", import.meta.url),
|
|
114
|
+
"utf-8"
|
|
115
|
+
)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function resolvePartials({
|
|
119
|
+
ast,
|
|
120
|
+
fileUrl,
|
|
121
|
+
root,
|
|
122
|
+
tokenizer,
|
|
123
|
+
allowHTML,
|
|
124
|
+
markdocConfig,
|
|
125
|
+
pluginContext,
|
|
126
|
+
raisePartialValidationErrors
|
|
127
|
+
}) {
|
|
128
|
+
const relativePartialPath = path.relative(fileURLToPath(root), fileURLToPath(fileUrl));
|
|
129
|
+
for (const node of ast.walk()) {
|
|
130
|
+
if (node.type === "tag" && node.tag === "partial") {
|
|
131
|
+
const { file } = node.attributes;
|
|
132
|
+
if (!file) {
|
|
133
|
+
throw new MarkdocError({
|
|
134
|
+
// Should be caught by Markdoc validation step.
|
|
135
|
+
message: `(Uncaught error) Partial tag requires a 'file' attribute`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (markdocConfig.partials?.[file])
|
|
139
|
+
continue;
|
|
140
|
+
let partialPath;
|
|
141
|
+
let partialContents;
|
|
142
|
+
try {
|
|
143
|
+
const resolved = await pluginContext.resolve(file, fileURLToPath(fileUrl));
|
|
144
|
+
let partialId = resolved?.id;
|
|
145
|
+
if (!partialId) {
|
|
146
|
+
const attemptResolveAsRelative = await pluginContext.resolve(
|
|
147
|
+
"./" + file,
|
|
148
|
+
fileURLToPath(fileUrl)
|
|
149
|
+
);
|
|
150
|
+
if (!attemptResolveAsRelative?.id)
|
|
151
|
+
throw new Error();
|
|
152
|
+
partialId = attemptResolveAsRelative.id;
|
|
153
|
+
}
|
|
154
|
+
partialPath = fileURLToPath(new URL(prependForwardSlash(partialId), "file://"));
|
|
155
|
+
partialContents = await fs.promises.readFile(partialPath, "utf-8");
|
|
156
|
+
} catch {
|
|
157
|
+
throw new MarkdocError({
|
|
158
|
+
message: [
|
|
159
|
+
`**${String(relativePartialPath)}** contains invalid content:`,
|
|
160
|
+
`Could not read partial file \`${file}\`. Does the file exist?`
|
|
161
|
+
].join("\n")
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (pluginContext.meta.watchMode)
|
|
165
|
+
pluginContext.addWatchFile(partialPath);
|
|
166
|
+
let partialTokens = tokenizer.tokenize(partialContents);
|
|
167
|
+
if (allowHTML) {
|
|
168
|
+
partialTokens = htmlTokenTransform(tokenizer, partialTokens);
|
|
169
|
+
}
|
|
170
|
+
const partialAst = Markdoc.parse(partialTokens);
|
|
171
|
+
raisePartialValidationErrors(partialAst, partialPath);
|
|
172
|
+
await resolvePartials({
|
|
173
|
+
ast: partialAst,
|
|
174
|
+
root,
|
|
175
|
+
fileUrl: pathToFileURL(partialPath),
|
|
176
|
+
tokenizer,
|
|
177
|
+
allowHTML,
|
|
178
|
+
markdocConfig,
|
|
179
|
+
pluginContext,
|
|
180
|
+
raisePartialValidationErrors
|
|
181
|
+
});
|
|
182
|
+
Object.assign(node, partialAst);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function raiseValidationErrors({
|
|
187
|
+
ast,
|
|
188
|
+
markdocConfig,
|
|
189
|
+
entry,
|
|
190
|
+
viteId,
|
|
191
|
+
astroConfig,
|
|
192
|
+
filePath
|
|
193
|
+
}) {
|
|
194
|
+
const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
|
|
195
|
+
return (e.error.level === "error" || e.error.level === "critical") && // Ignore `variable-undefined` errors.
|
|
196
|
+
// Variables can be configured at runtime,
|
|
197
|
+
// so we cannot validate them at build time.
|
|
198
|
+
e.error.id !== "variable-undefined" && // Ignore missing partial errors.
|
|
199
|
+
// We will resolve these in `resolvePartials`.
|
|
200
|
+
!(e.error.id === "attribute-value-invalid" && e.error.message.match(/^Partial .+ not found/));
|
|
201
|
+
});
|
|
202
|
+
if (validationErrors.length) {
|
|
203
|
+
const frontmatterBlockOffset = entry.rawData.split("\n").length + 2;
|
|
204
|
+
const rootRelativePath = path.relative(fileURLToPath(astroConfig.root), filePath);
|
|
205
|
+
throw new MarkdocError({
|
|
206
|
+
message: [
|
|
207
|
+
`**${String(rootRelativePath)}** contains invalid content:`,
|
|
208
|
+
...validationErrors.map((e) => `- ${e.error.message}`)
|
|
209
|
+
].join("\n"),
|
|
210
|
+
location: {
|
|
211
|
+
// Error overlay does not support multi-line or ranges.
|
|
212
|
+
// Just point to the first line.
|
|
213
|
+
line: frontmatterBlockOffset + validationErrors[0].lines[0],
|
|
214
|
+
file: viteId
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function getUsedTags(markdocAst) {
|
|
220
|
+
const tags = /* @__PURE__ */ new Set();
|
|
221
|
+
const validationErrors = Markdoc.validate(markdocAst);
|
|
222
|
+
for (const { error } of validationErrors) {
|
|
223
|
+
if (error.id === "tag-undefined") {
|
|
224
|
+
const [, tagName] = error.message.match(/Undefined tag: '(.*)'/) ?? [];
|
|
225
|
+
tags.add(tagName);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return tags;
|
|
229
|
+
}
|
|
230
|
+
function getEntryInfo({ fileUrl, contents }) {
|
|
231
|
+
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
232
|
+
return {
|
|
233
|
+
data: parsed.data,
|
|
234
|
+
body: parsed.content,
|
|
235
|
+
slug: parsed.data.slug,
|
|
236
|
+
rawData: parsed.matter
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
async function emitOptimizedImages(nodeChildren, ctx) {
|
|
240
|
+
for (const node of nodeChildren) {
|
|
241
|
+
let isComponent = node.type === "tag" && node.tag === "image";
|
|
242
|
+
if ((node.type === "image" || isComponent) && typeof node.attributes.src === "string") {
|
|
243
|
+
let attributeName = isComponent ? "src" : "__optimizedSrc";
|
|
244
|
+
if (shouldOptimizeImage(node.attributes.src)) {
|
|
245
|
+
const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath);
|
|
246
|
+
if (resolved?.id && fs.existsSync(new URL(prependForwardSlash(resolved.id), "file://"))) {
|
|
247
|
+
const src = await emitESMImage(
|
|
248
|
+
resolved.id,
|
|
249
|
+
ctx.pluginContext.meta.watchMode,
|
|
250
|
+
ctx.pluginContext.emitFile
|
|
251
|
+
);
|
|
252
|
+
const fsPath = resolved.id;
|
|
253
|
+
if (src) {
|
|
254
|
+
if (ctx.astroConfig.output === "static") {
|
|
255
|
+
if (globalThis.astroAsset.referencedImages)
|
|
256
|
+
globalThis.astroAsset.referencedImages.add(fsPath);
|
|
257
|
+
}
|
|
258
|
+
node.attributes[attributeName] = { ...src, fsPath };
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
throw new MarkdocError({
|
|
262
|
+
message: `Could not resolve image ${JSON.stringify(
|
|
263
|
+
node.attributes.src
|
|
264
|
+
)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
} else if (isComponent) {
|
|
268
|
+
node.attributes[attributeName] = node.attributes.src;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
await emitOptimizedImages(node.children, ctx);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function shouldOptimizeImage(src) {
|
|
275
|
+
return !isValidUrl(src) && !src.startsWith("/");
|
|
276
|
+
}
|
|
277
|
+
function getStringifiedImports(componentConfigMap, componentNamePrefix, root) {
|
|
278
|
+
let stringifiedComponentImports = "";
|
|
279
|
+
for (const [key, config] of Object.entries(componentConfigMap)) {
|
|
280
|
+
const importName = config.namedExport ? `{ ${config.namedExport} as ${componentNamePrefix + toImportName(key)} }` : componentNamePrefix + toImportName(key);
|
|
281
|
+
const resolvedPath = config.type === "local" ? fileURLToPath(new URL(config.path, root)) : config.path;
|
|
282
|
+
stringifiedComponentImports += `import ${importName} from ${JSON.stringify(resolvedPath)};
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
return stringifiedComponentImports;
|
|
286
|
+
}
|
|
287
|
+
function toImportName(unsafeName) {
|
|
288
|
+
return unsafeName.replace("-", "_");
|
|
289
|
+
}
|
|
290
|
+
function getStringifiedMap(componentConfigMap, componentNamePrefix) {
|
|
291
|
+
let stringifiedComponentMap = "{";
|
|
292
|
+
for (const key in componentConfigMap) {
|
|
293
|
+
stringifiedComponentMap += `${JSON.stringify(key)}: ${componentNamePrefix + toImportName(key)},
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
stringifiedComponentMap += "}";
|
|
297
|
+
return stringifiedComponentMap;
|
|
298
|
+
}
|
|
299
|
+
function parseFrontmatter(fileContents, filePath) {
|
|
300
|
+
try {
|
|
301
|
+
matter.clearCache();
|
|
302
|
+
return matter(fileContents);
|
|
303
|
+
} catch (e) {
|
|
304
|
+
if (e.name === "YAMLException") {
|
|
305
|
+
const err = e;
|
|
306
|
+
err.id = filePath;
|
|
307
|
+
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
308
|
+
err.message = e.reason;
|
|
309
|
+
throw err;
|
|
310
|
+
} else {
|
|
311
|
+
throw e;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
export {
|
|
316
|
+
getContentEntryType
|
|
317
|
+
};
|