@astrojs/markdoc 0.0.0-data-collections-20230418125011
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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +129 -0
- package/LICENSE +61 -0
- package/README.md +326 -0
- package/components/Renderer.astro +17 -0
- package/components/TreeNode.ts +81 -0
- package/components/index.ts +2 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +6 -0
- package/dist/default-config.d.ts +5 -0
- package/dist/default-config.js +13 -0
- package/dist/experimental-assets-config.d.ts +2 -0
- package/dist/experimental-assets-config.js +25 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +114 -0
- package/dist/load-config.d.ts +14 -0
- package/dist/load-config.js +82 -0
- package/dist/utils.d.ts +40 -0
- package/dist/utils.js +60 -0
- package/package.json +62 -0
- package/src/config.ts +5 -0
- package/src/default-config.ts +18 -0
- package/src/experimental-assets-config.ts +29 -0
- package/src/index.ts +164 -0
- package/src/load-config.ts +102 -0
- package/src/utils.ts +98 -0
- package/template/content-module-types.d.ts +7 -0
- package/test/content-collections.test.js +110 -0
- package/test/entry-prop.test.js +58 -0
- package/test/fixtures/content-collections/astro.config.mjs +7 -0
- package/test/fixtures/content-collections/node_modules/.bin/astro +17 -0
- package/test/fixtures/content-collections/package.json +9 -0
- package/test/fixtures/content-collections/src/content/blog/post-1.mdoc +7 -0
- package/test/fixtures/content-collections/src/content/blog/post-2.mdoc +7 -0
- package/test/fixtures/content-collections/src/content/blog/post-3.mdoc +7 -0
- package/test/fixtures/content-collections/src/content/config.ts +12 -0
- package/test/fixtures/content-collections/src/pages/collection.json.js +10 -0
- package/test/fixtures/content-collections/src/pages/entry.json.js +10 -0
- package/test/fixtures/content-collections/utils.js +8 -0
- package/test/fixtures/entry-prop/astro.config.mjs +7 -0
- package/test/fixtures/entry-prop/node_modules/.bin/astro +17 -0
- package/test/fixtures/entry-prop/package.json +9 -0
- package/test/fixtures/entry-prop/src/content/blog/entry.mdoc +9 -0
- package/test/fixtures/entry-prop/src/content/config.ts +9 -0
- package/test/fixtures/entry-prop/src/pages/index.astro +19 -0
- package/test/fixtures/image-assets/astro.config.mjs +10 -0
- package/test/fixtures/image-assets/node_modules/.bin/astro +17 -0
- package/test/fixtures/image-assets/package.json +9 -0
- package/test/fixtures/image-assets/src/assets/alias/cityscape.jpg +0 -0
- package/test/fixtures/image-assets/src/assets/relative/oar.jpg +0 -0
- package/test/fixtures/image-assets/src/content/docs/intro.mdoc +7 -0
- package/test/fixtures/image-assets/src/pages/index.astro +19 -0
- package/test/fixtures/image-assets/src/public/favicon.svg +9 -0
- package/test/fixtures/render-null/astro.config.mjs +7 -0
- package/test/fixtures/render-null/markdoc.config.mjs +26 -0
- package/test/fixtures/render-null/node_modules/.bin/astro +17 -0
- package/test/fixtures/render-null/package.json +9 -0
- package/test/fixtures/render-null/src/content/blog/render-null.mdoc +7 -0
- package/test/fixtures/render-null/src/pages/index.astro +19 -0
- package/test/fixtures/render-simple/astro.config.mjs +7 -0
- package/test/fixtures/render-simple/node_modules/.bin/astro +17 -0
- package/test/fixtures/render-simple/package.json +9 -0
- package/test/fixtures/render-simple/src/content/blog/simple.mdoc +7 -0
- package/test/fixtures/render-simple/src/pages/index.astro +19 -0
- package/test/fixtures/render-with-components/astro.config.mjs +7 -0
- package/test/fixtures/render-with-components/markdoc.config.mjs +28 -0
- package/test/fixtures/render-with-components/node_modules/.bin/astro +17 -0
- package/test/fixtures/render-with-components/package.json +12 -0
- package/test/fixtures/render-with-components/src/components/Code.astro +12 -0
- package/test/fixtures/render-with-components/src/components/CustomMarquee.astro +1 -0
- package/test/fixtures/render-with-components/src/content/blog/with-components.mdoc +17 -0
- package/test/fixtures/render-with-components/src/pages/index.astro +19 -0
- package/test/fixtures/render-with-config/astro.config.mjs +7 -0
- package/test/fixtures/render-with-config/markdoc.config.mjs +15 -0
- package/test/fixtures/render-with-config/node_modules/.bin/astro +17 -0
- package/test/fixtures/render-with-config/package.json +9 -0
- package/test/fixtures/render-with-config/src/content/blog/with-config.mdoc +13 -0
- package/test/fixtures/render-with-config/src/pages/index.astro +19 -0
- package/test/image-assets.test.js +76 -0
- package/test/render.test.js +150 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Markdoc from "@markdoc/markdoc";
|
|
2
|
+
import { Image } from "astro:assets";
|
|
3
|
+
const experimentalAssetsConfig = {
|
|
4
|
+
nodes: {
|
|
5
|
+
image: {
|
|
6
|
+
attributes: {
|
|
7
|
+
...Markdoc.nodes.image.attributes,
|
|
8
|
+
__optimizedSrc: { type: "Object" }
|
|
9
|
+
},
|
|
10
|
+
transform(node, config) {
|
|
11
|
+
const attributes = node.transformAttributes(config);
|
|
12
|
+
const children = node.transformChildren(config);
|
|
13
|
+
if (node.type === "image" && "__optimizedSrc" in node.attributes) {
|
|
14
|
+
const { __optimizedSrc, ...rest } = node.attributes;
|
|
15
|
+
return new Markdoc.Tag(Image, { ...rest, src: __optimizedSrc }, children);
|
|
16
|
+
} else {
|
|
17
|
+
return new Markdoc.Tag("img", attributes, children);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export {
|
|
24
|
+
experimentalAssetsConfig
|
|
25
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Markdoc from "@markdoc/markdoc";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from "./utils.js";
|
|
5
|
+
import { emitESMImage } from "astro/assets";
|
|
6
|
+
import { bold, red } from "kleur/colors";
|
|
7
|
+
import { applyDefaultConfig } from "./default-config.js";
|
|
8
|
+
import { loadMarkdocConfig } from "./load-config.js";
|
|
9
|
+
function markdocIntegration(legacyConfig) {
|
|
10
|
+
if (legacyConfig) {
|
|
11
|
+
console.log(
|
|
12
|
+
`${red(
|
|
13
|
+
bold("[Markdoc]")
|
|
14
|
+
)} Passing Markdoc config from your \`astro.config\` is no longer supported. Configuration should be exported from a \`markdoc.config.mjs\` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration`
|
|
15
|
+
);
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
name: "@astrojs/markdoc",
|
|
20
|
+
hooks: {
|
|
21
|
+
"astro:config:setup": async (params) => {
|
|
22
|
+
const { config: astroConfig, addContentEntryType } = params;
|
|
23
|
+
const configLoadResult = await loadMarkdocConfig(astroConfig);
|
|
24
|
+
const userMarkdocConfig = (configLoadResult == null ? void 0 : configLoadResult.config) ?? {};
|
|
25
|
+
function getEntryInfo({ fileUrl, contents }) {
|
|
26
|
+
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
27
|
+
return {
|
|
28
|
+
data: parsed.data,
|
|
29
|
+
body: parsed.content,
|
|
30
|
+
slug: parsed.data.slug,
|
|
31
|
+
rawData: parsed.matter
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
addContentEntryType({
|
|
35
|
+
extensions: [".mdoc"],
|
|
36
|
+
getEntryInfo,
|
|
37
|
+
async getRenderModule({ entry, viteId }) {
|
|
38
|
+
const ast = Markdoc.parse(entry.body);
|
|
39
|
+
const pluginContext = this;
|
|
40
|
+
const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry });
|
|
41
|
+
const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
|
|
42
|
+
return e.error.id !== "variable-undefined";
|
|
43
|
+
});
|
|
44
|
+
if (validationErrors.length) {
|
|
45
|
+
throw new MarkdocError({
|
|
46
|
+
message: [
|
|
47
|
+
`**${String(entry.collection)} \u2192 ${String(entry.id)}** failed to validate:`,
|
|
48
|
+
...validationErrors.map((e) => e.error.id)
|
|
49
|
+
].join("\n")
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (astroConfig.experimental.assets) {
|
|
53
|
+
await emitOptimizedImages(ast.children, {
|
|
54
|
+
astroConfig,
|
|
55
|
+
pluginContext,
|
|
56
|
+
filePath: entry._internal.filePath
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const code = {
|
|
60
|
+
code: `import { jsx as h } from 'astro/jsx-runtime';
|
|
61
|
+
import { applyDefaultConfig } from '@astrojs/markdoc/default-config';
|
|
62
|
+
import { Renderer } from '@astrojs/markdoc/components';
|
|
63
|
+
import * as entry from ${JSON.stringify(viteId + "?astroContentCollectionEntry")};${configLoadResult ? `
|
|
64
|
+
import userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};` : ""}${astroConfig.experimental.assets ? `
|
|
65
|
+
import { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';` : ""}
|
|
66
|
+
const stringifiedAst = ${JSON.stringify(
|
|
67
|
+
/* Double stringify to encode *as* stringified JSON */
|
|
68
|
+
JSON.stringify(ast)
|
|
69
|
+
)};
|
|
70
|
+
export async function Content (props) {
|
|
71
|
+
const config = applyDefaultConfig(${configLoadResult ? "{ ...userConfig, variables: { ...userConfig.variables, ...props } }" : "{ variables: props }"}, { entry });${astroConfig.experimental.assets ? `
|
|
72
|
+
config.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };` : ""}
|
|
73
|
+
return h(Renderer, { stringifiedAst, config }); };`
|
|
74
|
+
};
|
|
75
|
+
return code;
|
|
76
|
+
},
|
|
77
|
+
contentModuleTypes: await fs.promises.readFile(
|
|
78
|
+
new URL("../template/content-module-types.d.ts", import.meta.url),
|
|
79
|
+
"utf-8"
|
|
80
|
+
)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function emitOptimizedImages(nodeChildren, ctx) {
|
|
87
|
+
for (const node of nodeChildren) {
|
|
88
|
+
if (node.type === "image" && typeof node.attributes.src === "string" && shouldOptimizeImage(node.attributes.src)) {
|
|
89
|
+
const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath);
|
|
90
|
+
if ((resolved == null ? void 0 : resolved.id) && fs.existsSync(new URL(prependForwardSlash(resolved.id), "file://"))) {
|
|
91
|
+
const src = await emitESMImage(
|
|
92
|
+
resolved.id,
|
|
93
|
+
ctx.pluginContext.meta.watchMode,
|
|
94
|
+
ctx.pluginContext.emitFile,
|
|
95
|
+
{ config: ctx.astroConfig }
|
|
96
|
+
);
|
|
97
|
+
node.attributes.__optimizedSrc = src;
|
|
98
|
+
} else {
|
|
99
|
+
throw new MarkdocError({
|
|
100
|
+
message: `Could not resolve image ${JSON.stringify(
|
|
101
|
+
node.attributes.src
|
|
102
|
+
)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
await emitOptimizedImages(node.children, ctx);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function shouldOptimizeImage(src) {
|
|
110
|
+
return !isValidUrl(src) && !src.startsWith("/");
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
markdocIntegration as default
|
|
114
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AstroConfig } from 'astro';
|
|
2
|
+
export declare function loadMarkdocConfig(astroConfig: Pick<AstroConfig, 'root'>): Promise<{
|
|
3
|
+
config: Readonly<Partial<{
|
|
4
|
+
nodes: Partial<Record<import("@markdoc/markdoc").NodeType, import("@markdoc/markdoc").Schema<Readonly<Partial<any>>, string>>>;
|
|
5
|
+
tags: Record<string, import("@markdoc/markdoc").Schema<Readonly<Partial<any>>, string>>;
|
|
6
|
+
variables: Record<string, any>;
|
|
7
|
+
functions: Record<string, import("@markdoc/markdoc").ConfigFunction>;
|
|
8
|
+
partials: Record<string, any>;
|
|
9
|
+
validation?: {
|
|
10
|
+
validateFunctions?: boolean | undefined;
|
|
11
|
+
} | undefined;
|
|
12
|
+
}>>;
|
|
13
|
+
fileUrl: URL;
|
|
14
|
+
} | undefined>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { build as esbuild } from "esbuild";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const SUPPORTED_MARKDOC_CONFIG_FILES = [
|
|
5
|
+
"markdoc.config.js",
|
|
6
|
+
"markdoc.config.mjs",
|
|
7
|
+
"markdoc.config.mts",
|
|
8
|
+
"markdoc.config.ts"
|
|
9
|
+
];
|
|
10
|
+
async function loadMarkdocConfig(astroConfig) {
|
|
11
|
+
let markdocConfigUrl;
|
|
12
|
+
for (const filename of SUPPORTED_MARKDOC_CONFIG_FILES) {
|
|
13
|
+
const filePath = new URL(filename, astroConfig.root);
|
|
14
|
+
if (!fs.existsSync(filePath))
|
|
15
|
+
continue;
|
|
16
|
+
markdocConfigUrl = filePath;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
if (!markdocConfigUrl)
|
|
20
|
+
return;
|
|
21
|
+
const { code, dependencies } = await bundleConfigFile({
|
|
22
|
+
markdocConfigUrl,
|
|
23
|
+
astroConfig
|
|
24
|
+
});
|
|
25
|
+
const config = await loadConfigFromBundledFile(astroConfig.root, code);
|
|
26
|
+
return {
|
|
27
|
+
config,
|
|
28
|
+
fileUrl: markdocConfigUrl
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function bundleConfigFile({
|
|
32
|
+
markdocConfigUrl,
|
|
33
|
+
astroConfig
|
|
34
|
+
}) {
|
|
35
|
+
const result = await esbuild({
|
|
36
|
+
absWorkingDir: fileURLToPath(astroConfig.root),
|
|
37
|
+
entryPoints: [fileURLToPath(markdocConfigUrl)],
|
|
38
|
+
outfile: "out.js",
|
|
39
|
+
write: false,
|
|
40
|
+
target: ["node16"],
|
|
41
|
+
platform: "node",
|
|
42
|
+
packages: "external",
|
|
43
|
+
bundle: true,
|
|
44
|
+
format: "esm",
|
|
45
|
+
sourcemap: "inline",
|
|
46
|
+
metafile: true,
|
|
47
|
+
plugins: [
|
|
48
|
+
{
|
|
49
|
+
name: "stub-astro-imports",
|
|
50
|
+
setup(build) {
|
|
51
|
+
build.onResolve({ filter: /.*\.astro$/ }, () => {
|
|
52
|
+
return {
|
|
53
|
+
// Stub with an unused default export
|
|
54
|
+
path: "data:text/javascript,export default true",
|
|
55
|
+
external: true
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
const { text } = result.outputFiles[0];
|
|
63
|
+
return {
|
|
64
|
+
code: text,
|
|
65
|
+
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function loadConfigFromBundledFile(root, code) {
|
|
69
|
+
const tmpFileUrl = new URL(`markdoc.config.timestamp-${Date.now()}.mjs`, root);
|
|
70
|
+
fs.writeFileSync(tmpFileUrl, code);
|
|
71
|
+
try {
|
|
72
|
+
return (await import(tmpFileUrl.pathname)).default;
|
|
73
|
+
} finally {
|
|
74
|
+
try {
|
|
75
|
+
fs.unlinkSync(tmpFileUrl);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
loadMarkdocConfig
|
|
82
|
+
};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import matter from 'gray-matter';
|
|
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>;
|
|
7
|
+
/**
|
|
8
|
+
* Matches AstroError object with types like error codes stubbed out
|
|
9
|
+
* @see 'astro/src/core/errors/errors.ts'
|
|
10
|
+
*/
|
|
11
|
+
export declare class MarkdocError extends Error {
|
|
12
|
+
errorCode: number;
|
|
13
|
+
loc: ErrorLocation | undefined;
|
|
14
|
+
title: string | undefined;
|
|
15
|
+
hint: string | undefined;
|
|
16
|
+
frame: string | undefined;
|
|
17
|
+
type: string;
|
|
18
|
+
constructor(props: ErrorProperties, ...params: any);
|
|
19
|
+
}
|
|
20
|
+
interface ErrorLocation {
|
|
21
|
+
file?: string;
|
|
22
|
+
line?: number;
|
|
23
|
+
column?: number;
|
|
24
|
+
}
|
|
25
|
+
interface ErrorProperties {
|
|
26
|
+
code?: number;
|
|
27
|
+
title?: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
message?: string;
|
|
30
|
+
location?: ErrorLocation;
|
|
31
|
+
hint?: string;
|
|
32
|
+
stack?: string;
|
|
33
|
+
frame?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @see 'astro/src/core/path.ts'
|
|
37
|
+
*/
|
|
38
|
+
export declare function prependForwardSlash(str: string): string;
|
|
39
|
+
export declare function isValidUrl(str: string): boolean;
|
|
40
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
class MarkdocError extends Error {
|
|
19
|
+
constructor(props, ...params) {
|
|
20
|
+
super(...params);
|
|
21
|
+
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;
|
|
35
|
+
this.title = title;
|
|
36
|
+
if (message)
|
|
37
|
+
this.message = message;
|
|
38
|
+
this.stack = stack ? stack : this.stack;
|
|
39
|
+
this.loc = location;
|
|
40
|
+
this.hint = hint;
|
|
41
|
+
this.frame = frame;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function prependForwardSlash(str) {
|
|
45
|
+
return str[0] === "/" ? str : "/" + str;
|
|
46
|
+
}
|
|
47
|
+
function isValidUrl(str) {
|
|
48
|
+
try {
|
|
49
|
+
new URL(str);
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
MarkdocError,
|
|
57
|
+
isValidUrl,
|
|
58
|
+
parseFrontmatter,
|
|
59
|
+
prependForwardSlash
|
|
60
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astrojs/markdoc",
|
|
3
|
+
"description": "Add support for Markdoc pages in your Astro site",
|
|
4
|
+
"version": "0.0.0-data-collections-20230418125011",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"author": "withastro",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/withastro/astro.git",
|
|
12
|
+
"directory": "packages/integrations/markdoc"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"astro-integration",
|
|
16
|
+
"astro-component",
|
|
17
|
+
"markdoc"
|
|
18
|
+
],
|
|
19
|
+
"bugs": "https://github.com/withastro/astro/issues",
|
|
20
|
+
"homepage": "https://docs.astro.build/en/guides/integrations-guide/markdoc/",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./dist/index.js",
|
|
23
|
+
"./components": "./components/index.ts",
|
|
24
|
+
"./default-config": "./dist/default-config.js",
|
|
25
|
+
"./config": "./dist/config.js",
|
|
26
|
+
"./experimental-assets-config": "./dist/experimental-assets-config.js",
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@markdoc/markdoc": "^0.2.2",
|
|
31
|
+
"esbuild": "^0.17.12",
|
|
32
|
+
"gray-matter": "^4.0.3",
|
|
33
|
+
"kleur": "^4.1.5",
|
|
34
|
+
"zod": "^3.17.3"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"astro": "0.0.0-data-collections-20230418125011"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/chai": "^4.3.1",
|
|
41
|
+
"@types/html-escaper": "^3.0.0",
|
|
42
|
+
"@types/mocha": "^9.1.1",
|
|
43
|
+
"chai": "^4.3.6",
|
|
44
|
+
"devalue": "^4.2.0",
|
|
45
|
+
"linkedom": "^0.14.12",
|
|
46
|
+
"mocha": "^9.2.2",
|
|
47
|
+
"rollup": "^3.20.1",
|
|
48
|
+
"vite": "^4.0.3",
|
|
49
|
+
"astro": "0.0.0-data-collections-20230418125011",
|
|
50
|
+
"astro-scripts": "0.0.14"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=16.12.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
|
57
|
+
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
|
58
|
+
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
|
59
|
+
"test": "mocha --exit --timeout 20000",
|
|
60
|
+
"test:match": "mocha --timeout 20000 -g"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc';
|
|
2
|
+
import type { ContentEntryModule } from 'astro';
|
|
3
|
+
|
|
4
|
+
export function applyDefaultConfig(
|
|
5
|
+
config: MarkdocConfig,
|
|
6
|
+
ctx: {
|
|
7
|
+
entry: ContentEntryModule;
|
|
8
|
+
}
|
|
9
|
+
): MarkdocConfig {
|
|
10
|
+
return {
|
|
11
|
+
...config,
|
|
12
|
+
variables: {
|
|
13
|
+
entry: ctx.entry,
|
|
14
|
+
...config.variables,
|
|
15
|
+
},
|
|
16
|
+
// TODO: heading ID calculation, Shiki syntax highlighting
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Config as MarkdocConfig } from '@markdoc/markdoc';
|
|
2
|
+
import Markdoc from '@markdoc/markdoc';
|
|
3
|
+
//@ts-expect-error Cannot find module 'astro:assets' or its corresponding type declarations.
|
|
4
|
+
import { Image } from 'astro:assets';
|
|
5
|
+
|
|
6
|
+
// Separate module to only import `astro:assets` when
|
|
7
|
+
// `experimental.assets` flag is set in a project.
|
|
8
|
+
// TODO: merge with `./default-config.ts` when `experimental.assets` is baselined.
|
|
9
|
+
export const experimentalAssetsConfig: MarkdocConfig = {
|
|
10
|
+
nodes: {
|
|
11
|
+
image: {
|
|
12
|
+
attributes: {
|
|
13
|
+
...Markdoc.nodes.image.attributes,
|
|
14
|
+
__optimizedSrc: { type: 'Object' },
|
|
15
|
+
},
|
|
16
|
+
transform(node, config) {
|
|
17
|
+
const attributes = node.transformAttributes(config);
|
|
18
|
+
const children = node.transformChildren(config);
|
|
19
|
+
|
|
20
|
+
if (node.type === 'image' && '__optimizedSrc' in node.attributes) {
|
|
21
|
+
const { __optimizedSrc, ...rest } = node.attributes;
|
|
22
|
+
return new Markdoc.Tag(Image, { ...rest, src: __optimizedSrc }, children);
|
|
23
|
+
} else {
|
|
24
|
+
return new Markdoc.Tag('img', attributes, children);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Node } from '@markdoc/markdoc';
|
|
2
|
+
import Markdoc from '@markdoc/markdoc';
|
|
3
|
+
import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from './utils.js';
|
|
7
|
+
// @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations.
|
|
8
|
+
import { emitESMImage } from 'astro/assets';
|
|
9
|
+
import { bold, red } from 'kleur/colors';
|
|
10
|
+
import type * as rollup from 'rollup';
|
|
11
|
+
import { applyDefaultConfig } from './default-config.js';
|
|
12
|
+
import { loadMarkdocConfig } from './load-config.js';
|
|
13
|
+
|
|
14
|
+
type SetupHookParams = HookParameters<'astro:config:setup'> & {
|
|
15
|
+
// `contentEntryType` is not a public API
|
|
16
|
+
// Add type defs here
|
|
17
|
+
addContentEntryType: (contentEntryType: ContentEntryType) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function markdocIntegration(legacyConfig: any): AstroIntegration {
|
|
21
|
+
if (legacyConfig) {
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.log(
|
|
24
|
+
`${red(
|
|
25
|
+
bold('[Markdoc]')
|
|
26
|
+
)} Passing Markdoc config from your \`astro.config\` is no longer supported. Configuration should be exported from a \`markdoc.config.mjs\` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration`
|
|
27
|
+
);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
name: '@astrojs/markdoc',
|
|
32
|
+
hooks: {
|
|
33
|
+
'astro:config:setup': async (params) => {
|
|
34
|
+
const { config: astroConfig, addContentEntryType } = params as SetupHookParams;
|
|
35
|
+
|
|
36
|
+
const configLoadResult = await loadMarkdocConfig(astroConfig);
|
|
37
|
+
const userMarkdocConfig = configLoadResult?.config ?? {};
|
|
38
|
+
|
|
39
|
+
function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
|
|
40
|
+
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
41
|
+
return {
|
|
42
|
+
data: parsed.data,
|
|
43
|
+
body: parsed.content,
|
|
44
|
+
slug: parsed.data.slug,
|
|
45
|
+
rawData: parsed.matter,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
addContentEntryType({
|
|
49
|
+
extensions: ['.mdoc'],
|
|
50
|
+
getEntryInfo,
|
|
51
|
+
async getRenderModule({ entry, viteId }) {
|
|
52
|
+
const ast = Markdoc.parse(entry.body);
|
|
53
|
+
const pluginContext = this;
|
|
54
|
+
const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry });
|
|
55
|
+
|
|
56
|
+
const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
|
|
57
|
+
// Ignore `variable-undefined` errors.
|
|
58
|
+
// Variables can be configured at runtime,
|
|
59
|
+
// so we cannot validate them at build time.
|
|
60
|
+
return e.error.id !== 'variable-undefined';
|
|
61
|
+
});
|
|
62
|
+
if (validationErrors.length) {
|
|
63
|
+
throw new MarkdocError({
|
|
64
|
+
message: [
|
|
65
|
+
`**${String(entry.collection)} → ${String(entry.id)}** failed to validate:`,
|
|
66
|
+
...validationErrors.map((e) => e.error.id),
|
|
67
|
+
].join('\n'),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (astroConfig.experimental.assets) {
|
|
72
|
+
await emitOptimizedImages(ast.children, {
|
|
73
|
+
astroConfig,
|
|
74
|
+
pluginContext,
|
|
75
|
+
filePath: entry._internal.filePath,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const code = {
|
|
80
|
+
code: `import { jsx as h } from 'astro/jsx-runtime';
|
|
81
|
+
import { applyDefaultConfig } from '@astrojs/markdoc/default-config';
|
|
82
|
+
import { Renderer } from '@astrojs/markdoc/components';
|
|
83
|
+
import * as entry from ${JSON.stringify(viteId + '?astroContentCollectionEntry')};${
|
|
84
|
+
configLoadResult
|
|
85
|
+
? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};`
|
|
86
|
+
: ''
|
|
87
|
+
}${
|
|
88
|
+
astroConfig.experimental.assets
|
|
89
|
+
? `\nimport { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';`
|
|
90
|
+
: ''
|
|
91
|
+
}
|
|
92
|
+
const stringifiedAst = ${JSON.stringify(
|
|
93
|
+
/* Double stringify to encode *as* stringified JSON */ JSON.stringify(ast)
|
|
94
|
+
)};
|
|
95
|
+
export async function Content (props) {
|
|
96
|
+
const config = applyDefaultConfig(${
|
|
97
|
+
configLoadResult
|
|
98
|
+
? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }'
|
|
99
|
+
: '{ variables: props }'
|
|
100
|
+
}, { entry });${
|
|
101
|
+
astroConfig.experimental.assets
|
|
102
|
+
? `\nconfig.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };`
|
|
103
|
+
: ''
|
|
104
|
+
}
|
|
105
|
+
return h(Renderer, { stringifiedAst, config }); };`,
|
|
106
|
+
};
|
|
107
|
+
return code;
|
|
108
|
+
},
|
|
109
|
+
contentModuleTypes: await fs.promises.readFile(
|
|
110
|
+
new URL('../template/content-module-types.d.ts', import.meta.url),
|
|
111
|
+
'utf-8'
|
|
112
|
+
),
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Emits optimized images, and appends the generated `src` to each AST node
|
|
121
|
+
* via the `__optimizedSrc` attribute.
|
|
122
|
+
*/
|
|
123
|
+
async function emitOptimizedImages(
|
|
124
|
+
nodeChildren: Node[],
|
|
125
|
+
ctx: {
|
|
126
|
+
pluginContext: rollup.PluginContext;
|
|
127
|
+
filePath: string;
|
|
128
|
+
astroConfig: AstroConfig;
|
|
129
|
+
}
|
|
130
|
+
) {
|
|
131
|
+
for (const node of nodeChildren) {
|
|
132
|
+
if (
|
|
133
|
+
node.type === 'image' &&
|
|
134
|
+
typeof node.attributes.src === 'string' &&
|
|
135
|
+
shouldOptimizeImage(node.attributes.src)
|
|
136
|
+
) {
|
|
137
|
+
// Attempt to resolve source with Vite.
|
|
138
|
+
// This handles relative paths and configured aliases
|
|
139
|
+
const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath);
|
|
140
|
+
|
|
141
|
+
if (resolved?.id && fs.existsSync(new URL(prependForwardSlash(resolved.id), 'file://'))) {
|
|
142
|
+
const src = await emitESMImage(
|
|
143
|
+
resolved.id,
|
|
144
|
+
ctx.pluginContext.meta.watchMode,
|
|
145
|
+
ctx.pluginContext.emitFile,
|
|
146
|
+
{ config: ctx.astroConfig }
|
|
147
|
+
);
|
|
148
|
+
node.attributes.__optimizedSrc = src;
|
|
149
|
+
} else {
|
|
150
|
+
throw new MarkdocError({
|
|
151
|
+
message: `Could not resolve image ${JSON.stringify(
|
|
152
|
+
node.attributes.src
|
|
153
|
+
)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
await emitOptimizedImages(node.children, ctx);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function shouldOptimizeImage(src: string) {
|
|
162
|
+
// Optimize anything that is NOT external or an absolute path to `public/`
|
|
163
|
+
return !isValidUrl(src) && !src.startsWith('/');
|
|
164
|
+
}
|