@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.
Files changed (32) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +247 -1
  4. package/dist/index.d.ts +17 -1
  5. package/dist/index.js +87 -10
  6. package/dist/remark-prism.d.ts +2 -0
  7. package/dist/remark-prism.js +49 -0
  8. package/dist/utils.d.ts +15 -0
  9. package/dist/utils.js +47 -0
  10. package/package.json +18 -4
  11. package/src/index.ts +122 -15
  12. package/src/remark-prism.ts +60 -0
  13. package/src/utils.ts +60 -0
  14. package/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js +9 -0
  15. package/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx +6 -0
  16. package/test/fixtures/mdx-frontmatter/src/layouts/Base.astro +18 -0
  17. package/test/fixtures/mdx-frontmatter/src/pages/glob.json.js +9 -0
  18. package/test/fixtures/mdx-frontmatter/src/pages/index.mdx +7 -0
  19. package/test/fixtures/mdx-get-static-paths/src/content/1.mdx +5 -0
  20. package/test/fixtures/mdx-get-static-paths/src/pages/[slug].astro +34 -0
  21. package/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx +3 -0
  22. package/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx +19 -0
  23. package/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx +9 -0
  24. package/test/fixtures/mdx-url-export/src/pages/pages.json.js +9 -0
  25. package/test/fixtures/mdx-url-export/src/pages/test-1.mdx +1 -0
  26. package/test/fixtures/mdx-url-export/src/pages/test-2.mdx +1 -0
  27. package/test/fixtures/mdx-url-export/src/pages/with-url-override.mdx +3 -0
  28. package/test/mdx-frontmatter.test.js +76 -0
  29. package/test/mdx-get-static-paths.test.js +32 -0
  30. package/test/mdx-remark-plugins.test.js +62 -0
  31. package/test/mdx-syntax-highlighting.test.js +67 -0
  32. package/test/mdx-url-export.test.js +28 -0
package/src/index.ts CHANGED
@@ -1,36 +1,143 @@
1
- import mdxPlugin from '@mdx-js/rollup';
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
- export default function mdx(): AstroIntegration {
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
- ...mdxPlugin({
16
- jsx: true,
17
- jsxImportSource: 'astro',
18
- // Note: disable `.md` support
19
- format: 'mdx',
20
- mdExtensions: [],
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
- command === 'dev' && {
110
+ {
24
111
  name: '@astrojs/mdx',
25
- transform(code: string, id: string) {
112
+ transform(code, id) {
26
113
  if (!id.endsWith('.mdx')) return;
27
- // TODO: decline HMR updates until we have a stable approach
28
- return `${code}\nif (import.meta.hot) {
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,9 @@
1
+ export async function get() {
2
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
3
+
4
+ return {
5
+ body: JSON.stringify({
6
+ titles: Object.values(mdxPages ?? {}).map(v => v?.customFrontmatter?.title),
7
+ })
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: 'Using YAML frontmatter'
3
+ illThrowIfIDontExist: "Oh no, that's scary!"
4
+ ---
5
+
6
+ # {customFrontmatter.illThrowIfIDontExist}
@@ -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,9 @@
1
+ export async function get() {
2
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
3
+
4
+ return {
5
+ body: JSON.stringify({
6
+ titles: Object.values(mdxPages ?? {}).map(v => v?.frontmatter?.title),
7
+ })
8
+ }
9
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: 'Using YAML frontmatter'
3
+ layout: '../layouts/Base.astro'
4
+ illThrowIfIDontExist: "Oh no, that's scary!"
5
+ ---
6
+
7
+ {frontmatter.illThrowIfIDontExist}
@@ -0,0 +1,5 @@
1
+ ---
2
+ one: hello
3
+ slug: one
4
+ ---
5
+ First mdx file
@@ -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,3 @@
1
+ # GitHub-flavored Markdown test
2
+
3
+ This should auto-gen a link: https://example.com
@@ -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,9 @@
1
+ # Syntax highlighting
2
+
3
+ ```astro
4
+ ---
5
+ const handlesAstroSyntax = true
6
+ ---
7
+
8
+ <h1>{handlesAstroSyntax}</h1>
9
+ ```
@@ -0,0 +1,9 @@
1
+ export async function get() {
2
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
3
+
4
+ return {
5
+ body: JSON.stringify({
6
+ urls: Object.values(mdxPages ?? {}).map(v => v?.url),
7
+ })
8
+ }
9
+ }
@@ -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,3 @@
1
+ export const url = '/AH!'
2
+
3
+ # I'm a test with a url override!
@@ -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
+ });