@astrojs/mdx 0.2.1 → 0.4.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 +28 -0
- package/README.md +247 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +87 -10
- package/dist/remark-prism.d.ts +2 -0
- package/dist/remark-prism.js +49 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.js +47 -0
- package/package.json +18 -4
- package/src/index.ts +122 -15
- package/src/remark-prism.ts +60 -0
- package/src/utils.ts +60 -0
- package/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js +9 -0
- package/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx +6 -0
- package/test/fixtures/mdx-frontmatter/src/layouts/Base.astro +18 -0
- package/test/fixtures/mdx-frontmatter/src/pages/glob.json.js +9 -0
- package/test/fixtures/mdx-frontmatter/src/pages/index.mdx +7 -0
- package/test/fixtures/mdx-get-static-paths/src/content/1.mdx +5 -0
- package/test/fixtures/mdx-get-static-paths/src/pages/[slug].astro +34 -0
- package/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx +3 -0
- package/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx +19 -0
- package/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx +9 -0
- package/test/fixtures/mdx-url-export/src/pages/pages.json.js +9 -0
- package/test/fixtures/mdx-url-export/src/pages/test-1.mdx +1 -0
- package/test/fixtures/mdx-url-export/src/pages/test-2.mdx +1 -0
- package/test/fixtures/mdx-url-export/src/pages/with-url-override.mdx +3 -0
- package/test/mdx-frontmatter.test.js +76 -0
- package/test/mdx-get-static-paths.test.js +32 -0
- package/test/mdx-remark-plugins.test.js +62 -0
- package/test/mdx-syntax-highlighting.test.js +67 -0
- package/test/mdx-url-export.test.js +28 -0
package/src/index.ts
CHANGED
|
@@ -1,36 +1,143 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { nodeTypes } from '@mdx-js/mdx';
|
|
2
|
+
import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
|
|
2
3
|
import type { AstroIntegration } from 'astro';
|
|
4
|
+
import { parse as parseESM } from 'es-module-lexer';
|
|
5
|
+
import rehypeRaw from 'rehype-raw';
|
|
6
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
7
|
+
import remarkGfm from 'remark-gfm';
|
|
8
|
+
import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
|
|
9
|
+
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
|
|
10
|
+
import remarkShikiTwoslash from 'remark-shiki-twoslash';
|
|
11
|
+
import remarkSmartypants from 'remark-smartypants';
|
|
12
|
+
import type { Plugin as VitePlugin } from 'vite';
|
|
13
|
+
import remarkPrism from './remark-prism.js';
|
|
14
|
+
import { getFileInfo, getFrontmatter } from './utils.js';
|
|
3
15
|
|
|
4
|
-
|
|
16
|
+
type WithExtends<T> = T | { extends: T };
|
|
17
|
+
|
|
18
|
+
type MdxOptions = {
|
|
19
|
+
remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>;
|
|
20
|
+
rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>;
|
|
21
|
+
/**
|
|
22
|
+
* Configure the remark-mdx-frontmatter plugin
|
|
23
|
+
* @see https://github.com/remcohaszing/remark-mdx-frontmatter#options for a full list of options
|
|
24
|
+
* @default {{ name: 'frontmatter' }}
|
|
25
|
+
*/
|
|
26
|
+
frontmatterOptions?: RemarkMdxFrontmatterOptions;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
|
|
30
|
+
|
|
31
|
+
function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] {
|
|
32
|
+
if (Array.isArray(config)) return config;
|
|
33
|
+
|
|
34
|
+
return [...defaults, ...(config?.extends ?? [])];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
|
|
5
38
|
return {
|
|
6
39
|
name: '@astrojs/mdx',
|
|
7
40
|
hooks: {
|
|
8
|
-
'astro:config:setup': ({ updateConfig, addPageExtension, command }: any) => {
|
|
41
|
+
'astro:config:setup': ({ updateConfig, config, addPageExtension, command }: any) => {
|
|
9
42
|
addPageExtension('.mdx');
|
|
43
|
+
let remarkPlugins = handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS);
|
|
44
|
+
let rehypePlugins = handleExtends(mdxOptions.rehypePlugins);
|
|
45
|
+
|
|
46
|
+
if (config.markdown.syntaxHighlight === 'shiki') {
|
|
47
|
+
remarkPlugins.push([
|
|
48
|
+
// Default export still requires ".default" chaining for some reason
|
|
49
|
+
// Workarounds tried:
|
|
50
|
+
// - "import * as remarkShikiTwoslash"
|
|
51
|
+
// - "import { default as remarkShikiTwoslash }"
|
|
52
|
+
(remarkShikiTwoslash as any).default,
|
|
53
|
+
config.markdown.shikiConfig,
|
|
54
|
+
]);
|
|
55
|
+
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (config.markdown.syntaxHighlight === 'prism') {
|
|
59
|
+
remarkPlugins.push(remarkPrism);
|
|
60
|
+
rehypePlugins.push([rehypeRaw, { passThrough: nodeTypes }]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
remarkPlugins.push(remarkFrontmatter);
|
|
64
|
+
remarkPlugins.push([
|
|
65
|
+
remarkMdxFrontmatter,
|
|
66
|
+
{
|
|
67
|
+
name: 'frontmatter',
|
|
68
|
+
...mdxOptions.frontmatterOptions,
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
const configuredMdxPlugin = mdxPlugin({
|
|
73
|
+
remarkPlugins,
|
|
74
|
+
rehypePlugins,
|
|
75
|
+
jsx: true,
|
|
76
|
+
jsxImportSource: 'astro',
|
|
77
|
+
// Note: disable `.md` support
|
|
78
|
+
format: 'mdx',
|
|
79
|
+
mdExtensions: [],
|
|
80
|
+
});
|
|
81
|
+
|
|
10
82
|
updateConfig({
|
|
11
83
|
vite: {
|
|
12
84
|
plugins: [
|
|
13
85
|
{
|
|
14
86
|
enforce: 'pre',
|
|
15
|
-
...
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
87
|
+
...configuredMdxPlugin,
|
|
88
|
+
// Override transform to inject layouts before MDX compilation
|
|
89
|
+
async transform(this, code, id) {
|
|
90
|
+
if (!id.endsWith('.mdx')) return;
|
|
91
|
+
|
|
92
|
+
const mdxPluginTransform = configuredMdxPlugin.transform?.bind(this);
|
|
93
|
+
// If user overrides our default YAML parser,
|
|
94
|
+
// do not attempt to parse the `layout` via gray-matter
|
|
95
|
+
if (mdxOptions.frontmatterOptions?.parsers) {
|
|
96
|
+
return mdxPluginTransform?.(code, id);
|
|
97
|
+
}
|
|
98
|
+
const frontmatter = getFrontmatter(code, id);
|
|
99
|
+
if (frontmatter.layout) {
|
|
100
|
+
const { layout, ...content } = frontmatter;
|
|
101
|
+
code += `\nexport default async function({ children }) {\nconst Layout = (await import(${JSON.stringify(
|
|
102
|
+
frontmatter.layout
|
|
103
|
+
)})).default;\nreturn <Layout content={${JSON.stringify(
|
|
104
|
+
content
|
|
105
|
+
)}}>{children}</Layout> }`;
|
|
106
|
+
}
|
|
107
|
+
return mdxPluginTransform?.(code, id);
|
|
108
|
+
},
|
|
22
109
|
},
|
|
23
|
-
|
|
110
|
+
{
|
|
24
111
|
name: '@astrojs/mdx',
|
|
25
|
-
transform(code
|
|
112
|
+
transform(code, id) {
|
|
26
113
|
if (!id.endsWith('.mdx')) return;
|
|
27
|
-
|
|
28
|
-
|
|
114
|
+
const [, moduleExports] = parseESM(code);
|
|
115
|
+
|
|
116
|
+
// This adds support for injected "page-ssr" scripts in MDX files.
|
|
117
|
+
// TODO: This should only be happening on page entrypoints, not all imported MDX.
|
|
118
|
+
// TODO: This code is copy-pasted across all Astro/Vite plugins that deal with page
|
|
119
|
+
// entrypoints (.astro, .md, .mdx). This should be handled in some centralized place,
|
|
120
|
+
// or otherwise refactored to not require copy-paste handling logic.
|
|
121
|
+
code += `\nimport "${'astro:scripts/page-ssr.js'}";`;
|
|
122
|
+
|
|
123
|
+
const { fileUrl, fileId } = getFileInfo(id, config);
|
|
124
|
+
if (!moduleExports.includes('url')) {
|
|
125
|
+
code += `\nexport const url = ${JSON.stringify(fileUrl)};`;
|
|
126
|
+
}
|
|
127
|
+
if (!moduleExports.includes('file')) {
|
|
128
|
+
code += `\nexport const file = ${JSON.stringify(fileId)};`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (command === 'dev') {
|
|
132
|
+
// TODO: decline HMR updates until we have a stable approach
|
|
133
|
+
code += `\nif (import.meta.hot) {
|
|
29
134
|
import.meta.hot.decline();
|
|
30
135
|
}`;
|
|
136
|
+
}
|
|
137
|
+
return code;
|
|
31
138
|
},
|
|
32
139
|
},
|
|
33
|
-
],
|
|
140
|
+
] as VitePlugin[],
|
|
34
141
|
},
|
|
35
142
|
});
|
|
36
143
|
},
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// TODO: discuss extracting this file to @astrojs/prism
|
|
2
|
+
import { addAstro } from '@astrojs/prism/internal';
|
|
3
|
+
import Prism from 'prismjs';
|
|
4
|
+
import loadLanguages from 'prismjs/components/index.js';
|
|
5
|
+
import { visit } from 'unist-util-visit';
|
|
6
|
+
|
|
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
|
+
/** */
|
|
47
|
+
export default function remarkPrism() {
|
|
48
|
+
return (tree: any) =>
|
|
49
|
+
visit(tree, 'code', (node: any) => {
|
|
50
|
+
let { lang, value } = node;
|
|
51
|
+
node.type = 'html';
|
|
52
|
+
|
|
53
|
+
let { html, classLanguage } = runHighlighter(lang, value);
|
|
54
|
+
let classes = [classLanguage];
|
|
55
|
+
node.value = `<pre class="${classes.join(
|
|
56
|
+
' '
|
|
57
|
+
)}"><code class="${classLanguage}">${html}</code></pre>`;
|
|
58
|
+
return node;
|
|
59
|
+
});
|
|
60
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AstroConfig, SSRError } from 'astro';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
|
|
4
|
+
function appendForwardSlash(path: string) {
|
|
5
|
+
return path.endsWith('/') ? path : path + '/';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface FileInfo {
|
|
9
|
+
fileId: string;
|
|
10
|
+
fileUrl: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** @see 'vite-plugin-utils' for source */
|
|
14
|
+
export function getFileInfo(id: string, config: AstroConfig): FileInfo {
|
|
15
|
+
const sitePathname = appendForwardSlash(
|
|
16
|
+
config.site ? new URL(config.base, config.site).pathname : config.base
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Try to grab the file's actual URL
|
|
20
|
+
let url: URL | undefined = undefined;
|
|
21
|
+
try {
|
|
22
|
+
url = new URL(`file://${id}`);
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
const fileId = id.split('?')[0];
|
|
26
|
+
let fileUrl: string;
|
|
27
|
+
const isPage = fileId.includes('/pages/');
|
|
28
|
+
if (isPage) {
|
|
29
|
+
fileUrl = fileId.replace(/^.*?\/pages\//, sitePathname).replace(/(\/index)?\.mdx$/, '');
|
|
30
|
+
} else if (url && url.pathname.startsWith(config.root.pathname)) {
|
|
31
|
+
fileUrl = url.pathname.slice(config.root.pathname.length);
|
|
32
|
+
} else {
|
|
33
|
+
fileUrl = fileId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (fileUrl && config.trailingSlash === 'always') {
|
|
37
|
+
fileUrl = appendForwardSlash(fileUrl);
|
|
38
|
+
}
|
|
39
|
+
return { fileId, fileUrl };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Match YAML exception handling from Astro core errors
|
|
44
|
+
* @see 'astro/src/core/errors.ts'
|
|
45
|
+
*/
|
|
46
|
+
export function getFrontmatter(code: string, id: string) {
|
|
47
|
+
try {
|
|
48
|
+
return matter(code).data;
|
|
49
|
+
} catch (e: any) {
|
|
50
|
+
if (e.name === 'YAMLException') {
|
|
51
|
+
const err: SSRError = e;
|
|
52
|
+
err.id = id;
|
|
53
|
+
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
54
|
+
err.message = e.reason;
|
|
55
|
+
throw err;
|
|
56
|
+
} else {
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { content = { title: "Didn't work" } } = Astro.props;
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!DOCTYPE html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8">
|
|
9
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
11
|
+
<title>{content.title}</title>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<h1>{content.title}</h1>
|
|
15
|
+
<p data-layout-rendered>Layout rendered!</p>
|
|
16
|
+
<slot />
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
export const getStaticPaths = async () => {
|
|
3
|
+
const content = await Astro.glob('../content/*.mdx');
|
|
4
|
+
|
|
5
|
+
return content
|
|
6
|
+
.filter((page) => !page.frontmatter.draft) // skip drafts
|
|
7
|
+
.map(({ default: MdxContent, frontmatter, url, file }) => {
|
|
8
|
+
return {
|
|
9
|
+
params: { slug: frontmatter.slug || "index" },
|
|
10
|
+
props: {
|
|
11
|
+
MdxContent,
|
|
12
|
+
file,
|
|
13
|
+
frontmatter,
|
|
14
|
+
url
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { MdxContent, frontmatter, url, file } = Astro.props;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<html>
|
|
24
|
+
<head>
|
|
25
|
+
<title>Page</title>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<MdxContent />
|
|
29
|
+
|
|
30
|
+
<div id="one">{frontmatter.one}</div>
|
|
31
|
+
<div id="url">{url}</div>
|
|
32
|
+
<div id="file">{file}</div>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# TOC test
|
|
2
|
+
|
|
3
|
+
## Table of contents
|
|
4
|
+
|
|
5
|
+
## Section 1
|
|
6
|
+
|
|
7
|
+
Some text!
|
|
8
|
+
|
|
9
|
+
### Subsection 1
|
|
10
|
+
|
|
11
|
+
Some subsection test!
|
|
12
|
+
|
|
13
|
+
### Subsection 2
|
|
14
|
+
|
|
15
|
+
Oh cool, more text!
|
|
16
|
+
|
|
17
|
+
## Section 2
|
|
18
|
+
|
|
19
|
+
And section 2, with a hyperlink to check GFM is preserved: https://handle-me-gfm.com
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# I'm a page with a url of "/test-1!"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# I'm a page with a url of "/test-2!"
|
|
@@ -0,0 +1,76 @@
|
|
|
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-frontmatter/', import.meta.url);
|
|
8
|
+
|
|
9
|
+
describe('MDX frontmatter', () => {
|
|
10
|
+
it('builds when "frontmatter.property" is in JSX expression', async () => {
|
|
11
|
+
const fixture = await loadFixture({
|
|
12
|
+
root: FIXTURE_ROOT,
|
|
13
|
+
integrations: [mdx()],
|
|
14
|
+
});
|
|
15
|
+
await fixture.build();
|
|
16
|
+
expect(true).to.equal(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('extracts frontmatter to "frontmatter" export', async () => {
|
|
20
|
+
const fixture = await loadFixture({
|
|
21
|
+
root: FIXTURE_ROOT,
|
|
22
|
+
integrations: [mdx()],
|
|
23
|
+
});
|
|
24
|
+
await fixture.build();
|
|
25
|
+
|
|
26
|
+
const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
|
|
27
|
+
expect(titles).to.include('Using YAML frontmatter');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders layout from "layout" frontmatter property', async () => {
|
|
31
|
+
const fixture = await loadFixture({
|
|
32
|
+
root: FIXTURE_ROOT,
|
|
33
|
+
integrations: [mdx()],
|
|
34
|
+
});
|
|
35
|
+
await fixture.build();
|
|
36
|
+
|
|
37
|
+
const html = await fixture.readFile('/index.html');
|
|
38
|
+
const { document } = parseHTML(html);
|
|
39
|
+
|
|
40
|
+
const layoutParagraph = document.querySelector('[data-layout-rendered]');
|
|
41
|
+
|
|
42
|
+
expect(layoutParagraph).to.not.be.null;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('passes frontmatter to layout via "content" prop', async () => {
|
|
46
|
+
const fixture = await loadFixture({
|
|
47
|
+
root: FIXTURE_ROOT,
|
|
48
|
+
integrations: [mdx()],
|
|
49
|
+
});
|
|
50
|
+
await fixture.build();
|
|
51
|
+
|
|
52
|
+
const html = await fixture.readFile('/index.html');
|
|
53
|
+
const { document } = parseHTML(html);
|
|
54
|
+
|
|
55
|
+
const h1 = document.querySelector('h1');
|
|
56
|
+
|
|
57
|
+
expect(h1.textContent).to.equal('Using YAML frontmatter');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('extracts frontmatter to "customFrontmatter" export when configured', async () => {
|
|
61
|
+
const fixture = await loadFixture({
|
|
62
|
+
root: new URL('./fixtures/mdx-custom-frontmatter-name/', import.meta.url),
|
|
63
|
+
integrations: [
|
|
64
|
+
mdx({
|
|
65
|
+
frontmatterOptions: {
|
|
66
|
+
name: 'customFrontmatter',
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
await fixture.build();
|
|
72
|
+
|
|
73
|
+
const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
|
|
74
|
+
expect(titles).to.include('Using YAML frontmatter');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import mdx from '@astrojs/mdx';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import { loadFixture } from '../../../astro/test/test-utils.js';
|
|
5
|
+
import * as cheerio from 'cheerio';
|
|
6
|
+
|
|
7
|
+
const FIXTURE_ROOT = new URL('./fixtures/mdx-get-static-paths', import.meta.url);
|
|
8
|
+
|
|
9
|
+
describe('getStaticPaths', () => {
|
|
10
|
+
/** @type {import('astro/test/test-utils').Fixture} */
|
|
11
|
+
let fixture;
|
|
12
|
+
before(async () => {
|
|
13
|
+
fixture = await loadFixture({
|
|
14
|
+
root: FIXTURE_ROOT,
|
|
15
|
+
integrations: [mdx()],
|
|
16
|
+
});
|
|
17
|
+
await fixture.build();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('Provides file and url', async () => {
|
|
21
|
+
const html = await fixture.readFile('/one/index.html');
|
|
22
|
+
|
|
23
|
+
const $ = cheerio.load(html);
|
|
24
|
+
expect($('p').text()).to.equal('First mdx file');
|
|
25
|
+
expect($('#one').text()).to.equal('hello', 'Frontmatter included');
|
|
26
|
+
expect($('#url').text()).to.equal('/src/content/1.mdx', 'url is included');
|
|
27
|
+
expect($('#file').text()).to.contain(
|
|
28
|
+
'fixtures/mdx-get-static-paths/src/content/1.mdx',
|
|
29
|
+
'file is included'
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
import remarkToc from 'remark-toc';
|
|
7
|
+
|
|
8
|
+
const FIXTURE_ROOT = new URL('./fixtures/mdx-remark-plugins/', import.meta.url);
|
|
9
|
+
|
|
10
|
+
describe('MDX remark plugins', () => {
|
|
11
|
+
it('supports custom remark plugins - TOC', async () => {
|
|
12
|
+
const fixture = await loadFixture({
|
|
13
|
+
root: FIXTURE_ROOT,
|
|
14
|
+
integrations: [
|
|
15
|
+
mdx({
|
|
16
|
+
remarkPlugins: [remarkToc],
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
await fixture.build();
|
|
21
|
+
|
|
22
|
+
const html = await fixture.readFile('/with-toc/index.html');
|
|
23
|
+
const { document } = parseHTML(html);
|
|
24
|
+
|
|
25
|
+
const tocLink1 = document.querySelector('ul a[href="#section-1"]');
|
|
26
|
+
expect(tocLink1).to.not.be.null;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('applies GitHub-flavored markdown by default', async () => {
|
|
30
|
+
const fixture = await loadFixture({
|
|
31
|
+
root: FIXTURE_ROOT,
|
|
32
|
+
integrations: [mdx()],
|
|
33
|
+
});
|
|
34
|
+
await fixture.build();
|
|
35
|
+
|
|
36
|
+
const html = await fixture.readFile('/with-gfm/index.html');
|
|
37
|
+
const { document } = parseHTML(html);
|
|
38
|
+
|
|
39
|
+
const autoGenLink = document.querySelector('a[href="https://example.com"]');
|
|
40
|
+
expect(autoGenLink).to.not.be.null;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('preserves default GitHub-flavored markdown with "extends"', async () => {
|
|
44
|
+
const fixture = await loadFixture({
|
|
45
|
+
root: FIXTURE_ROOT,
|
|
46
|
+
integrations: [
|
|
47
|
+
mdx({
|
|
48
|
+
remarkPlugins: { extends: [remarkToc] },
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
await fixture.build();
|
|
53
|
+
|
|
54
|
+
const html = await fixture.readFile('/with-toc/index.html');
|
|
55
|
+
const { document } = parseHTML(html);
|
|
56
|
+
|
|
57
|
+
const tocLink1 = document.querySelector('ul a[href="#section-1"]');
|
|
58
|
+
expect(tocLink1).to.not.be.null;
|
|
59
|
+
const autoGenLink = document.querySelector('a[href="https://handle-me-gfm.com"]');
|
|
60
|
+
expect(autoGenLink).to.not.be.null;
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
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-syntax-hightlighting/', import.meta.url);
|
|
8
|
+
|
|
9
|
+
describe('MDX syntax highlighting', () => {
|
|
10
|
+
describe('shiki', () => {
|
|
11
|
+
it('works', async () => {
|
|
12
|
+
const fixture = await loadFixture({
|
|
13
|
+
root: FIXTURE_ROOT,
|
|
14
|
+
markdown: {
|
|
15
|
+
syntaxHighlight: 'shiki',
|
|
16
|
+
},
|
|
17
|
+
integrations: [mdx()],
|
|
18
|
+
});
|
|
19
|
+
await fixture.build();
|
|
20
|
+
|
|
21
|
+
const html = await fixture.readFile('/index.html');
|
|
22
|
+
const { document } = parseHTML(html);
|
|
23
|
+
|
|
24
|
+
const shikiCodeBlock = document.querySelector('pre.shiki');
|
|
25
|
+
expect(shikiCodeBlock).to.not.be.null;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('respects markdown.shikiConfig.theme', async () => {
|
|
29
|
+
const fixture = await loadFixture({
|
|
30
|
+
root: FIXTURE_ROOT,
|
|
31
|
+
markdown: {
|
|
32
|
+
syntaxHighlight: 'shiki',
|
|
33
|
+
shikiConfig: {
|
|
34
|
+
theme: 'dracula',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
integrations: [mdx()],
|
|
38
|
+
});
|
|
39
|
+
await fixture.build();
|
|
40
|
+
|
|
41
|
+
const html = await fixture.readFile('/index.html');
|
|
42
|
+
const { document } = parseHTML(html);
|
|
43
|
+
|
|
44
|
+
const shikiCodeBlock = document.querySelector('pre.shiki.dracula');
|
|
45
|
+
expect(shikiCodeBlock).to.not.be.null;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('prism', () => {
|
|
50
|
+
it('works', async () => {
|
|
51
|
+
const fixture = await loadFixture({
|
|
52
|
+
root: FIXTURE_ROOT,
|
|
53
|
+
markdown: {
|
|
54
|
+
syntaxHighlight: 'prism',
|
|
55
|
+
},
|
|
56
|
+
integrations: [mdx()],
|
|
57
|
+
});
|
|
58
|
+
await fixture.build();
|
|
59
|
+
|
|
60
|
+
const html = await fixture.readFile('/index.html');
|
|
61
|
+
const { document } = parseHTML(html);
|
|
62
|
+
|
|
63
|
+
const prismCodeBlock = document.querySelector('pre.language-astro');
|
|
64
|
+
expect(prismCodeBlock).to.not.be.null;
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|