@astrojs/mdx 0.3.1 → 0.6.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 +5 -5
- package/CHANGELOG.md +29 -0
- package/README.md +68 -11
- package/dist/index.js +45 -15
- package/dist/rehype-collect-headings.d.ts +6 -0
- package/dist/rehype-collect-headings.js +47 -0
- package/dist/remark-prism.js +2 -34
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +37 -1
- package/package.json +11 -8
- package/src/index.ts +52 -23
- package/src/rehype-collect-headings.ts +50 -0
- package/src/remark-prism.ts +2 -44
- package/src/utils.ts +47 -1
- package/test/fixtures/mdx-frontmatter/src/layouts/Base.astro +28 -0
- package/test/fixtures/mdx-frontmatter/src/pages/index.mdx +2 -1
- package/test/fixtures/mdx-frontmatter/src/pages/with-headings.mdx +7 -0
- package/test/fixtures/mdx-get-headings/src/pages/pages.json.js +11 -0
- package/test/fixtures/mdx-get-headings/src/pages/test-with-jsx-expressions.mdx +8 -0
- package/test/fixtures/mdx-get-headings/src/pages/test.mdx +9 -0
- package/test/fixtures/mdx-page/astro.config.ts +5 -0
- package/test/fixtures/mdx-page/node_modules/.bin/astro +17 -0
- package/test/fixtures/mdx-page/package.json +7 -0
- package/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js +7 -0
- package/test/fixtures/mdx-rehype-plugins/src/pages/space-ipsum.mdx +25 -0
- package/test/mdx-frontmatter.test.js +42 -11
- package/test/mdx-get-headings.test.js +60 -0
- package/test/mdx-page.test.js +0 -1
- package/test/mdx-rehype-plugins.test.js +81 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[
|
|
2
|
-
[
|
|
3
|
-
[
|
|
4
|
-
[
|
|
5
|
-
[
|
|
1
|
+
[32m@astrojs/mdx:build: [0mcache hit, replaying output [2mdb9da5c9ed100942[0m
|
|
2
|
+
[32m@astrojs/mdx:build: [0m
|
|
3
|
+
[32m@astrojs/mdx:build: [0m> @astrojs/mdx@0.6.0 build /home/runner/work/astro/astro/packages/integrations/mdx
|
|
4
|
+
[32m@astrojs/mdx:build: [0m> astro-scripts build "src/**/*.ts" && tsc
|
|
5
|
+
[32m@astrojs/mdx:build: [0m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @astrojs/mdx
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4134](https://github.com/withastro/astro/pull/4134) [`2968ba2b6`](https://github.com/withastro/astro/commit/2968ba2b6f00775b6e9872681b390cb466fdbfa2) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add `headings` and `frontmatter` properties to layout props
|
|
8
|
+
|
|
9
|
+
## 0.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#4095](https://github.com/withastro/astro/pull/4095) [`40ef43a59`](https://github.com/withastro/astro/commit/40ef43a59b08a1a8fbcd9f4a53745a9636a4fbb9) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add IDs to MDX headings and expose via getHeadings() export
|
|
14
|
+
|
|
15
|
+
* [#4114](https://github.com/withastro/astro/pull/4114) [`64432bcb8`](https://github.com/withastro/astro/commit/64432bcb873efd0e4297c00fc9583a1fe516dfe7) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Refactor `@astrojs/mdx` and `@astrojs/markdown-remark` to use `@astrojs/prism` instead of duplicating the code
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#4112](https://github.com/withastro/astro/pull/4112) [`e33fc9bc4`](https://github.com/withastro/astro/commit/e33fc9bc46ff0a30013deb6dc76e545e70cc3a3e) Thanks [@matthewp](https://github.com/matthewp)! - Fix MDX working with a ts config file
|
|
20
|
+
|
|
21
|
+
* [#4049](https://github.com/withastro/astro/pull/4049) [`b60cc0538`](https://github.com/withastro/astro/commit/b60cc0538bc5c68dd411117780d20d892530789d) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve `injectScript` handling for non-Astro pages
|
|
22
|
+
|
|
23
|
+
* Updated dependencies [[`64432bcb8`](https://github.com/withastro/astro/commit/64432bcb873efd0e4297c00fc9583a1fe516dfe7)]:
|
|
24
|
+
- @astrojs/prism@0.7.0
|
|
25
|
+
|
|
26
|
+
## 0.4.0
|
|
27
|
+
|
|
28
|
+
### Minor Changes
|
|
29
|
+
|
|
30
|
+
- [#4088](https://github.com/withastro/astro/pull/4088) [`1743fe140`](https://github.com/withastro/astro/commit/1743fe140eb58d60e26cbd11a066bb60de046e0c) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support "layout" frontmatter property
|
|
31
|
+
|
|
3
32
|
## 0.3.1
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -103,6 +103,24 @@ const posts = await Astro.glob('./*.mdx');
|
|
|
103
103
|
|
|
104
104
|
See [the official "how MDX works" guide](https://mdxjs.com/docs/using-mdx/#how-mdx-works) for more on MDX variables.
|
|
105
105
|
|
|
106
|
+
### Exported properties
|
|
107
|
+
|
|
108
|
+
Alongside your [MDX variable exports](#variables), we generate a few helpful exports as well. These are accessible when importing an MDX file via `import` statements or [`Astro.glob`](https://docs.astro.build/en/reference/api-reference/#astroglob).
|
|
109
|
+
|
|
110
|
+
#### `file`
|
|
111
|
+
|
|
112
|
+
The absolute path to the MDX file (e.g. `home/user/projects/.../file.md`).
|
|
113
|
+
|
|
114
|
+
#### `url`
|
|
115
|
+
|
|
116
|
+
The browser-ready URL for MDX files under `src/pages/`. For example, `src/pages/en/about.mdx` will provide a `url` of `/en/about/`. For MDX files outside of `src/pages`, `url` will be `undefined`.
|
|
117
|
+
|
|
118
|
+
#### `getHeadings()`
|
|
119
|
+
|
|
120
|
+
**Returns:** `{ depth: number; slug: string; text: string }[]`
|
|
121
|
+
|
|
122
|
+
A function that returns an array of all headings (i.e. `h1 -> h6` elements) in the MDX file. Each heading’s `slug` corresponds to the generated ID for a given heading and can be used for anchor links.
|
|
123
|
+
|
|
106
124
|
### Frontmatter
|
|
107
125
|
|
|
108
126
|
Astro also supports YAML-based frontmatter out-of-the-box using the [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter) plugin. By default, all variables declared in a frontmatter fence (`---`) will be accessible via the `frontmatter` export. See the `frontmatterOptions` configuration to customize this behavior.
|
|
@@ -136,15 +154,37 @@ const posts = await Astro.glob('./*.mdx');
|
|
|
136
154
|
|
|
137
155
|
### Layouts
|
|
138
156
|
|
|
139
|
-
|
|
157
|
+
Layouts can be applied [in the same way as standard Astro Markdown](https://docs.astro.build/en/guides/markdown-content/#markdown-layouts). You can add a `layout` to [your frontmatter](#frontmatter) like so:
|
|
140
158
|
|
|
141
|
-
```
|
|
142
|
-
|
|
159
|
+
```yaml
|
|
160
|
+
---
|
|
161
|
+
layout: '../layouts/BaseLayout.astro'
|
|
162
|
+
title: 'My Blog Post'
|
|
163
|
+
---
|
|
164
|
+
```
|
|
143
165
|
|
|
144
|
-
|
|
166
|
+
Then, you can retrieve all other frontmatter properties from your layout via the `content` property, and render your MDX using the default [`<slot />`](https://docs.astro.build/en/core-concepts/astro-components/#slots):
|
|
167
|
+
|
|
168
|
+
```astro
|
|
169
|
+
---
|
|
170
|
+
// src/layouts/BaseLayout.astro
|
|
171
|
+
const { content } = Astro.props;
|
|
172
|
+
---
|
|
173
|
+
<html>
|
|
174
|
+
<head>
|
|
175
|
+
<title>{content.title}</title>
|
|
176
|
+
</head>
|
|
177
|
+
<body>
|
|
178
|
+
<h1>{content.title}</h1>
|
|
179
|
+
<!-- Rendered MDX will be passed into the default slot. -->
|
|
180
|
+
<slot />
|
|
181
|
+
</body>
|
|
182
|
+
</html>
|
|
145
183
|
```
|
|
146
184
|
|
|
147
|
-
|
|
185
|
+
#### Importing layouts manually
|
|
186
|
+
|
|
187
|
+
You may need to pass information to your layouts that does not (or cannot) exist in your frontmatter. In this case, you can import and use a [`<Layout />` component](https://docs.astro.build/en/core-concepts/layouts/) like any other component:
|
|
148
188
|
|
|
149
189
|
```mdx
|
|
150
190
|
---
|
|
@@ -155,9 +195,11 @@ publishDate: '21 September 2022'
|
|
|
155
195
|
---
|
|
156
196
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
|
157
197
|
|
|
158
|
-
|
|
159
|
-
|
|
198
|
+
function fancyJsHelper() {
|
|
199
|
+
return "Try doing that with YAML!";
|
|
200
|
+
}
|
|
160
201
|
|
|
202
|
+
<BaseLayout title={frontmatter.title} fancyJsHelper={fancyJsHelper}>
|
|
161
203
|
Welcome to my new Astro blog, using MDX!
|
|
162
204
|
</BaseLayout>
|
|
163
205
|
```
|
|
@@ -166,12 +208,12 @@ Then, your values are available to you through `Astro.props` in your layout, and
|
|
|
166
208
|
```astro
|
|
167
209
|
---
|
|
168
210
|
// src/layouts/BaseLayout.astro
|
|
169
|
-
const { title,
|
|
211
|
+
const { title, fancyJsHelper } = Astro.props;
|
|
170
212
|
---
|
|
171
213
|
<!-- -->
|
|
172
214
|
<h1>{title}</h1>
|
|
173
215
|
<slot />
|
|
174
|
-
<p>
|
|
216
|
+
<p>{fancyJsHelper()}</p>
|
|
175
217
|
<!-- -->
|
|
176
218
|
```
|
|
177
219
|
|
|
@@ -255,11 +297,26 @@ export default {
|
|
|
255
297
|
<details>
|
|
256
298
|
<summary><strong>rehypePlugins</strong></summary>
|
|
257
299
|
|
|
258
|
-
**Default plugins:**
|
|
300
|
+
**Default plugins:** [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts)
|
|
259
301
|
|
|
260
302
|
[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!
|
|
261
303
|
|
|
262
|
-
|
|
304
|
+
We apply our own [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts) plugin by default. 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).
|
|
305
|
+
|
|
306
|
+
To apply rehype plugins _while preserving_ Astro's default plugins, use a nested `extends` object like so:
|
|
307
|
+
|
|
308
|
+
```js
|
|
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:
|
|
263
320
|
|
|
264
321
|
```js
|
|
265
322
|
// astro.config.mjs
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nodeTypes } from "@mdx-js/mdx";
|
|
1
|
+
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";
|
|
@@ -7,9 +7,12 @@ import remarkGfm from "remark-gfm";
|
|
|
7
7
|
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
|
|
8
8
|
import remarkShikiTwoslash from "remark-shiki-twoslash";
|
|
9
9
|
import remarkSmartypants from "remark-smartypants";
|
|
10
|
+
import { VFile } from "vfile";
|
|
11
|
+
import rehypeCollectHeadings from "./rehype-collect-headings.js";
|
|
10
12
|
import remarkPrism from "./remark-prism.js";
|
|
11
|
-
import { getFileInfo } from "./utils.js";
|
|
13
|
+
import { getFileInfo, getFrontmatter } from "./utils.js";
|
|
12
14
|
const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
|
|
15
|
+
const DEFAULT_REHYPE_PLUGINS = [rehypeCollectHeadings];
|
|
13
16
|
function handleExtends(config, defaults = []) {
|
|
14
17
|
if (Array.isArray(config))
|
|
15
18
|
return config;
|
|
@@ -22,10 +25,10 @@ function mdx(mdxOptions = {}) {
|
|
|
22
25
|
"astro:config:setup": ({ updateConfig, config, addPageExtension, command }) => {
|
|
23
26
|
addPageExtension(".mdx");
|
|
24
27
|
let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
|
|
25
|
-
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins);
|
|
28
|
+
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
|
|
26
29
|
if (config.markdown.syntaxHighlight === "shiki") {
|
|
27
30
|
remarkPlugins.push([
|
|
28
|
-
remarkShikiTwoslash.default,
|
|
31
|
+
remarkShikiTwoslash.default ?? remarkShikiTwoslash,
|
|
29
32
|
config.markdown.shikiConfig
|
|
30
33
|
]);
|
|
31
34
|
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
@@ -42,28 +45,55 @@ function mdx(mdxOptions = {}) {
|
|
|
42
45
|
...mdxOptions.frontmatterOptions
|
|
43
46
|
}
|
|
44
47
|
]);
|
|
48
|
+
const mdxPluginOpts = {
|
|
49
|
+
remarkPlugins,
|
|
50
|
+
rehypePlugins,
|
|
51
|
+
jsx: true,
|
|
52
|
+
jsxImportSource: "astro",
|
|
53
|
+
format: "mdx",
|
|
54
|
+
mdExtensions: []
|
|
55
|
+
};
|
|
45
56
|
updateConfig({
|
|
46
57
|
vite: {
|
|
47
58
|
plugins: [
|
|
48
59
|
{
|
|
49
60
|
enforce: "pre",
|
|
50
|
-
...mdxPlugin(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
...mdxPlugin(mdxPluginOpts),
|
|
62
|
+
async transform(code, id) {
|
|
63
|
+
var _a;
|
|
64
|
+
if (!id.endsWith("mdx"))
|
|
65
|
+
return;
|
|
66
|
+
if (!((_a = mdxOptions.frontmatterOptions) == null ? void 0 : _a.parsers)) {
|
|
67
|
+
const frontmatter = getFrontmatter(code, id);
|
|
68
|
+
if (frontmatter.layout) {
|
|
69
|
+
const { layout, ...content } = frontmatter;
|
|
70
|
+
code += `
|
|
71
|
+
export default async function({ children }) {
|
|
72
|
+
const Layout = (await import(${JSON.stringify(
|
|
73
|
+
frontmatter.layout
|
|
74
|
+
)})).default;
|
|
75
|
+
const frontmatter=${JSON.stringify(
|
|
76
|
+
content
|
|
77
|
+
)};
|
|
78
|
+
return <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const compiled = await mdxCompile(
|
|
82
|
+
new VFile({ value: code, path: id }),
|
|
83
|
+
mdxPluginOpts
|
|
84
|
+
);
|
|
85
|
+
return {
|
|
86
|
+
code: String(compiled.value),
|
|
87
|
+
map: compiled.map
|
|
88
|
+
};
|
|
89
|
+
}
|
|
58
90
|
},
|
|
59
91
|
{
|
|
60
|
-
name: "@astrojs/mdx",
|
|
92
|
+
name: "@astrojs/mdx-postprocess",
|
|
61
93
|
transform(code, id) {
|
|
62
94
|
if (!id.endsWith(".mdx"))
|
|
63
95
|
return;
|
|
64
96
|
const [, moduleExports] = parseESM(code);
|
|
65
|
-
code += `
|
|
66
|
-
import "${"astro:scripts/page-ssr.js"}";`;
|
|
67
97
|
const { fileUrl, fileId } = getFileInfo(id, config);
|
|
68
98
|
if (!moduleExports.includes("url")) {
|
|
69
99
|
code += `
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Slugger from "github-slugger";
|
|
2
|
+
import { visit } from "unist-util-visit";
|
|
3
|
+
import { jsToTreeNode } from "./utils.js";
|
|
4
|
+
function rehypeCollectHeadings() {
|
|
5
|
+
const slugger = new Slugger();
|
|
6
|
+
return function(tree) {
|
|
7
|
+
const headings = [];
|
|
8
|
+
visit(tree, (node) => {
|
|
9
|
+
if (node.type !== "element")
|
|
10
|
+
return;
|
|
11
|
+
const { tagName } = node;
|
|
12
|
+
if (tagName[0] !== "h")
|
|
13
|
+
return;
|
|
14
|
+
const [_, level] = tagName.match(/h([0-6])/) ?? [];
|
|
15
|
+
if (!level)
|
|
16
|
+
return;
|
|
17
|
+
const depth = Number.parseInt(level);
|
|
18
|
+
let text = "";
|
|
19
|
+
visit(node, (child, __, parent) => {
|
|
20
|
+
if (child.type === "element" || parent == null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (child.type === "raw" && child.value.match(/^\n?<.*>\n?$/)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if ((/* @__PURE__ */ new Set(["text", "raw", "mdxTextExpression"])).has(child.type)) {
|
|
27
|
+
text += child.value;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
node.properties = node.properties || {};
|
|
31
|
+
if (typeof node.properties.id !== "string") {
|
|
32
|
+
let slug = slugger.slug(text);
|
|
33
|
+
if (slug.endsWith("-")) {
|
|
34
|
+
slug = slug.slice(0, -1);
|
|
35
|
+
}
|
|
36
|
+
node.properties.id = slug;
|
|
37
|
+
}
|
|
38
|
+
headings.push({ depth, slug: node.properties.id, text });
|
|
39
|
+
});
|
|
40
|
+
tree.children.unshift(
|
|
41
|
+
jsToTreeNode(`export function getHeadings() { return ${JSON.stringify(headings)} }`)
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
rehypeCollectHeadings as default
|
|
47
|
+
};
|
package/dist/remark-prism.js
CHANGED
|
@@ -1,42 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Prism from "prismjs";
|
|
3
|
-
import loadLanguages from "prismjs/components/index.js";
|
|
1
|
+
import { runHighlighterWithAstro } from "@astrojs/prism/dist/highlighter";
|
|
4
2
|
import { visit } from "unist-util-visit";
|
|
5
|
-
const languageMap = /* @__PURE__ */ new Map([["ts", "typescript"]]);
|
|
6
|
-
function runHighlighter(lang, code) {
|
|
7
|
-
let classLanguage = `language-${lang}`;
|
|
8
|
-
if (lang == null) {
|
|
9
|
-
lang = "plaintext";
|
|
10
|
-
}
|
|
11
|
-
const ensureLoaded = (language) => {
|
|
12
|
-
if (language && !Prism.languages[language]) {
|
|
13
|
-
loadLanguages([language]);
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
if (languageMap.has(lang)) {
|
|
17
|
-
ensureLoaded(languageMap.get(lang));
|
|
18
|
-
} else if (lang === "astro") {
|
|
19
|
-
ensureLoaded("typescript");
|
|
20
|
-
addAstro(Prism);
|
|
21
|
-
} else {
|
|
22
|
-
ensureLoaded("markup-templating");
|
|
23
|
-
ensureLoaded(lang);
|
|
24
|
-
}
|
|
25
|
-
if (lang && !Prism.languages[lang]) {
|
|
26
|
-
console.warn(`Unable to load the language: ${lang}`);
|
|
27
|
-
}
|
|
28
|
-
const grammar = Prism.languages[lang];
|
|
29
|
-
let html = code;
|
|
30
|
-
if (grammar) {
|
|
31
|
-
html = Prism.highlight(code, grammar, lang);
|
|
32
|
-
}
|
|
33
|
-
return { classLanguage, html };
|
|
34
|
-
}
|
|
35
3
|
function remarkPrism() {
|
|
36
4
|
return (tree) => visit(tree, "code", (node) => {
|
|
37
5
|
let { lang, value } = node;
|
|
38
6
|
node.type = "html";
|
|
39
|
-
let { html, classLanguage } =
|
|
7
|
+
let { html, classLanguage } = runHighlighterWithAstro(lang, value);
|
|
40
8
|
let classes = [classLanguage];
|
|
41
9
|
node.value = `<pre class="${classes.join(
|
|
42
10
|
" "
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import type { Options as AcornOpts } from 'acorn';
|
|
1
2
|
import type { AstroConfig } from 'astro';
|
|
3
|
+
import type { MdxjsEsm } from 'mdast-util-mdx';
|
|
2
4
|
interface FileInfo {
|
|
3
5
|
fileId: string;
|
|
4
6
|
fileUrl: string;
|
|
5
7
|
}
|
|
6
8
|
/** @see 'vite-plugin-utils' for source */
|
|
7
9
|
export declare function getFileInfo(id: string, config: AstroConfig): FileInfo;
|
|
10
|
+
/**
|
|
11
|
+
* Match YAML exception handling from Astro core errors
|
|
12
|
+
* @see 'astro/src/core/errors.ts'
|
|
13
|
+
*/
|
|
14
|
+
export declare function getFrontmatter(code: string, id: string): {
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
};
|
|
17
|
+
export declare function jsToTreeNode(jsString: string, acornOpts?: AcornOpts): MdxjsEsm;
|
|
8
18
|
export {};
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { parse } from "acorn";
|
|
2
|
+
import matter from "gray-matter";
|
|
1
3
|
function appendForwardSlash(path) {
|
|
2
4
|
return path.endsWith("/") ? path : path + "/";
|
|
3
5
|
}
|
|
@@ -25,6 +27,40 @@ function getFileInfo(id, config) {
|
|
|
25
27
|
}
|
|
26
28
|
return { fileId, fileUrl };
|
|
27
29
|
}
|
|
30
|
+
function getFrontmatter(code, id) {
|
|
31
|
+
try {
|
|
32
|
+
return matter(code).data;
|
|
33
|
+
} catch (e) {
|
|
34
|
+
if (e.name === "YAMLException") {
|
|
35
|
+
const err = e;
|
|
36
|
+
err.id = id;
|
|
37
|
+
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
38
|
+
err.message = e.reason;
|
|
39
|
+
throw err;
|
|
40
|
+
} else {
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function jsToTreeNode(jsString, acornOpts = {
|
|
46
|
+
ecmaVersion: "latest",
|
|
47
|
+
sourceType: "module"
|
|
48
|
+
}) {
|
|
49
|
+
return {
|
|
50
|
+
type: "mdxjsEsm",
|
|
51
|
+
value: "",
|
|
52
|
+
data: {
|
|
53
|
+
estree: {
|
|
54
|
+
body: [],
|
|
55
|
+
...parse(jsString, acornOpts),
|
|
56
|
+
type: "Program",
|
|
57
|
+
sourceType: "module"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
28
62
|
export {
|
|
29
|
-
getFileInfo
|
|
63
|
+
getFileInfo,
|
|
64
|
+
getFrontmatter,
|
|
65
|
+
jsToTreeNode
|
|
30
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.6.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"author": "withastro",
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"keywords": [
|
|
15
15
|
"astro-integration",
|
|
16
16
|
"astro-component",
|
|
17
|
-
"renderer",
|
|
18
17
|
"mdx"
|
|
19
18
|
],
|
|
20
19
|
"bugs": "https://github.com/withastro/astro/issues",
|
|
@@ -24,29 +23,33 @@
|
|
|
24
23
|
"./package.json": "./package.json"
|
|
25
24
|
},
|
|
26
25
|
"dependencies": {
|
|
27
|
-
"@astrojs/prism": "^0.
|
|
26
|
+
"@astrojs/prism": "^0.7.0",
|
|
28
27
|
"@mdx-js/mdx": "^2.1.2",
|
|
29
28
|
"@mdx-js/rollup": "^2.1.1",
|
|
29
|
+
"acorn": "^8.8.0",
|
|
30
30
|
"es-module-lexer": "^0.10.5",
|
|
31
|
-
"
|
|
31
|
+
"github-slugger": "^1.4.0",
|
|
32
|
+
"gray-matter": "^4.0.3",
|
|
32
33
|
"rehype-raw": "^6.1.1",
|
|
34
|
+
"remark-frontmatter": "^4.0.1",
|
|
33
35
|
"remark-gfm": "^3.0.1",
|
|
36
|
+
"remark-mdx-frontmatter": "^2.0.2",
|
|
34
37
|
"remark-shiki-twoslash": "^3.1.0",
|
|
35
38
|
"remark-smartypants": "^2.0.0",
|
|
36
39
|
"shiki": "^0.10.1",
|
|
37
|
-
"unist-util-visit": "^4.1.0"
|
|
38
|
-
"remark-frontmatter": "^4.0.1",
|
|
39
|
-
"remark-mdx-frontmatter": "^2.0.2"
|
|
40
|
+
"unist-util-visit": "^4.1.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@types/chai": "^4.3.1",
|
|
43
44
|
"@types/mocha": "^9.1.1",
|
|
44
45
|
"@types/yargs-parser": "^21.0.0",
|
|
45
|
-
"astro": "1.0.0-rc.
|
|
46
|
+
"astro": "1.0.0-rc.5",
|
|
46
47
|
"astro-scripts": "0.0.6",
|
|
47
48
|
"chai": "^4.3.6",
|
|
48
49
|
"linkedom": "^0.14.12",
|
|
50
|
+
"mdast-util-to-string": "^3.1.0",
|
|
49
51
|
"mocha": "^9.2.2",
|
|
52
|
+
"reading-time": "^1.5.0",
|
|
50
53
|
"remark-toc": "^8.0.1"
|
|
51
54
|
},
|
|
52
55
|
"engines": {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nodeTypes } from '@mdx-js/mdx';
|
|
1
|
+
import { compile as mdxCompile, nodeTypes } from '@mdx-js/mdx';
|
|
2
2
|
import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
|
|
3
3
|
import type { AstroIntegration } from 'astro';
|
|
4
4
|
import { parse as parseESM } from 'es-module-lexer';
|
|
@@ -9,8 +9,11 @@ import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
|
|
|
9
9
|
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
|
|
10
10
|
import remarkShikiTwoslash from 'remark-shiki-twoslash';
|
|
11
11
|
import remarkSmartypants from 'remark-smartypants';
|
|
12
|
+
import { VFile } from 'vfile';
|
|
13
|
+
import type { Plugin as VitePlugin } from 'vite';
|
|
14
|
+
import rehypeCollectHeadings from './rehype-collect-headings.js';
|
|
12
15
|
import remarkPrism from './remark-prism.js';
|
|
13
|
-
import { getFileInfo } from './utils.js';
|
|
16
|
+
import { getFileInfo, getFrontmatter } from './utils.js';
|
|
14
17
|
|
|
15
18
|
type WithExtends<T> = T | { extends: T };
|
|
16
19
|
|
|
@@ -26,6 +29,7 @@ type MdxOptions = {
|
|
|
26
29
|
};
|
|
27
30
|
|
|
28
31
|
const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
|
|
32
|
+
const DEFAULT_REHYPE_PLUGINS = [rehypeCollectHeadings];
|
|
29
33
|
|
|
30
34
|
function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] {
|
|
31
35
|
if (Array.isArray(config)) return config;
|
|
@@ -40,7 +44,7 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
|
|
|
40
44
|
'astro:config:setup': ({ updateConfig, config, addPageExtension, command }: any) => {
|
|
41
45
|
addPageExtension('.mdx');
|
|
42
46
|
let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
|
|
43
|
-
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins);
|
|
47
|
+
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
|
|
44
48
|
|
|
45
49
|
if (config.markdown.syntaxHighlight === 'shiki') {
|
|
46
50
|
remarkPlugins.push([
|
|
@@ -48,7 +52,7 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
|
|
|
48
52
|
// Workarounds tried:
|
|
49
53
|
// - "import * as remarkShikiTwoslash"
|
|
50
54
|
// - "import { default as remarkShikiTwoslash }"
|
|
51
|
-
(remarkShikiTwoslash as any).default,
|
|
55
|
+
(remarkShikiTwoslash as any).default ?? remarkShikiTwoslash,
|
|
52
56
|
config.markdown.shikiConfig,
|
|
53
57
|
]);
|
|
54
58
|
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
@@ -68,34 +72,59 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
|
|
|
68
72
|
},
|
|
69
73
|
]);
|
|
70
74
|
|
|
75
|
+
const mdxPluginOpts: MdxRollupPluginOptions = {
|
|
76
|
+
remarkPlugins,
|
|
77
|
+
rehypePlugins,
|
|
78
|
+
jsx: true,
|
|
79
|
+
jsxImportSource: 'astro',
|
|
80
|
+
// Note: disable `.md` support
|
|
81
|
+
format: 'mdx',
|
|
82
|
+
mdExtensions: [],
|
|
83
|
+
};
|
|
84
|
+
|
|
71
85
|
updateConfig({
|
|
72
86
|
vite: {
|
|
73
87
|
plugins: [
|
|
74
88
|
{
|
|
75
89
|
enforce: 'pre',
|
|
76
|
-
...mdxPlugin(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
...mdxPlugin(mdxPluginOpts),
|
|
91
|
+
// Override transform to alter code before MDX compilation
|
|
92
|
+
// ex. inject layouts
|
|
93
|
+
async transform(code, id) {
|
|
94
|
+
if (!id.endsWith('mdx')) return;
|
|
95
|
+
|
|
96
|
+
// If user overrides our default YAML parser,
|
|
97
|
+
// do not attempt to parse the `layout` via gray-matter
|
|
98
|
+
if (!mdxOptions.frontmatterOptions?.parsers) {
|
|
99
|
+
const frontmatter = getFrontmatter(code, id);
|
|
100
|
+
if (frontmatter.layout) {
|
|
101
|
+
const { layout, ...content } = frontmatter;
|
|
102
|
+
code += `\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
|
|
103
|
+
frontmatter.layout
|
|
104
|
+
)})).default;\nconst frontmatter=${JSON.stringify(
|
|
105
|
+
content
|
|
106
|
+
)};\nreturn <Layout frontmatter={frontmatter} content={frontmatter} headings={getHeadings()}>{children}</Layout> }`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const compiled = await mdxCompile(
|
|
111
|
+
new VFile({ value: code, path: id }),
|
|
112
|
+
mdxPluginOpts
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
code: String(compiled.value),
|
|
117
|
+
map: compiled.map,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
85
120
|
},
|
|
86
121
|
{
|
|
87
|
-
name: '@astrojs/mdx',
|
|
88
|
-
|
|
122
|
+
name: '@astrojs/mdx-postprocess',
|
|
123
|
+
// These transforms must happen *after* JSX runtime transformations
|
|
124
|
+
transform(code, id) {
|
|
89
125
|
if (!id.endsWith('.mdx')) return;
|
|
90
126
|
const [, moduleExports] = parseESM(code);
|
|
91
127
|
|
|
92
|
-
// This adds support for injected "page-ssr" scripts in MDX files.
|
|
93
|
-
// TODO: This should only be happening on page entrypoints, not all imported MDX.
|
|
94
|
-
// TODO: This code is copy-pasted across all Astro/Vite plugins that deal with page
|
|
95
|
-
// entrypoints (.astro, .md, .mdx). This should be handled in some centralized place,
|
|
96
|
-
// or otherwise refactored to not require copy-paste handling logic.
|
|
97
|
-
code += `\nimport "${'astro:scripts/page-ssr.js'}";`;
|
|
98
|
-
|
|
99
128
|
const { fileUrl, fileId } = getFileInfo(id, config);
|
|
100
129
|
if (!moduleExports.includes('url')) {
|
|
101
130
|
code += `\nexport const url = ${JSON.stringify(fileUrl)};`;
|
|
@@ -113,7 +142,7 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
|
|
|
113
142
|
return code;
|
|
114
143
|
},
|
|
115
144
|
},
|
|
116
|
-
],
|
|
145
|
+
] as VitePlugin[],
|
|
117
146
|
},
|
|
118
147
|
});
|
|
119
148
|
},
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import Slugger from 'github-slugger';
|
|
2
|
+
import { visit } from 'unist-util-visit';
|
|
3
|
+
import { jsToTreeNode } from './utils.js';
|
|
4
|
+
|
|
5
|
+
export interface MarkdownHeading {
|
|
6
|
+
depth: number;
|
|
7
|
+
slug: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function rehypeCollectHeadings() {
|
|
12
|
+
const slugger = new Slugger();
|
|
13
|
+
return function (tree: any) {
|
|
14
|
+
const headings: MarkdownHeading[] = [];
|
|
15
|
+
visit(tree, (node) => {
|
|
16
|
+
if (node.type !== 'element') return;
|
|
17
|
+
const { tagName } = node;
|
|
18
|
+
if (tagName[0] !== 'h') return;
|
|
19
|
+
const [_, level] = tagName.match(/h([0-6])/) ?? [];
|
|
20
|
+
if (!level) return;
|
|
21
|
+
const depth = Number.parseInt(level);
|
|
22
|
+
|
|
23
|
+
let text = '';
|
|
24
|
+
visit(node, (child, __, parent) => {
|
|
25
|
+
if (child.type === 'element' || parent == null) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (child.type === 'raw' && child.value.match(/^\n?<.*>\n?$/)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (new Set(['text', 'raw', 'mdxTextExpression']).has(child.type)) {
|
|
32
|
+
text += child.value;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
node.properties = node.properties || {};
|
|
37
|
+
if (typeof node.properties.id !== 'string') {
|
|
38
|
+
let slug = slugger.slug(text);
|
|
39
|
+
if (slug.endsWith('-')) {
|
|
40
|
+
slug = slug.slice(0, -1);
|
|
41
|
+
}
|
|
42
|
+
node.properties.id = slug;
|
|
43
|
+
}
|
|
44
|
+
headings.push({ depth, slug: node.properties.id, text });
|
|
45
|
+
});
|
|
46
|
+
tree.children.unshift(
|
|
47
|
+
jsToTreeNode(`export function getHeadings() { return ${JSON.stringify(headings)} }`)
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
}
|
package/src/remark-prism.ts
CHANGED
|
@@ -1,48 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { addAstro } from '@astrojs/prism/internal';
|
|
3
|
-
import Prism from 'prismjs';
|
|
4
|
-
import loadLanguages from 'prismjs/components/index.js';
|
|
1
|
+
import { runHighlighterWithAstro } from '@astrojs/prism/dist/highlighter';
|
|
5
2
|
import { visit } from 'unist-util-visit';
|
|
6
3
|
|
|
7
|
-
const languageMap = new Map([['ts', 'typescript']]);
|
|
8
|
-
|
|
9
|
-
function runHighlighter(lang: string, code: string) {
|
|
10
|
-
let classLanguage = `language-${lang}`;
|
|
11
|
-
|
|
12
|
-
if (lang == null) {
|
|
13
|
-
lang = 'plaintext';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const ensureLoaded = (language: string) => {
|
|
17
|
-
if (language && !Prism.languages[language]) {
|
|
18
|
-
loadLanguages([language]);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
if (languageMap.has(lang)) {
|
|
23
|
-
ensureLoaded(languageMap.get(lang)!);
|
|
24
|
-
} else if (lang === 'astro') {
|
|
25
|
-
ensureLoaded('typescript');
|
|
26
|
-
addAstro(Prism);
|
|
27
|
-
} else {
|
|
28
|
-
ensureLoaded('markup-templating'); // Prism expects this to exist for a number of other langs
|
|
29
|
-
ensureLoaded(lang);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (lang && !Prism.languages[lang]) {
|
|
33
|
-
// eslint-disable-next-line no-console
|
|
34
|
-
console.warn(`Unable to load the language: ${lang}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const grammar = Prism.languages[lang];
|
|
38
|
-
let html = code;
|
|
39
|
-
if (grammar) {
|
|
40
|
-
html = Prism.highlight(code, grammar, lang);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { classLanguage, html };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
4
|
/** */
|
|
47
5
|
export default function remarkPrism() {
|
|
48
6
|
return (tree: any) =>
|
|
@@ -50,7 +8,7 @@ export default function remarkPrism() {
|
|
|
50
8
|
let { lang, value } = node;
|
|
51
9
|
node.type = 'html';
|
|
52
10
|
|
|
53
|
-
let { html, classLanguage } =
|
|
11
|
+
let { html, classLanguage } = runHighlighterWithAstro(lang, value);
|
|
54
12
|
let classes = [classLanguage];
|
|
55
13
|
node.value = `<pre class="${classes.join(
|
|
56
14
|
' '
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Options as AcornOpts } from 'acorn';
|
|
2
|
+
import { parse } from 'acorn';
|
|
3
|
+
import type { AstroConfig, SSRError } from 'astro';
|
|
4
|
+
import type { MdxjsEsm } from 'mdast-util-mdx';
|
|
5
|
+
|
|
6
|
+
import matter from 'gray-matter';
|
|
2
7
|
|
|
3
8
|
function appendForwardSlash(path: string) {
|
|
4
9
|
return path.endsWith('/') ? path : path + '/';
|
|
@@ -37,3 +42,44 @@ export function getFileInfo(id: string, config: AstroConfig): FileInfo {
|
|
|
37
42
|
}
|
|
38
43
|
return { fileId, fileUrl };
|
|
39
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Match YAML exception handling from Astro core errors
|
|
48
|
+
* @see 'astro/src/core/errors.ts'
|
|
49
|
+
*/
|
|
50
|
+
export function getFrontmatter(code: string, id: string) {
|
|
51
|
+
try {
|
|
52
|
+
return matter(code).data;
|
|
53
|
+
} catch (e: any) {
|
|
54
|
+
if (e.name === 'YAMLException') {
|
|
55
|
+
const err: SSRError = e;
|
|
56
|
+
err.id = id;
|
|
57
|
+
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
58
|
+
err.message = e.reason;
|
|
59
|
+
throw err;
|
|
60
|
+
} else {
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function jsToTreeNode(
|
|
67
|
+
jsString: string,
|
|
68
|
+
acornOpts: AcornOpts = {
|
|
69
|
+
ecmaVersion: 'latest',
|
|
70
|
+
sourceType: 'module',
|
|
71
|
+
}
|
|
72
|
+
): MdxjsEsm {
|
|
73
|
+
return {
|
|
74
|
+
type: 'mdxjsEsm',
|
|
75
|
+
value: '',
|
|
76
|
+
data: {
|
|
77
|
+
estree: {
|
|
78
|
+
body: [],
|
|
79
|
+
...parse(jsString, acornOpts),
|
|
80
|
+
type: 'Program',
|
|
81
|
+
sourceType: 'module',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
const {
|
|
3
|
+
content = { title: "content didn't work" },
|
|
4
|
+
frontmatter = { title: "frontmatter didn't work" },
|
|
5
|
+
headings = [],
|
|
6
|
+
} = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8">
|
|
14
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
15
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
16
|
+
</head>
|
|
17
|
+
|
|
18
|
+
<body>
|
|
19
|
+
<p data-content-title>{content.title}</p>
|
|
20
|
+
<p data-frontmatter-title>{frontmatter.title}</p>
|
|
21
|
+
<p data-layout-rendered>Layout rendered!</p>
|
|
22
|
+
<ul data-headings>
|
|
23
|
+
{headings.map(heading => <li>{heading.slug}</li>)}
|
|
24
|
+
</ul>
|
|
25
|
+
<slot />
|
|
26
|
+
</body>
|
|
27
|
+
|
|
28
|
+
</html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export async function get() {
|
|
2
|
+
const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
body: JSON.stringify({
|
|
6
|
+
headingsByPage: Object.fromEntries(
|
|
7
|
+
Object.entries(mdxPages ?? {}).map(([k, v]) => [k, v?.getHeadings()])
|
|
8
|
+
),
|
|
9
|
+
}),
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -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,25 @@
|
|
|
1
|
+
# Space ipsum
|
|
2
|
+
|
|
3
|
+
For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us.
|
|
4
|
+
|
|
5
|
+
It suddenly struck me that that tiny pea, pretty and blue, was the Earth. I put up my thumb and shut one eye, and my thumb blotted out the planet Earth. I didn’t feel like a giant. I felt very, very small.
|
|
6
|
+
|
|
7
|
+
Science has not yet mastered prophecy. We predict too much for the next year and yet far too little for the next 10.
|
|
8
|
+
|
|
9
|
+
## Section 2
|
|
10
|
+
|
|
11
|
+
We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard, because that goal will serve to organize and measure the best of our energies and skills, because that challenge is one that we are willing to accept, one we are unwilling to postpone, and one which we intend to win.
|
|
12
|
+
|
|
13
|
+
There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.
|
|
14
|
+
|
|
15
|
+
As I stand out here in the wonders of the unknown at Hadley, I sort of realize there’s a fundamental truth to our nature, Man must explore . . . and this is exploration at its greatest.
|
|
16
|
+
|
|
17
|
+
## Section 3
|
|
18
|
+
|
|
19
|
+
Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman’s earth, if free men make it, will be truly round: a globe in practice, not in theory.
|
|
20
|
+
|
|
21
|
+
To be the first to enter the cosmos, to engage, single-handed, in an unprecedented duel with nature—could one dream of anything more?
|
|
22
|
+
|
|
23
|
+
There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.
|
|
24
|
+
|
|
25
|
+
We are all connected; To each other, biologically. To the earth, chemically. To the rest of the universe atomically.
|
|
@@ -1,33 +1,64 @@
|
|
|
1
1
|
import mdx from '@astrojs/mdx';
|
|
2
2
|
|
|
3
3
|
import { expect } from 'chai';
|
|
4
|
+
import { parseHTML } from 'linkedom';
|
|
4
5
|
import { loadFixture } from '../../../astro/test/test-utils.js';
|
|
5
6
|
|
|
6
7
|
const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter/', import.meta.url);
|
|
7
8
|
|
|
8
9
|
describe('MDX frontmatter', () => {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
let fixture;
|
|
11
|
+
before(async () => {
|
|
12
|
+
fixture = await loadFixture({
|
|
11
13
|
root: FIXTURE_ROOT,
|
|
12
14
|
integrations: [mdx()],
|
|
13
15
|
});
|
|
14
16
|
await fixture.build();
|
|
17
|
+
});
|
|
18
|
+
it('builds when "frontmatter.property" is in JSX expression', async () => {
|
|
15
19
|
expect(true).to.equal(true);
|
|
16
20
|
});
|
|
17
21
|
|
|
18
22
|
it('extracts frontmatter to "frontmatter" export', async () => {
|
|
19
|
-
const fixture = await loadFixture({
|
|
20
|
-
root: FIXTURE_ROOT,
|
|
21
|
-
integrations: [mdx()],
|
|
22
|
-
});
|
|
23
|
-
await fixture.build();
|
|
24
|
-
|
|
25
23
|
const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
|
|
26
24
|
expect(titles).to.include('Using YAML frontmatter');
|
|
27
25
|
});
|
|
28
26
|
|
|
27
|
+
it('renders layout from "layout" frontmatter property', async () => {
|
|
28
|
+
const html = await fixture.readFile('/index.html');
|
|
29
|
+
const { document } = parseHTML(html);
|
|
30
|
+
|
|
31
|
+
const layoutParagraph = document.querySelector('[data-layout-rendered]');
|
|
32
|
+
|
|
33
|
+
expect(layoutParagraph).to.not.be.null;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('passes frontmatter to layout via "content" and "frontmatter" props', async () => {
|
|
37
|
+
const html = await fixture.readFile('/index.html');
|
|
38
|
+
const { document } = parseHTML(html);
|
|
39
|
+
|
|
40
|
+
const contentTitle = document.querySelector('[data-content-title]');
|
|
41
|
+
const frontmatterTitle = document.querySelector('[data-frontmatter-title]');
|
|
42
|
+
|
|
43
|
+
expect(contentTitle.textContent).to.equal('Using YAML frontmatter');
|
|
44
|
+
expect(frontmatterTitle.textContent).to.equal('Using YAML frontmatter');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('passes headings to layout via "headings" prop', async () => {
|
|
48
|
+
const html = await fixture.readFile('/with-headings/index.html');
|
|
49
|
+
const { document } = parseHTML(html);
|
|
50
|
+
|
|
51
|
+
const headingSlugs = [...document.querySelectorAll('[data-headings] > li')].map(
|
|
52
|
+
(el) => el.textContent
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(headingSlugs.length).to.be.greaterThan(0);
|
|
56
|
+
expect(headingSlugs).to.contain('section-1');
|
|
57
|
+
expect(headingSlugs).to.contain('section-2');
|
|
58
|
+
});
|
|
59
|
+
|
|
29
60
|
it('extracts frontmatter to "customFrontmatter" export when configured', async () => {
|
|
30
|
-
const
|
|
61
|
+
const customFixture = await loadFixture({
|
|
31
62
|
root: new URL('./fixtures/mdx-custom-frontmatter-name/', import.meta.url),
|
|
32
63
|
integrations: [
|
|
33
64
|
mdx({
|
|
@@ -37,9 +68,9 @@ describe('MDX frontmatter', () => {
|
|
|
37
68
|
}),
|
|
38
69
|
],
|
|
39
70
|
});
|
|
40
|
-
await
|
|
71
|
+
await customFixture.build();
|
|
41
72
|
|
|
42
|
-
const { titles } = JSON.parse(await
|
|
73
|
+
const { titles } = JSON.parse(await customFixture.readFile('/glob.json'));
|
|
43
74
|
expect(titles).to.include('Using YAML frontmatter');
|
|
44
75
|
});
|
|
45
76
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
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 getHeadings', () => {
|
|
8
|
+
let fixture;
|
|
9
|
+
|
|
10
|
+
before(async () => {
|
|
11
|
+
fixture = await loadFixture({
|
|
12
|
+
root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
|
|
13
|
+
integrations: [mdx()],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await fixture.build();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('adds anchor IDs to headings', async () => {
|
|
20
|
+
const html = await fixture.readFile('/test/index.html');
|
|
21
|
+
const { document } = parseHTML(html);
|
|
22
|
+
|
|
23
|
+
const h2Ids = document.querySelectorAll('h2').map((el) => el?.id);
|
|
24
|
+
const h3Ids = document.querySelectorAll('h3').map((el) => el?.id);
|
|
25
|
+
expect(document.querySelector('h1').id).to.equal('heading-test');
|
|
26
|
+
expect(h2Ids).to.contain('section-1');
|
|
27
|
+
expect(h2Ids).to.contain('section-2');
|
|
28
|
+
expect(h3Ids).to.contain('subsection-1');
|
|
29
|
+
expect(h3Ids).to.contain('subsection-2');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('generates correct getHeadings() export', async () => {
|
|
33
|
+
const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
|
|
34
|
+
// TODO: make this a snapshot test :)
|
|
35
|
+
expect(JSON.stringify(headingsByPage['./test.mdx'])).to.equal(
|
|
36
|
+
JSON.stringify([
|
|
37
|
+
{ depth: 1, slug: 'heading-test', text: 'Heading test' },
|
|
38
|
+
{ depth: 2, slug: 'section-1', text: 'Section 1' },
|
|
39
|
+
{ depth: 3, slug: 'subsection-1', text: 'Subsection 1' },
|
|
40
|
+
{ depth: 3, slug: 'subsection-2', text: 'Subsection 2' },
|
|
41
|
+
{ depth: 2, slug: 'section-2', text: 'Section 2' },
|
|
42
|
+
])
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('generates correct getHeadings() export for JSX expressions', async () => {
|
|
47
|
+
const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
|
|
48
|
+
expect(JSON.stringify(headingsByPage['./test-with-jsx-expressions.mdx'])).to.equal(
|
|
49
|
+
JSON.stringify([
|
|
50
|
+
{
|
|
51
|
+
depth: 1,
|
|
52
|
+
slug: 'heading-test-with-jsx-expressions',
|
|
53
|
+
text: 'Heading test with JSX expressions',
|
|
54
|
+
},
|
|
55
|
+
{ depth: 2, slug: 'h2title', text: 'h2Title' },
|
|
56
|
+
{ depth: 3, slug: 'h3title', text: 'h3Title' },
|
|
57
|
+
])
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
package/test/mdx-page.test.js
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import mdx from '@astrojs/mdx';
|
|
2
|
+
import { jsToTreeNode } from '../dist/utils.js';
|
|
3
|
+
|
|
4
|
+
import { expect } from 'chai';
|
|
5
|
+
import { parseHTML } from 'linkedom';
|
|
6
|
+
import getReadingTime from 'reading-time';
|
|
7
|
+
import { toString } from 'mdast-util-to-string';
|
|
8
|
+
|
|
9
|
+
import { loadFixture } from '../../../astro/test/test-utils.js';
|
|
10
|
+
|
|
11
|
+
export function rehypeReadingTime() {
|
|
12
|
+
return function (tree) {
|
|
13
|
+
const readingTime = getReadingTime(toString(tree));
|
|
14
|
+
tree.children.unshift(
|
|
15
|
+
jsToTreeNode(`export const readingTime = ${JSON.stringify(readingTime)}`)
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const FIXTURE_ROOT = new URL('./fixtures/mdx-rehype-plugins/', import.meta.url);
|
|
21
|
+
|
|
22
|
+
describe('MDX rehype plugins', () => {
|
|
23
|
+
describe('without "extends"', () => {
|
|
24
|
+
let fixture;
|
|
25
|
+
before(async () => {
|
|
26
|
+
fixture = await loadFixture({
|
|
27
|
+
root: FIXTURE_ROOT,
|
|
28
|
+
integrations: [
|
|
29
|
+
mdx({
|
|
30
|
+
rehypePlugins: [rehypeReadingTime],
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
await fixture.build();
|
|
35
|
+
});
|
|
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
|
+
it('supports custom rehype plugins - reading time', async () => {
|
|
49
|
+
const readingTime = JSON.parse(await fixture.readFile('/reading-time.json'));
|
|
50
|
+
|
|
51
|
+
expect(readingTime).to.not.be.null;
|
|
52
|
+
expect(readingTime.text).to.match(/^\d+ min read/);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('with "extends"', () => {
|
|
57
|
+
let fixture;
|
|
58
|
+
before(async () => {
|
|
59
|
+
fixture = await loadFixture({
|
|
60
|
+
root: FIXTURE_ROOT,
|
|
61
|
+
integrations: [
|
|
62
|
+
mdx({
|
|
63
|
+
rehypePlugins: { extends: [rehypeReadingTime] },
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
await fixture.build();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('preserves default getHeadings', async () => {
|
|
71
|
+
const html = await fixture.readFile('/space-ipsum/index.html');
|
|
72
|
+
const { document } = parseHTML(html);
|
|
73
|
+
|
|
74
|
+
const headings = [...document.querySelectorAll('h1, h2')];
|
|
75
|
+
expect(headings.length).to.be.greaterThan(0);
|
|
76
|
+
for (const heading of headings) {
|
|
77
|
+
expect(heading.id).to.not.be.empty;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|