@astrojs/markdoc 0.2.0 → 0.2.2
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/components/Renderer.astro +2 -1
- package/components/TreeNode.ts +98 -12
- package/dist/index.js +53 -15
- package/dist/nodes/heading.d.ts +8 -2
- package/dist/nodes/heading.js +15 -5
- package/dist/nodes/index.d.ts +1 -1
- package/dist/nodes/index.js +3 -3
- package/dist/runtime.d.ts +6 -3
- package/dist/runtime.js +26 -12
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
//! astro-head-inject
|
|
2
3
|
import type { Config } from '@markdoc/markdoc';
|
|
3
4
|
import Markdoc from '@markdoc/markdoc';
|
|
4
5
|
import { ComponentNode, createTreeNode } from './TreeNode.js';
|
|
@@ -14,4 +15,4 @@ const ast = Markdoc.Ast.fromJSON(stringifiedAst);
|
|
|
14
15
|
const content = Markdoc.transform(ast, config);
|
|
15
16
|
---
|
|
16
17
|
|
|
17
|
-
<ComponentNode treeNode={createTreeNode(content)} />
|
|
18
|
+
<ComponentNode treeNode={await createTreeNode(content)} />
|
package/components/TreeNode.ts
CHANGED
|
@@ -2,7 +2,16 @@ 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 {
|
|
5
|
+
import {
|
|
6
|
+
createComponent,
|
|
7
|
+
renderComponent,
|
|
8
|
+
render,
|
|
9
|
+
renderScriptElement,
|
|
10
|
+
renderUniqueStylesheet,
|
|
11
|
+
createHeadAndContent,
|
|
12
|
+
unescapeHTML,
|
|
13
|
+
renderTemplate,
|
|
14
|
+
} from 'astro/runtime/server/index.js';
|
|
6
15
|
|
|
7
16
|
export type TreeNode =
|
|
8
17
|
| {
|
|
@@ -12,6 +21,9 @@ export type TreeNode =
|
|
|
12
21
|
| {
|
|
13
22
|
type: 'component';
|
|
14
23
|
component: AstroInstance['default'];
|
|
24
|
+
collectedLinks?: string[];
|
|
25
|
+
collectedStyles?: string[];
|
|
26
|
+
collectedScripts?: string[];
|
|
15
27
|
props: Record<string, any>;
|
|
16
28
|
children: TreeNode[];
|
|
17
29
|
}
|
|
@@ -32,20 +44,67 @@ export const ComponentNode = createComponent({
|
|
|
32
44
|
)}`,
|
|
33
45
|
};
|
|
34
46
|
if (treeNode.type === 'component') {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
treeNode.
|
|
40
|
-
|
|
47
|
+
let styles = '',
|
|
48
|
+
links = '',
|
|
49
|
+
scripts = '';
|
|
50
|
+
if (Array.isArray(treeNode.collectedStyles)) {
|
|
51
|
+
styles = treeNode.collectedStyles
|
|
52
|
+
.map((style: any) =>
|
|
53
|
+
renderUniqueStylesheet({
|
|
54
|
+
type: 'inline',
|
|
55
|
+
content: style,
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
.join('');
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(treeNode.collectedLinks)) {
|
|
61
|
+
links = treeNode.collectedLinks
|
|
62
|
+
.map((link: any) => {
|
|
63
|
+
return renderUniqueStylesheet(result, {
|
|
64
|
+
href: link[0] === '/' ? link : '/' + link,
|
|
65
|
+
});
|
|
66
|
+
})
|
|
67
|
+
.join('');
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(treeNode.collectedScripts)) {
|
|
70
|
+
scripts = treeNode.collectedScripts
|
|
71
|
+
.map((script: any) => renderScriptElement(script))
|
|
72
|
+
.join('');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const head = unescapeHTML(styles + links + scripts);
|
|
76
|
+
|
|
77
|
+
let headAndContent = createHeadAndContent(
|
|
78
|
+
head,
|
|
79
|
+
renderTemplate`${renderComponent(
|
|
80
|
+
result,
|
|
81
|
+
treeNode.component.name,
|
|
82
|
+
treeNode.component,
|
|
83
|
+
treeNode.props,
|
|
84
|
+
slots
|
|
85
|
+
)}`
|
|
41
86
|
);
|
|
87
|
+
|
|
88
|
+
// Let the runtime know that this component is being used.
|
|
89
|
+
result.propagators.set(
|
|
90
|
+
{},
|
|
91
|
+
{
|
|
92
|
+
init() {
|
|
93
|
+
return headAndContent;
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return headAndContent;
|
|
42
99
|
}
|
|
43
100
|
return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots);
|
|
44
101
|
},
|
|
45
|
-
propagation: '
|
|
102
|
+
propagation: 'self',
|
|
46
103
|
});
|
|
47
104
|
|
|
48
|
-
export function createTreeNode(
|
|
105
|
+
export async function createTreeNode(
|
|
106
|
+
node: RenderableTreeNode | RenderableTreeNode[]
|
|
107
|
+
): Promise<TreeNode> {
|
|
49
108
|
if (typeof node === 'string' || typeof node === 'number') {
|
|
50
109
|
return { type: 'text', content: String(node) };
|
|
51
110
|
} else if (Array.isArray(node)) {
|
|
@@ -53,16 +112,17 @@ export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]):
|
|
|
53
112
|
type: 'component',
|
|
54
113
|
component: Fragment,
|
|
55
114
|
props: {},
|
|
56
|
-
children: node.map((child) => createTreeNode(child)),
|
|
115
|
+
children: await Promise.all(node.map((child) => createTreeNode(child))),
|
|
57
116
|
};
|
|
58
117
|
} else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) {
|
|
59
118
|
return { type: 'text', content: '' };
|
|
60
119
|
}
|
|
61
120
|
|
|
121
|
+
const children = await Promise.all(node.children.map((child) => createTreeNode(child)));
|
|
122
|
+
|
|
62
123
|
if (typeof node.name === 'function') {
|
|
63
124
|
const component = node.name;
|
|
64
125
|
const props = node.attributes;
|
|
65
|
-
const children = node.children.map((child) => createTreeNode(child));
|
|
66
126
|
|
|
67
127
|
return {
|
|
68
128
|
type: 'component',
|
|
@@ -70,12 +130,38 @@ export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]):
|
|
|
70
130
|
props,
|
|
71
131
|
children,
|
|
72
132
|
};
|
|
133
|
+
} else if (isPropagatedAssetsModule(node.name)) {
|
|
134
|
+
const { collectedStyles, collectedLinks, collectedScripts } = node.name;
|
|
135
|
+
const component = (await node.name.getMod())?.default ?? Fragment;
|
|
136
|
+
const props = node.attributes;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
type: 'component',
|
|
140
|
+
component,
|
|
141
|
+
collectedStyles,
|
|
142
|
+
collectedLinks,
|
|
143
|
+
collectedScripts,
|
|
144
|
+
props,
|
|
145
|
+
children,
|
|
146
|
+
};
|
|
73
147
|
} else {
|
|
74
148
|
return {
|
|
75
149
|
type: 'element',
|
|
76
150
|
tag: node.name,
|
|
77
151
|
attributes: node.attributes,
|
|
78
|
-
children
|
|
152
|
+
children,
|
|
79
153
|
};
|
|
80
154
|
}
|
|
81
155
|
}
|
|
156
|
+
|
|
157
|
+
type PropagatedAssetsModule = {
|
|
158
|
+
__astroPropagation: true;
|
|
159
|
+
getMod: () => Promise<AstroInstance['default']>;
|
|
160
|
+
collectedStyles: string[];
|
|
161
|
+
collectedLinks: string[];
|
|
162
|
+
collectedScripts: string[];
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
function isPropagatedAssetsModule(module: any): module is PropagatedAssetsModule {
|
|
166
|
+
return typeof module === 'object' && module != null && '__astroPropagation' in module;
|
|
167
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from
|
|
|
5
5
|
import { emitESMImage } from "astro/assets";
|
|
6
6
|
import { bold, red, yellow } from "kleur/colors";
|
|
7
7
|
import { loadMarkdocConfig } from "./load-config.js";
|
|
8
|
-
import {
|
|
8
|
+
import { setupConfig } from "./runtime.js";
|
|
9
9
|
function markdocIntegration(legacyConfig) {
|
|
10
10
|
if (legacyConfig) {
|
|
11
11
|
console.log(
|
|
@@ -20,7 +20,11 @@ function markdocIntegration(legacyConfig) {
|
|
|
20
20
|
name: "@astrojs/markdoc",
|
|
21
21
|
hooks: {
|
|
22
22
|
"astro:config:setup": async (params) => {
|
|
23
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
config: astroConfig,
|
|
25
|
+
updateConfig,
|
|
26
|
+
addContentEntryType
|
|
27
|
+
} = params;
|
|
24
28
|
markdocConfigResult = await loadMarkdocConfig(astroConfig);
|
|
25
29
|
const userMarkdocConfig = (markdocConfigResult == null ? void 0 : markdocConfigResult.config) ?? {};
|
|
26
30
|
function getEntryInfo({ fileUrl, contents }) {
|
|
@@ -35,10 +39,13 @@ function markdocIntegration(legacyConfig) {
|
|
|
35
39
|
addContentEntryType({
|
|
36
40
|
extensions: [".mdoc"],
|
|
37
41
|
getEntryInfo,
|
|
42
|
+
// Markdoc handles script / style propagation
|
|
43
|
+
// for Astro components internally
|
|
44
|
+
handlePropagation: false,
|
|
38
45
|
async getRenderModule({ entry, viteId }) {
|
|
39
46
|
const ast = Markdoc.parse(entry.body);
|
|
40
47
|
const pluginContext = this;
|
|
41
|
-
const markdocConfig =
|
|
48
|
+
const markdocConfig = setupConfig(userMarkdocConfig, entry);
|
|
42
49
|
const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
|
|
43
50
|
return (
|
|
44
51
|
// Ignore `variable-undefined` errors.
|
|
@@ -69,9 +76,12 @@ function markdocIntegration(legacyConfig) {
|
|
|
69
76
|
filePath: entry._internal.filePath
|
|
70
77
|
});
|
|
71
78
|
}
|
|
72
|
-
const res = `import {
|
|
79
|
+
const res = `import {
|
|
80
|
+
createComponent,
|
|
81
|
+
renderComponent,
|
|
82
|
+
} from 'astro/runtime/server/index.js';
|
|
73
83
|
import { Renderer } from '@astrojs/markdoc/components';
|
|
74
|
-
import { collectHeadings,
|
|
84
|
+
import { collectHeadings, setupConfig, Markdoc } from '@astrojs/markdoc/runtime';
|
|
75
85
|
import * as entry from ${JSON.stringify(viteId + "?astroContentCollectionEntry")};
|
|
76
86
|
${markdocConfigResult ? `import _userConfig from ${JSON.stringify(
|
|
77
87
|
markdocConfigResult.fileUrl.pathname
|
|
@@ -88,22 +98,30 @@ export function getHeadings() {
|
|
|
88
98
|
TODO: propose new `render()` API to allow Markdoc variable passing to `render()` itself,
|
|
89
99
|
instead of the Content component. Would remove double-transform and unlock variable resolution in heading slugs. */
|
|
90
100
|
""}
|
|
91
|
-
headingSlugger.reset();
|
|
92
101
|
const headingConfig = userConfig.nodes?.heading;
|
|
93
|
-
const config =
|
|
102
|
+
const config = setupConfig(headingConfig ? { nodes: { heading: headingConfig } } : {}, entry);
|
|
94
103
|
const ast = Markdoc.Ast.fromJSON(stringifiedAst);
|
|
95
104
|
const content = Markdoc.transform(ast, config);
|
|
96
105
|
return collectHeadings(Array.isArray(content) ? content : content.children);
|
|
97
106
|
}
|
|
98
|
-
export async function Content (props) {
|
|
99
|
-
headingSlugger.reset();
|
|
100
|
-
const config = applyDefaultConfig({
|
|
101
|
-
...userConfig,
|
|
102
|
-
variables: { ...userConfig.variables, ...props },
|
|
103
|
-
}, entry);
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
export const Content = createComponent({
|
|
109
|
+
factory(result, props) {
|
|
110
|
+
const config = setupConfig({
|
|
111
|
+
...userConfig,
|
|
112
|
+
variables: { ...userConfig.variables, ...props },
|
|
113
|
+
}, entry);
|
|
114
|
+
|
|
115
|
+
return renderComponent(
|
|
116
|
+
result,
|
|
117
|
+
Renderer.name,
|
|
118
|
+
Renderer,
|
|
119
|
+
{ stringifiedAst, config },
|
|
120
|
+
{}
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
propagation: 'self',
|
|
124
|
+
});`;
|
|
107
125
|
return { code: res };
|
|
108
126
|
},
|
|
109
127
|
contentModuleTypes: await fs.promises.readFile(
|
|
@@ -111,6 +129,26 @@ export async function Content (props) {
|
|
|
111
129
|
"utf-8"
|
|
112
130
|
)
|
|
113
131
|
});
|
|
132
|
+
updateConfig({
|
|
133
|
+
vite: {
|
|
134
|
+
plugins: [
|
|
135
|
+
{
|
|
136
|
+
name: "@astrojs/markdoc:astro-propagated-assets",
|
|
137
|
+
enforce: "pre",
|
|
138
|
+
// Astro component styles and scripts should only be injected
|
|
139
|
+
// When a given Markdoc file actually uses that component.
|
|
140
|
+
// Add the `astroPropagatedAssets` flag to inject only when rendered.
|
|
141
|
+
resolveId(id, importer) {
|
|
142
|
+
if (importer === (markdocConfigResult == null ? void 0 : markdocConfigResult.fileUrl.pathname) && id.endsWith(".astro")) {
|
|
143
|
+
return this.resolve(id + "?astroPropagatedAssets", importer, {
|
|
144
|
+
skipSelf: true
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
});
|
|
114
152
|
},
|
|
115
153
|
"astro:server:setup": async ({ server }) => {
|
|
116
154
|
server.watcher.on("all", (event, entry) => {
|
package/dist/nodes/heading.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import { type Schema } from '@markdoc/markdoc';
|
|
1
|
+
import { type ConfigType, type Schema } from '@markdoc/markdoc';
|
|
2
2
|
import Slugger from 'github-slugger';
|
|
3
|
-
|
|
3
|
+
type ConfigTypeWithCtx = ConfigType & {
|
|
4
|
+
ctx: {
|
|
5
|
+
headingSlugger: Slugger;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
4
8
|
export declare const heading: Schema;
|
|
9
|
+
export declare function setupHeadingConfig(): ConfigTypeWithCtx;
|
|
10
|
+
export {};
|
package/dist/nodes/heading.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import Markdoc from "@markdoc/markdoc";
|
|
2
2
|
import Slugger from "github-slugger";
|
|
3
3
|
import { getTextContent } from "../runtime.js";
|
|
4
|
-
|
|
5
|
-
function getSlug(attributes, children) {
|
|
4
|
+
function getSlug(attributes, children, headingSlugger) {
|
|
6
5
|
if (attributes.id && typeof attributes.id === "string") {
|
|
7
6
|
return attributes.id;
|
|
8
7
|
}
|
|
@@ -22,18 +21,29 @@ const heading = {
|
|
|
22
21
|
var _a, _b;
|
|
23
22
|
const { level, ...attributes } = node.transformAttributes(config);
|
|
24
23
|
const children = node.transformChildren(config);
|
|
25
|
-
const slug = getSlug(attributes, children);
|
|
24
|
+
const slug = getSlug(attributes, children, config.ctx.headingSlugger);
|
|
26
25
|
const render = ((_b = (_a = config.nodes) == null ? void 0 : _a.heading) == null ? void 0 : _b.render) ?? `h${level}`;
|
|
27
26
|
const tagProps = (
|
|
28
27
|
// For components, pass down `level` as a prop,
|
|
29
28
|
// alongside `__collectHeading` for our `headings` collector.
|
|
30
29
|
// Avoid accidentally rendering `level` as an HTML attribute otherwise!
|
|
31
|
-
typeof render === "
|
|
30
|
+
typeof render === "string" ? { ...attributes, id: slug } : { ...attributes, id: slug, __collectHeading: true, level }
|
|
32
31
|
);
|
|
33
32
|
return new Markdoc.Tag(render, tagProps, children);
|
|
34
33
|
}
|
|
35
34
|
};
|
|
35
|
+
function setupHeadingConfig() {
|
|
36
|
+
const headingSlugger = new Slugger();
|
|
37
|
+
return {
|
|
38
|
+
ctx: {
|
|
39
|
+
headingSlugger
|
|
40
|
+
},
|
|
41
|
+
nodes: {
|
|
42
|
+
heading
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
36
46
|
export {
|
|
37
47
|
heading,
|
|
38
|
-
|
|
48
|
+
setupHeadingConfig
|
|
39
49
|
};
|
package/dist/nodes/index.d.ts
CHANGED
package/dist/nodes/index.js
CHANGED
package/dist/runtime.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { MarkdownHeading } from '@astrojs/markdown-remark';
|
|
2
2
|
import { type ConfigType as MarkdocConfig, type RenderableTreeNode } from '@markdoc/markdoc';
|
|
3
3
|
import type { ContentEntryModule } from 'astro';
|
|
4
|
-
/** Used to
|
|
4
|
+
/** Used to call `Markdoc.transform()` and `Markdoc.Ast` in runtime modules */
|
|
5
5
|
export { default as Markdoc } from '@markdoc/markdoc';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Merge user config with default config and set up context (ex. heading ID slugger)
|
|
8
|
+
* Called on each file's individual transform
|
|
9
|
+
*/
|
|
10
|
+
export declare function setupConfig(userConfig: MarkdocConfig, entry: ContentEntryModule): MarkdocConfig;
|
|
8
11
|
/**
|
|
9
12
|
* Get text content as a string from a Markdoc transform AST
|
|
10
13
|
*/
|
package/dist/runtime.js
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
import Markdoc from "@markdoc/markdoc";
|
|
2
|
-
import {
|
|
2
|
+
import { setupHeadingConfig } from "./nodes/index.js";
|
|
3
3
|
import { default as default2 } from "@markdoc/markdoc";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
function setupConfig(userConfig, entry) {
|
|
5
|
+
const defaultConfig = {
|
|
6
|
+
// `setupXConfig()` could become a "plugin" convention as well?
|
|
7
|
+
...setupHeadingConfig(),
|
|
8
|
+
variables: { entry }
|
|
9
|
+
};
|
|
10
|
+
return mergeConfig(defaultConfig, userConfig);
|
|
11
|
+
}
|
|
12
|
+
function mergeConfig(configA, configB) {
|
|
6
13
|
return {
|
|
7
|
-
...
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
...
|
|
14
|
+
...configA,
|
|
15
|
+
...configB,
|
|
16
|
+
tags: {
|
|
17
|
+
...configA.tags,
|
|
18
|
+
...configB.tags
|
|
11
19
|
},
|
|
12
20
|
nodes: {
|
|
13
|
-
...
|
|
14
|
-
...
|
|
21
|
+
...configA.nodes,
|
|
22
|
+
...configB.nodes
|
|
23
|
+
},
|
|
24
|
+
functions: {
|
|
25
|
+
...configA.functions,
|
|
26
|
+
...configB.functions
|
|
27
|
+
},
|
|
28
|
+
variables: {
|
|
29
|
+
...configA.variables,
|
|
30
|
+
...configB.variables
|
|
15
31
|
}
|
|
16
|
-
// TODO: Syntax highlighting
|
|
17
32
|
};
|
|
18
33
|
}
|
|
19
34
|
function getTextContent(childNodes) {
|
|
@@ -56,8 +71,7 @@ function collectHeadings(children) {
|
|
|
56
71
|
}
|
|
57
72
|
export {
|
|
58
73
|
default2 as Markdoc,
|
|
59
|
-
applyDefaultConfig,
|
|
60
74
|
collectHeadings,
|
|
61
75
|
getTextContent,
|
|
62
|
-
|
|
76
|
+
setupConfig
|
|
63
77
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/markdoc",
|
|
3
3
|
"description": "Add support for Markdoc pages in your Astro site",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"author": "withastro",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"zod": "^3.17.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"astro": "^2.5.
|
|
43
|
+
"astro": "^2.5.3"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@astrojs/markdown-remark": "^2.2.1",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"mocha": "^9.2.2",
|
|
54
54
|
"rollup": "^3.20.1",
|
|
55
55
|
"vite": "^4.3.1",
|
|
56
|
-
"astro": "2.5.
|
|
56
|
+
"astro": "2.5.3",
|
|
57
57
|
"astro-scripts": "0.0.14"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|