@astrojs/mdx 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +14 -0
- package/README.md +2 -17
- package/dist/astro-data-utils.d.ts +9 -0
- package/dist/astro-data-utils.js +66 -0
- package/dist/index.js +53 -43
- package/dist/utils.d.ts +2 -3
- package/dist/utils.js +4 -4
- package/package.json +4 -3
- package/src/astro-data-utils.ts +81 -0
- package/src/index.ts +67 -52
- package/src/utils.ts +2 -2
- package/test/fixtures/mdx-escape/src/components/Em.astro +7 -0
- package/test/fixtures/mdx-escape/src/components/P.astro +1 -0
- package/test/fixtures/mdx-escape/src/components/Title.astro +1 -0
- package/test/fixtures/mdx-escape/src/pages/html-tag.mdx +5 -0
- package/test/fixtures/mdx-escape/src/pages/index.mdx +13 -0
- package/test/fixtures/mdx-frontmatter/src/pages/index.mdx +3 -0
- package/test/fixtures/mdx-frontmatter-injection/astro.config.mjs +12 -0
- package/test/fixtures/mdx-frontmatter-injection/node_modules/.bin/astro +17 -0
- package/test/fixtures/mdx-frontmatter-injection/package.json +12 -0
- package/test/fixtures/mdx-frontmatter-injection/src/markdown-plugins.mjs +20 -0
- package/test/fixtures/mdx-frontmatter-injection/src/pages/glob.json.js +6 -0
- package/test/fixtures/mdx-frontmatter-injection/src/pages/page-1.mdx +3 -0
- package/test/fixtures/mdx-frontmatter-injection/src/pages/page-2.mdx +19 -0
- package/test/fixtures/mdx-frontmatter-injection/src/pages/with-overrides.mdx +7 -0
- package/test/fixtures/mdx-plus-react/astro.config.mjs +6 -0
- package/test/fixtures/mdx-plus-react/node_modules/.bin/astro +17 -0
- package/test/fixtures/mdx-plus-react/package.json +8 -0
- package/test/fixtures/mdx-plus-react/src/components/Component.jsx +5 -0
- package/test/fixtures/mdx-plus-react/src/pages/index.astro +11 -0
- package/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js +2 -2
- package/test/fixtures/mdx-remark-plugins/src/pages/headings-glob.json.js +6 -0
- package/test/mdx-escape.test.js +32 -0
- package/test/mdx-frontmatter-injection.test.js +44 -0
- package/test/mdx-plus-react.test.js +25 -0
- package/test/mdx-rehype-plugins.test.js +6 -17
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[32m@astrojs/mdx:build: [0mcache hit, replaying output [
|
|
1
|
+
[32m@astrojs/mdx:build: [0mcache hit, replaying output [2mfbb8fed20a6e13e0[0m
|
|
2
2
|
[32m@astrojs/mdx:build: [0m
|
|
3
|
-
[32m@astrojs/mdx:build: [0m> @astrojs/mdx@0.
|
|
3
|
+
[32m@astrojs/mdx:build: [0m> @astrojs/mdx@0.7.0 build /home/runner/work/astro/astro/packages/integrations/mdx
|
|
4
4
|
[32m@astrojs/mdx:build: [0m> astro-scripts build "src/**/*.ts" && tsc
|
|
5
5
|
[32m@astrojs/mdx:build: [0m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @astrojs/mdx
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4176](https://github.com/withastro/astro/pull/4176) [`2675b8633`](https://github.com/withastro/astro/commit/2675b8633c5d5c45b237ec87940d5eaf1bfb1b4b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support frontmatter injection for MD and MDX using remark and rehype plugins
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#4181](https://github.com/withastro/astro/pull/4181) [`77cede720`](https://github.com/withastro/astro/commit/77cede720b09bce34f29c3d2d8b505311ce876b1) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Make collect-headings rehype plugin non-overridable
|
|
12
|
+
|
|
13
|
+
* [#4174](https://github.com/withastro/astro/pull/4174) [`8eb3a8c6d`](https://github.com/withastro/astro/commit/8eb3a8c6d9554707963c3a3bc36ed8b68d3cf0fb) Thanks [@matthewp](https://github.com/matthewp)! - Allows using React with automatic imports alongside MDX
|
|
14
|
+
|
|
15
|
+
- [#4145](https://github.com/withastro/astro/pull/4145) [`c7efcf57e`](https://github.com/withastro/astro/commit/c7efcf57e00a0fcde3bc9f813e3cc59902bd484c) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Fix a missing newline bug when `layout` was set.
|
|
16
|
+
|
|
3
17
|
## 0.6.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -297,26 +297,11 @@ export default {
|
|
|
297
297
|
<details>
|
|
298
298
|
<summary><strong>rehypePlugins</strong></summary>
|
|
299
299
|
|
|
300
|
-
**Default plugins:** [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts)
|
|
301
|
-
|
|
302
300
|
[Rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) allow you to transform the HTML that your Markdown generates. We recommend checking the [Remark plugin](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) catalog first _before_ considering rehype plugins, since most users want to transform their Markdown syntax instead. If HTML transforms are what you need, we encourage you to browse [awesome-rehype](https://github.com/rehypejs/awesome-rehype) for a full curated list of plugins!
|
|
303
301
|
|
|
304
|
-
We apply our own [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts) plugin
|
|
305
|
-
|
|
306
|
-
To apply rehype plugins _while preserving_ Astro's default plugins, use a nested `extends` object like so:
|
|
302
|
+
We apply our own (non-overridable) [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts) plugin. This applies IDs to all headings (i.e. `h1 -> h6`) in your MDX files to [link to headings via anchor tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_page).
|
|
307
303
|
|
|
308
|
-
|
|
309
|
-
// astro.config.mjs
|
|
310
|
-
import rehypeMinifyHtml from 'rehype-minify';
|
|
311
|
-
|
|
312
|
-
export default {
|
|
313
|
-
integrations: [mdx({
|
|
314
|
-
rehypePlugins: { extends: [rehypeMinifyHtml] },
|
|
315
|
-
})],
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
To apply plugins _without_ Astro's defaults, you can apply a plain array:
|
|
304
|
+
To apply additional rehype plugins, pass an array to the `rehypePlugins` option like so:
|
|
320
305
|
|
|
321
306
|
```js
|
|
322
307
|
// astro.config.mjs
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MarkdownAstroData } from 'astro';
|
|
2
|
+
import type { Data, VFile } from 'vfile';
|
|
3
|
+
export declare function remarkInitializeAstroData(): (tree: any, vfile: VFile) => void;
|
|
4
|
+
export declare function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any>, exportName?: string): (tree: any, vfile: VFile) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Copied from markdown utils
|
|
7
|
+
* @see "vite-plugin-utils"
|
|
8
|
+
*/
|
|
9
|
+
export declare function safelyGetAstroData(vfileData: Data): MarkdownAstroData;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { name as isValidIdentifierName } from "estree-util-is-identifier-name";
|
|
2
|
+
import { jsToTreeNode } from "./utils.js";
|
|
3
|
+
function remarkInitializeAstroData() {
|
|
4
|
+
return function(tree, vfile) {
|
|
5
|
+
if (!vfile.data.astro) {
|
|
6
|
+
vfile.data.astro = { frontmatter: {} };
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function rehypeApplyFrontmatterExport(pageFrontmatter, exportName = "frontmatter") {
|
|
11
|
+
return function(tree, vfile) {
|
|
12
|
+
if (!isValidIdentifierName(exportName)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`[MDX] ${JSON.stringify(
|
|
15
|
+
exportName
|
|
16
|
+
)} is not a valid frontmatter export name! Make sure "frontmatterOptions.name" could be used as a JS export (i.e. "export const frontmatterName = ...")`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const { frontmatter: injectedFrontmatter } = safelyGetAstroData(vfile.data);
|
|
20
|
+
const frontmatter = { ...injectedFrontmatter, ...pageFrontmatter };
|
|
21
|
+
let exportNodes = [];
|
|
22
|
+
if (!exportName) {
|
|
23
|
+
exportNodes = Object.entries(frontmatter).map(([k, v]) => {
|
|
24
|
+
if (!isValidIdentifierName(k)) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`[MDX] A remark or rehype plugin tried to inject ${JSON.stringify(
|
|
27
|
+
k
|
|
28
|
+
)} as a top-level export, which is not a valid export name.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return jsToTreeNode(`export const ${k} = ${JSON.stringify(v)};`);
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
exportNodes = [jsToTreeNode(`export const ${exportName} = ${JSON.stringify(frontmatter)};`)];
|
|
35
|
+
}
|
|
36
|
+
tree.children = exportNodes.concat(tree.children);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function isValidAstroData(obj) {
|
|
40
|
+
if (typeof obj === "object" && obj !== null && obj.hasOwnProperty("frontmatter")) {
|
|
41
|
+
const { frontmatter } = obj;
|
|
42
|
+
try {
|
|
43
|
+
JSON.stringify(frontmatter);
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return typeof frontmatter === "object" && frontmatter !== null;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
function safelyGetAstroData(vfileData) {
|
|
52
|
+
const { astro } = vfileData;
|
|
53
|
+
if (!astro)
|
|
54
|
+
return { frontmatter: {} };
|
|
55
|
+
if (!isValidAstroData(astro)) {
|
|
56
|
+
throw Error(
|
|
57
|
+
`[MDX] A remark or rehype plugin tried to add invalid frontmatter. Ensure "astro.frontmatter" is a JSON object!`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return astro;
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
rehypeApplyFrontmatterExport,
|
|
64
|
+
remarkInitializeAstroData,
|
|
65
|
+
safelyGetAstroData
|
|
66
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -2,52 +2,55 @@ import { compile as mdxCompile, nodeTypes } from "@mdx-js/mdx";
|
|
|
2
2
|
import mdxPlugin from "@mdx-js/rollup";
|
|
3
3
|
import { parse as parseESM } from "es-module-lexer";
|
|
4
4
|
import rehypeRaw from "rehype-raw";
|
|
5
|
-
import remarkFrontmatter from "remark-frontmatter";
|
|
6
5
|
import remarkGfm from "remark-gfm";
|
|
7
|
-
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
|
|
8
6
|
import remarkShikiTwoslash from "remark-shiki-twoslash";
|
|
9
7
|
import remarkSmartypants from "remark-smartypants";
|
|
10
8
|
import { VFile } from "vfile";
|
|
9
|
+
import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from "./astro-data-utils.js";
|
|
11
10
|
import rehypeCollectHeadings from "./rehype-collect-headings.js";
|
|
12
11
|
import remarkPrism from "./remark-prism.js";
|
|
13
|
-
import { getFileInfo,
|
|
14
|
-
const DEFAULT_REMARK_PLUGINS = [
|
|
15
|
-
|
|
12
|
+
import { getFileInfo, parseFrontmatter } from "./utils.js";
|
|
13
|
+
const DEFAULT_REMARK_PLUGINS = [
|
|
14
|
+
remarkGfm,
|
|
15
|
+
remarkSmartypants
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_REHYPE_PLUGINS = [];
|
|
16
18
|
function handleExtends(config, defaults = []) {
|
|
17
19
|
if (Array.isArray(config))
|
|
18
20
|
return config;
|
|
19
21
|
return [...defaults, ...(config == null ? void 0 : config.extends) ?? []];
|
|
20
22
|
}
|
|
23
|
+
function getRemarkPlugins(mdxOptions, config) {
|
|
24
|
+
let remarkPlugins = [
|
|
25
|
+
remarkInitializeAstroData,
|
|
26
|
+
...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS)
|
|
27
|
+
];
|
|
28
|
+
if (config.markdown.syntaxHighlight === "shiki") {
|
|
29
|
+
const shikiTwoslash = remarkShikiTwoslash.default ?? remarkShikiTwoslash;
|
|
30
|
+
remarkPlugins.push([shikiTwoslash, config.markdown.shikiConfig]);
|
|
31
|
+
}
|
|
32
|
+
if (config.markdown.syntaxHighlight === "prism") {
|
|
33
|
+
remarkPlugins.push(remarkPrism);
|
|
34
|
+
}
|
|
35
|
+
return remarkPlugins;
|
|
36
|
+
}
|
|
37
|
+
function getRehypePlugins(mdxOptions, config) {
|
|
38
|
+
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
|
|
39
|
+
if (config.markdown.syntaxHighlight === "shiki" || config.markdown.syntaxHighlight === "prism") {
|
|
40
|
+
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
41
|
+
}
|
|
42
|
+
rehypePlugins.push(rehypeCollectHeadings);
|
|
43
|
+
return rehypePlugins;
|
|
44
|
+
}
|
|
21
45
|
function mdx(mdxOptions = {}) {
|
|
22
46
|
return {
|
|
23
47
|
name: "@astrojs/mdx",
|
|
24
48
|
hooks: {
|
|
25
49
|
"astro:config:setup": ({ updateConfig, config, addPageExtension, command }) => {
|
|
26
50
|
addPageExtension(".mdx");
|
|
27
|
-
let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
|
|
28
|
-
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
|
|
29
|
-
if (config.markdown.syntaxHighlight === "shiki") {
|
|
30
|
-
remarkPlugins.push([
|
|
31
|
-
remarkShikiTwoslash.default ?? remarkShikiTwoslash,
|
|
32
|
-
config.markdown.shikiConfig
|
|
33
|
-
]);
|
|
34
|
-
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
35
|
-
}
|
|
36
|
-
if (config.markdown.syntaxHighlight === "prism") {
|
|
37
|
-
remarkPlugins.push(remarkPrism);
|
|
38
|
-
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
39
|
-
}
|
|
40
|
-
remarkPlugins.push(remarkFrontmatter);
|
|
41
|
-
remarkPlugins.push([
|
|
42
|
-
remarkMdxFrontmatter,
|
|
43
|
-
{
|
|
44
|
-
name: "frontmatter",
|
|
45
|
-
...mdxOptions.frontmatterOptions
|
|
46
|
-
}
|
|
47
|
-
]);
|
|
48
51
|
const mdxPluginOpts = {
|
|
49
|
-
remarkPlugins,
|
|
50
|
-
rehypePlugins,
|
|
52
|
+
remarkPlugins: getRemarkPlugins(mdxOptions, config),
|
|
53
|
+
rehypePlugins: getRehypePlugins(mdxOptions, config),
|
|
51
54
|
jsx: true,
|
|
52
55
|
jsxImportSource: "astro",
|
|
53
56
|
format: "mdx",
|
|
@@ -60,28 +63,35 @@ function mdx(mdxOptions = {}) {
|
|
|
60
63
|
enforce: "pre",
|
|
61
64
|
...mdxPlugin(mdxPluginOpts),
|
|
62
65
|
async transform(code, id) {
|
|
63
|
-
var _a;
|
|
64
66
|
if (!id.endsWith("mdx"))
|
|
65
67
|
return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
let { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
|
|
69
|
+
if (frontmatter.layout) {
|
|
70
|
+
const { layout, ...contentProp } = frontmatter;
|
|
71
|
+
pageContent += `
|
|
72
|
+
|
|
71
73
|
export default async function({ children }) {
|
|
72
74
|
const Layout = (await import(${JSON.stringify(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
frontmatter.layout
|
|
76
|
+
)})).default;
|
|
75
77
|
const frontmatter=${JSON.stringify(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
contentProp
|
|
79
|
+
)};
|
|
78
80
|
return <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
|
|
79
|
-
}
|
|
80
81
|
}
|
|
81
|
-
const compiled = await mdxCompile(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const compiled = await mdxCompile(new VFile({ value: pageContent, path: id }), {
|
|
83
|
+
...mdxPluginOpts,
|
|
84
|
+
rehypePlugins: [
|
|
85
|
+
...mdxPluginOpts.rehypePlugins ?? [],
|
|
86
|
+
() => {
|
|
87
|
+
var _a;
|
|
88
|
+
return rehypeApplyFrontmatterExport(
|
|
89
|
+
frontmatter,
|
|
90
|
+
(_a = mdxOptions.frontmatterOptions) == null ? void 0 : _a.name
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
});
|
|
85
95
|
return {
|
|
86
96
|
code: String(compiled.value),
|
|
87
97
|
map: compiled.map
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Options as AcornOpts } from 'acorn';
|
|
2
2
|
import type { AstroConfig } from 'astro';
|
|
3
3
|
import type { MdxjsEsm } from 'mdast-util-mdx';
|
|
4
|
+
import matter from 'gray-matter';
|
|
4
5
|
interface FileInfo {
|
|
5
6
|
fileId: string;
|
|
6
7
|
fileUrl: string;
|
|
@@ -11,8 +12,6 @@ export declare function getFileInfo(id: string, config: AstroConfig): FileInfo;
|
|
|
11
12
|
* Match YAML exception handling from Astro core errors
|
|
12
13
|
* @see 'astro/src/core/errors.ts'
|
|
13
14
|
*/
|
|
14
|
-
export declare function
|
|
15
|
-
[key: string]: any;
|
|
16
|
-
};
|
|
15
|
+
export declare function parseFrontmatter(code: string, id: string): matter.GrayMatterFile<string>;
|
|
17
16
|
export declare function jsToTreeNode(jsString: string, acornOpts?: AcornOpts): MdxjsEsm;
|
|
18
17
|
export {};
|
package/dist/utils.js
CHANGED
|
@@ -27,9 +27,9 @@ function getFileInfo(id, config) {
|
|
|
27
27
|
}
|
|
28
28
|
return { fileId, fileUrl };
|
|
29
29
|
}
|
|
30
|
-
function
|
|
30
|
+
function parseFrontmatter(code, id) {
|
|
31
31
|
try {
|
|
32
|
-
return matter(code)
|
|
32
|
+
return matter(code);
|
|
33
33
|
} catch (e) {
|
|
34
34
|
if (e.name === "YAMLException") {
|
|
35
35
|
const err = e;
|
|
@@ -61,6 +61,6 @@ function jsToTreeNode(jsString, acornOpts = {
|
|
|
61
61
|
}
|
|
62
62
|
export {
|
|
63
63
|
getFileInfo,
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
jsToTreeNode,
|
|
65
|
+
parseFrontmatter
|
|
66
66
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/mdx",
|
|
3
3
|
"description": "Use MDX within Astro",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"author": "withastro",
|
|
@@ -37,13 +37,14 @@
|
|
|
37
37
|
"remark-shiki-twoslash": "^3.1.0",
|
|
38
38
|
"remark-smartypants": "^2.0.0",
|
|
39
39
|
"shiki": "^0.10.1",
|
|
40
|
-
"unist-util-visit": "^4.1.0"
|
|
40
|
+
"unist-util-visit": "^4.1.0",
|
|
41
|
+
"vfile": "^5.3.2"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@types/chai": "^4.3.1",
|
|
44
45
|
"@types/mocha": "^9.1.1",
|
|
45
46
|
"@types/yargs-parser": "^21.0.0",
|
|
46
|
-
"astro": "1.0.0-rc.
|
|
47
|
+
"astro": "1.0.0-rc.7",
|
|
47
48
|
"astro-scripts": "0.0.6",
|
|
48
49
|
"chai": "^4.3.6",
|
|
49
50
|
"linkedom": "^0.14.12",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { MarkdownAstroData } from 'astro';
|
|
2
|
+
import { name as isValidIdentifierName } from 'estree-util-is-identifier-name';
|
|
3
|
+
import type { MdxjsEsm } from 'mdast-util-mdx';
|
|
4
|
+
import type { Data, VFile } from 'vfile';
|
|
5
|
+
import { jsToTreeNode } from './utils.js';
|
|
6
|
+
|
|
7
|
+
export function remarkInitializeAstroData() {
|
|
8
|
+
return function (tree: any, vfile: VFile) {
|
|
9
|
+
if (!vfile.data.astro) {
|
|
10
|
+
vfile.data.astro = { frontmatter: {} };
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function rehypeApplyFrontmatterExport(
|
|
16
|
+
pageFrontmatter: Record<string, any>,
|
|
17
|
+
exportName = 'frontmatter'
|
|
18
|
+
) {
|
|
19
|
+
return function (tree: any, vfile: VFile) {
|
|
20
|
+
if (!isValidIdentifierName(exportName)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`[MDX] ${JSON.stringify(
|
|
23
|
+
exportName
|
|
24
|
+
)} is not a valid frontmatter export name! Make sure "frontmatterOptions.name" could be used as a JS export (i.e. "export const frontmatterName = ...")`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const { frontmatter: injectedFrontmatter } = safelyGetAstroData(vfile.data);
|
|
28
|
+
const frontmatter = { ...injectedFrontmatter, ...pageFrontmatter };
|
|
29
|
+
let exportNodes: MdxjsEsm[] = [];
|
|
30
|
+
if (!exportName) {
|
|
31
|
+
exportNodes = Object.entries(frontmatter).map(([k, v]) => {
|
|
32
|
+
if (!isValidIdentifierName(k)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`[MDX] A remark or rehype plugin tried to inject ${JSON.stringify(
|
|
35
|
+
k
|
|
36
|
+
)} as a top-level export, which is not a valid export name.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return jsToTreeNode(`export const ${k} = ${JSON.stringify(v)};`);
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
exportNodes = [jsToTreeNode(`export const ${exportName} = ${JSON.stringify(frontmatter)};`)];
|
|
43
|
+
}
|
|
44
|
+
tree.children = exportNodes.concat(tree.children);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Copied from markdown utils
|
|
50
|
+
* @see "vite-plugin-utils"
|
|
51
|
+
*/
|
|
52
|
+
function isValidAstroData(obj: unknown): obj is MarkdownAstroData {
|
|
53
|
+
if (typeof obj === 'object' && obj !== null && obj.hasOwnProperty('frontmatter')) {
|
|
54
|
+
const { frontmatter } = obj as any;
|
|
55
|
+
try {
|
|
56
|
+
// ensure frontmatter is JSON-serializable
|
|
57
|
+
JSON.stringify(frontmatter);
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return typeof frontmatter === 'object' && frontmatter !== null;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Copied from markdown utils
|
|
68
|
+
* @see "vite-plugin-utils"
|
|
69
|
+
*/
|
|
70
|
+
export function safelyGetAstroData(vfileData: Data): MarkdownAstroData {
|
|
71
|
+
const { astro } = vfileData;
|
|
72
|
+
|
|
73
|
+
if (!astro) return { frontmatter: {} };
|
|
74
|
+
if (!isValidAstroData(astro)) {
|
|
75
|
+
throw Error(
|
|
76
|
+
`[MDX] A remark or rehype plugin tried to add invalid frontmatter. Ensure "astro.frontmatter" is a JSON object!`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return astro;
|
|
81
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { compile as mdxCompile, nodeTypes } from '@mdx-js/mdx';
|
|
2
2
|
import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
|
|
3
|
-
import type { AstroIntegration } from 'astro';
|
|
3
|
+
import type { AstroConfig, AstroIntegration } from 'astro';
|
|
4
4
|
import { parse as parseESM } from 'es-module-lexer';
|
|
5
5
|
import rehypeRaw from 'rehype-raw';
|
|
6
|
-
import remarkFrontmatter from 'remark-frontmatter';
|
|
7
6
|
import remarkGfm from 'remark-gfm';
|
|
8
7
|
import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
|
|
9
|
-
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
|
|
10
8
|
import remarkShikiTwoslash from 'remark-shiki-twoslash';
|
|
11
9
|
import remarkSmartypants from 'remark-smartypants';
|
|
12
10
|
import { VFile } from 'vfile';
|
|
13
11
|
import type { Plugin as VitePlugin } from 'vite';
|
|
12
|
+
import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from './astro-data-utils.js';
|
|
14
13
|
import rehypeCollectHeadings from './rehype-collect-headings.js';
|
|
15
14
|
import remarkPrism from './remark-prism.js';
|
|
16
|
-
import { getFileInfo,
|
|
15
|
+
import { getFileInfo, parseFrontmatter } from './utils.js';
|
|
17
16
|
|
|
18
17
|
type WithExtends<T> = T | { extends: T };
|
|
19
18
|
|
|
@@ -28,8 +27,11 @@ type MdxOptions = {
|
|
|
28
27
|
frontmatterOptions?: RemarkMdxFrontmatterOptions;
|
|
29
28
|
};
|
|
30
29
|
|
|
31
|
-
const DEFAULT_REMARK_PLUGINS = [
|
|
32
|
-
|
|
30
|
+
const DEFAULT_REMARK_PLUGINS: MdxRollupPluginOptions['remarkPlugins'] = [
|
|
31
|
+
remarkGfm,
|
|
32
|
+
remarkSmartypants,
|
|
33
|
+
];
|
|
34
|
+
const DEFAULT_REHYPE_PLUGINS: MdxRollupPluginOptions['rehypePlugins'] = [];
|
|
33
35
|
|
|
34
36
|
function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] {
|
|
35
37
|
if (Array.isArray(config)) return config;
|
|
@@ -37,44 +39,54 @@ function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] =
|
|
|
37
39
|
return [...defaults, ...(config?.extends ?? [])];
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
function getRemarkPlugins(
|
|
43
|
+
mdxOptions: MdxOptions,
|
|
44
|
+
config: AstroConfig
|
|
45
|
+
): MdxRollupPluginOptions['remarkPlugins'] {
|
|
46
|
+
let remarkPlugins = [
|
|
47
|
+
// Initialize vfile.data.astroExports before all plugins are run
|
|
48
|
+
remarkInitializeAstroData,
|
|
49
|
+
...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS),
|
|
50
|
+
];
|
|
51
|
+
if (config.markdown.syntaxHighlight === 'shiki') {
|
|
52
|
+
// Default export still requires ".default" chaining for some reason
|
|
53
|
+
// Workarounds tried:
|
|
54
|
+
// - "import * as remarkShikiTwoslash"
|
|
55
|
+
// - "import { default as remarkShikiTwoslash }"
|
|
56
|
+
const shikiTwoslash = (remarkShikiTwoslash as any).default ?? remarkShikiTwoslash;
|
|
57
|
+
remarkPlugins.push([shikiTwoslash, config.markdown.shikiConfig]);
|
|
58
|
+
}
|
|
59
|
+
if (config.markdown.syntaxHighlight === 'prism') {
|
|
60
|
+
remarkPlugins.push(remarkPrism);
|
|
61
|
+
}
|
|
62
|
+
return remarkPlugins;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getRehypePlugins(
|
|
66
|
+
mdxOptions: MdxOptions,
|
|
67
|
+
config: AstroConfig
|
|
68
|
+
): MdxRollupPluginOptions['rehypePlugins'] {
|
|
69
|
+
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
|
|
70
|
+
|
|
71
|
+
if (config.markdown.syntaxHighlight === 'shiki' || config.markdown.syntaxHighlight === 'prism') {
|
|
72
|
+
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
73
|
+
}
|
|
74
|
+
// getHeadings() is guaranteed by TS, so we can't allow user to override
|
|
75
|
+
rehypePlugins.push(rehypeCollectHeadings);
|
|
76
|
+
|
|
77
|
+
return rehypePlugins;
|
|
78
|
+
}
|
|
79
|
+
|
|
40
80
|
export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
|
|
41
81
|
return {
|
|
42
82
|
name: '@astrojs/mdx',
|
|
43
83
|
hooks: {
|
|
44
84
|
'astro:config:setup': ({ updateConfig, config, addPageExtension, command }: any) => {
|
|
45
85
|
addPageExtension('.mdx');
|
|
46
|
-
let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
|
|
47
|
-
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
|
|
48
|
-
|
|
49
|
-
if (config.markdown.syntaxHighlight === 'shiki') {
|
|
50
|
-
remarkPlugins.push([
|
|
51
|
-
// Default export still requires ".default" chaining for some reason
|
|
52
|
-
// Workarounds tried:
|
|
53
|
-
// - "import * as remarkShikiTwoslash"
|
|
54
|
-
// - "import { default as remarkShikiTwoslash }"
|
|
55
|
-
(remarkShikiTwoslash as any).default ?? remarkShikiTwoslash,
|
|
56
|
-
config.markdown.shikiConfig,
|
|
57
|
-
]);
|
|
58
|
-
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (config.markdown.syntaxHighlight === 'prism') {
|
|
62
|
-
remarkPlugins.push(remarkPrism);
|
|
63
|
-
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
remarkPlugins.push(remarkFrontmatter);
|
|
67
|
-
remarkPlugins.push([
|
|
68
|
-
remarkMdxFrontmatter,
|
|
69
|
-
{
|
|
70
|
-
name: 'frontmatter',
|
|
71
|
-
...mdxOptions.frontmatterOptions,
|
|
72
|
-
},
|
|
73
|
-
]);
|
|
74
86
|
|
|
75
87
|
const mdxPluginOpts: MdxRollupPluginOptions = {
|
|
76
|
-
remarkPlugins,
|
|
77
|
-
rehypePlugins,
|
|
88
|
+
remarkPlugins: getRemarkPlugins(mdxOptions, config),
|
|
89
|
+
rehypePlugins: getRehypePlugins(mdxOptions, config),
|
|
78
90
|
jsx: true,
|
|
79
91
|
jsxImportSource: 'astro',
|
|
80
92
|
// Note: disable `.md` support
|
|
@@ -93,24 +105,27 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
|
|
|
93
105
|
async transform(code, id) {
|
|
94
106
|
if (!id.endsWith('mdx')) return;
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)})).default;\nconst frontmatter=${JSON.stringify(
|
|
105
|
-
content
|
|
106
|
-
)};\nreturn <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
|
|
107
|
-
}
|
|
108
|
+
let { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
|
|
109
|
+
if (frontmatter.layout) {
|
|
110
|
+
const { layout, ...contentProp } = frontmatter;
|
|
111
|
+
pageContent += `\n\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
|
|
112
|
+
frontmatter.layout
|
|
113
|
+
)})).default;\nconst frontmatter=${JSON.stringify(
|
|
114
|
+
contentProp
|
|
115
|
+
)};\nreturn <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
const compiled = await mdxCompile(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
const compiled = await mdxCompile(new VFile({ value: pageContent, path: id }), {
|
|
119
|
+
...mdxPluginOpts,
|
|
120
|
+
rehypePlugins: [
|
|
121
|
+
...(mdxPluginOpts.rehypePlugins ?? []),
|
|
122
|
+
() =>
|
|
123
|
+
rehypeApplyFrontmatterExport(
|
|
124
|
+
frontmatter,
|
|
125
|
+
mdxOptions.frontmatterOptions?.name
|
|
126
|
+
),
|
|
127
|
+
],
|
|
128
|
+
});
|
|
114
129
|
|
|
115
130
|
return {
|
|
116
131
|
code: String(compiled.value),
|
package/src/utils.ts
CHANGED
|
@@ -47,9 +47,9 @@ export function getFileInfo(id: string, config: AstroConfig): FileInfo {
|
|
|
47
47
|
* Match YAML exception handling from Astro core errors
|
|
48
48
|
* @see 'astro/src/core/errors.ts'
|
|
49
49
|
*/
|
|
50
|
-
export function
|
|
50
|
+
export function parseFrontmatter(code: string, id: string) {
|
|
51
51
|
try {
|
|
52
|
-
return matter(code)
|
|
52
|
+
return matter(code);
|
|
53
53
|
} catch (e: any) {
|
|
54
54
|
if (e.name === 'YAMLException') {
|
|
55
55
|
const err: SSRError = e;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p><slot /></p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1><slot/></h1>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import P from '../components/P.astro';
|
|
2
|
+
import Em from '../components/Em.astro';
|
|
3
|
+
import Title from '../components/Title.astro';
|
|
4
|
+
|
|
5
|
+
export const components = { p: P, em: Em, h1: Title };
|
|
6
|
+
|
|
7
|
+
# Hello _there_
|
|
8
|
+
|
|
9
|
+
# _there_
|
|
10
|
+
|
|
11
|
+
Hello _there_
|
|
12
|
+
|
|
13
|
+
_there_
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'astro/config';
|
|
2
|
+
import mdx from '@astrojs/mdx';
|
|
3
|
+
import { rehypeReadingTime, remarkTitle } from './src/markdown-plugins.mjs';
|
|
4
|
+
|
|
5
|
+
// https://astro.build/config
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
site: 'https://astro.build/',
|
|
8
|
+
integrations: [mdx({
|
|
9
|
+
remarkPlugins: [remarkTitle],
|
|
10
|
+
rehypePlugins: [rehypeReadingTime],
|
|
11
|
+
})],
|
|
12
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
|
6
|
+
esac
|
|
7
|
+
|
|
8
|
+
if [ -z "$NODE_PATH" ]; then
|
|
9
|
+
export NODE_PATH="/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
|
|
10
|
+
else
|
|
11
|
+
export NODE_PATH="$NODE_PATH:/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
|
|
12
|
+
fi
|
|
13
|
+
if [ -x "$basedir/node" ]; then
|
|
14
|
+
exec "$basedir/node" "$basedir/../../../../../../../astro/astro.js" "$@"
|
|
15
|
+
else
|
|
16
|
+
exec node "$basedir/../../../../../../../astro/astro.js" "$@"
|
|
17
|
+
fi
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@test/mdx-frontmatter-injection",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"astro": "workspace:*",
|
|
7
|
+
"@astrojs/mdx": "workspace:*",
|
|
8
|
+
"mdast-util-to-string": "^3.1.0",
|
|
9
|
+
"reading-time": "^1.5.0",
|
|
10
|
+
"unist-util-visit": "^4.1.0"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import getReadingTime from 'reading-time';
|
|
2
|
+
import { toString } from 'mdast-util-to-string';
|
|
3
|
+
import { visit } from 'unist-util-visit';
|
|
4
|
+
|
|
5
|
+
export function rehypeReadingTime() {
|
|
6
|
+
return function (tree, { data }) {
|
|
7
|
+
const readingTime = getReadingTime(toString(tree));
|
|
8
|
+
data.astro.frontmatter.injectedReadingTime = readingTime;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function remarkTitle() {
|
|
13
|
+
return function (tree, { data }) {
|
|
14
|
+
visit(tree, ['heading'], (node) => {
|
|
15
|
+
if (node.depth === 1) {
|
|
16
|
+
data.astro.frontmatter.title = toString(node.children);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
|
6
|
+
esac
|
|
7
|
+
|
|
8
|
+
if [ -z "$NODE_PATH" ]; then
|
|
9
|
+
export NODE_PATH="/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
|
|
10
|
+
else
|
|
11
|
+
export NODE_PATH="$NODE_PATH:/home/runner/work/astro/astro/node_modules/.pnpm/node_modules"
|
|
12
|
+
fi
|
|
13
|
+
if [ -x "$basedir/node" ]; then
|
|
14
|
+
exec "$basedir/node" "$basedir/../../../../../../../astro/astro.js" "$@"
|
|
15
|
+
else
|
|
16
|
+
exec node "$basedir/../../../../../../../astro/astro.js" "$@"
|
|
17
|
+
fi
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import mdx from '@astrojs/mdx';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import { parseHTML } from 'linkedom';
|
|
5
|
+
import { loadFixture } from '../../../astro/test/test-utils.js';
|
|
6
|
+
|
|
7
|
+
const FIXTURE_ROOT = new URL('./fixtures/mdx-escape/', import.meta.url);
|
|
8
|
+
|
|
9
|
+
describe('MDX frontmatter', () => {
|
|
10
|
+
let fixture;
|
|
11
|
+
before(async () => {
|
|
12
|
+
fixture = await loadFixture({
|
|
13
|
+
root: FIXTURE_ROOT,
|
|
14
|
+
integrations: [mdx()],
|
|
15
|
+
});
|
|
16
|
+
await fixture.build();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('does not have unescaped HTML at top-level', async () => {
|
|
20
|
+
const html = await fixture.readFile('/index.html');
|
|
21
|
+
const { document } = parseHTML(html);
|
|
22
|
+
|
|
23
|
+
expect(document.body.textContent).to.not.include('<em');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('does not have unescaped HTML inside html tags', async () => {
|
|
27
|
+
const html = await fixture.readFile('/html-tag/index.html');
|
|
28
|
+
const { document } = parseHTML(html);
|
|
29
|
+
|
|
30
|
+
expect(document.body.textContent).to.not.include('<em');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { loadFixture } from '../../../astro/test/test-utils.js';
|
|
3
|
+
|
|
4
|
+
const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter-injection/', import.meta.url);
|
|
5
|
+
|
|
6
|
+
describe('MDX frontmatter injection', () => {
|
|
7
|
+
let fixture;
|
|
8
|
+
|
|
9
|
+
before(async () => {
|
|
10
|
+
fixture = await loadFixture({
|
|
11
|
+
root: FIXTURE_ROOT,
|
|
12
|
+
});
|
|
13
|
+
await fixture.build();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('remark supports custom vfile data - get title', async () => {
|
|
17
|
+
const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
|
|
18
|
+
const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
|
|
19
|
+
expect(titles).to.contain('Page 1');
|
|
20
|
+
expect(titles).to.contain('Page 2');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('rehype supports custom vfile data - reading time', async () => {
|
|
24
|
+
const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
|
|
25
|
+
const readingTimes = frontmatterByPage.map(
|
|
26
|
+
(frontmatter = {}) => frontmatter.injectedReadingTime
|
|
27
|
+
);
|
|
28
|
+
expect(readingTimes.length).to.be.greaterThan(0);
|
|
29
|
+
for (let readingTime of readingTimes) {
|
|
30
|
+
expect(readingTime).to.not.be.null;
|
|
31
|
+
expect(readingTime.text).match(/^\d+ min read/);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('overrides injected frontmatter with user frontmatter', async () => {
|
|
36
|
+
const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
|
|
37
|
+
const readingTimes = frontmatterByPage.map(
|
|
38
|
+
(frontmatter = {}) => frontmatter.injectedReadingTime?.text
|
|
39
|
+
);
|
|
40
|
+
const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
|
|
41
|
+
expect(titles).to.contain('Overridden title');
|
|
42
|
+
expect(readingTimes).to.contain('1000 min read');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import mdx from '@astrojs/mdx';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import { parseHTML } from 'linkedom';
|
|
5
|
+
import { loadFixture } from '../../../astro/test/test-utils.js';
|
|
6
|
+
|
|
7
|
+
describe('MDX and React', () => {
|
|
8
|
+
let fixture;
|
|
9
|
+
|
|
10
|
+
before(async () => {
|
|
11
|
+
fixture = await loadFixture({
|
|
12
|
+
root: new URL('./fixtures/mdx-plus-react/', import.meta.url),
|
|
13
|
+
});
|
|
14
|
+
await fixture.build();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('can be used in the same project', async () => {
|
|
18
|
+
const html = await fixture.readFile('/index.html');
|
|
19
|
+
const { document } = parseHTML(html);
|
|
20
|
+
|
|
21
|
+
const p = document.querySelector('p');
|
|
22
|
+
|
|
23
|
+
expect(p.textContent).to.equal('Hello world');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import mdx from '@astrojs/mdx';
|
|
2
|
-
import { jsToTreeNode } from '../dist/utils.js';
|
|
3
2
|
|
|
4
|
-
import { expect } from 'chai';
|
|
5
|
-
import { parseHTML } from 'linkedom';
|
|
6
3
|
import getReadingTime from 'reading-time';
|
|
7
4
|
import { toString } from 'mdast-util-to-string';
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import { parseHTML } from 'linkedom';
|
|
7
|
+
import { jsToTreeNode } from '../dist/utils.js';
|
|
8
8
|
|
|
9
9
|
import { loadFixture } from '../../../astro/test/test-utils.js';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
return function (tree) {
|
|
11
|
+
function rehypeReadingTime() {
|
|
12
|
+
return function (tree, { data }) {
|
|
13
13
|
const readingTime = getReadingTime(toString(tree));
|
|
14
14
|
tree.children.unshift(
|
|
15
15
|
jsToTreeNode(`export const readingTime = ${JSON.stringify(readingTime)}`)
|
|
@@ -34,19 +34,8 @@ describe('MDX rehype plugins', () => {
|
|
|
34
34
|
await fixture.build();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it('removes default getHeadings', async () => {
|
|
38
|
-
const html = await fixture.readFile('/space-ipsum/index.html');
|
|
39
|
-
const { document } = parseHTML(html);
|
|
40
|
-
|
|
41
|
-
const headings = [...document.querySelectorAll('h1, h2')];
|
|
42
|
-
expect(headings.length).to.be.greaterThan(0);
|
|
43
|
-
for (const heading of headings) {
|
|
44
|
-
expect(heading.id).to.be.empty;
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
37
|
it('supports custom rehype plugins - reading time', async () => {
|
|
49
|
-
const readingTime = JSON.parse(await fixture.readFile('/reading-time.json'));
|
|
38
|
+
const { readingTime } = JSON.parse(await fixture.readFile('/reading-time.json'));
|
|
50
39
|
|
|
51
40
|
expect(readingTime).to.not.be.null;
|
|
52
41
|
expect(readingTime.text).to.match(/^\d+ min read/);
|