@astrojs/mdx 0.13.0 → 0.15.0-beta.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/src/plugins.ts CHANGED
@@ -1,20 +1,27 @@
1
+ import { rehypeHeadingIds } from '@astrojs/markdown-remark';
2
+ import {
3
+ InvalidAstroDataError,
4
+ safelyGetAstroData,
5
+ } from '@astrojs/markdown-remark/dist/internal.js';
1
6
  import { nodeTypes } from '@mdx-js/mdx';
2
7
  import type { PluggableList } from '@mdx-js/mdx/lib/core.js';
3
8
  import type { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
4
- import type { AstroConfig, MarkdownAstroData } from 'astro';
9
+ import type { AstroConfig } from 'astro';
5
10
  import type { Literal, MemberExpression } from 'estree';
6
11
  import { visit as estreeVisit } from 'estree-util-visit';
7
12
  import { bold, yellow } from 'kleur/colors';
13
+ import type { Image } from 'mdast';
14
+ import { pathToFileURL } from 'node:url';
8
15
  import rehypeRaw from 'rehype-raw';
9
16
  import remarkGfm from 'remark-gfm';
10
- import remarkSmartypants from 'remark-smartypants';
11
- import type { Data, VFile } from 'vfile';
17
+ import { visit } from 'unist-util-visit';
18
+ import type { VFile } from 'vfile';
12
19
  import { MdxOptions } from './index.js';
13
- import rehypeCollectHeadings from './rehype-collect-headings.js';
20
+ import { rehypeInjectHeadingsExport } from './rehype-collect-headings.js';
14
21
  import rehypeMetaString from './rehype-meta-string.js';
15
22
  import remarkPrism from './remark-prism.js';
16
23
  import remarkShiki from './remark-shiki.js';
17
- import { jsToTreeNode } from './utils.js';
24
+ import { isRelativePath, jsToTreeNode } from './utils.js';
18
25
 
19
26
  export function recmaInjectImportMetaEnvPlugin({
20
27
  importMetaEnv,
@@ -43,26 +50,18 @@ export function recmaInjectImportMetaEnvPlugin({
43
50
  };
44
51
  }
45
52
 
46
- export function remarkInitializeAstroData() {
53
+ export function rehypeApplyFrontmatterExport() {
47
54
  return function (tree: any, vfile: VFile) {
48
- if (!vfile.data.astro) {
49
- vfile.data.astro = { frontmatter: {} };
50
- }
51
- };
52
- }
53
-
54
- export function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any>) {
55
- return function (tree: any, vfile: VFile) {
56
- const { frontmatter: injectedFrontmatter } = safelyGetAstroData(vfile.data);
57
- const frontmatter = { ...injectedFrontmatter, ...pageFrontmatter };
55
+ const astroData = safelyGetAstroData(vfile.data);
56
+ if (astroData instanceof InvalidAstroDataError)
57
+ throw new Error(
58
+ // Copied from Astro core `errors-data`
59
+ // TODO: find way to import error data from core
60
+ '[MDX] A remark or rehype plugin attempted to inject invalid frontmatter. Ensure "astro.frontmatter" is set to a valid JSON object that is not `null` or `undefined`.'
61
+ );
62
+ const { frontmatter } = astroData;
58
63
  const exportNodes = [
59
- jsToTreeNode(
60
- `export const frontmatter = ${JSON.stringify(
61
- frontmatter
62
- )};\nexport const _internal = { injectedFrontmatter: ${JSON.stringify(
63
- injectedFrontmatter
64
- )} };`
65
- ),
64
+ jsToTreeNode(`export const frontmatter = ${JSON.stringify(frontmatter)};`),
66
65
  ];
67
66
  if (frontmatter.layout) {
68
67
  // NOTE(bholmesdev) 08-22-2022
@@ -112,80 +111,79 @@ export function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any
112
111
  };
113
112
  }
114
113
 
115
- const DEFAULT_REMARK_PLUGINS: PluggableList = [remarkGfm, remarkSmartypants];
116
- const DEFAULT_REHYPE_PLUGINS: PluggableList = [];
114
+ /**
115
+ * `src/content/` does not support relative image paths.
116
+ * This plugin throws an error if any are found
117
+ */
118
+ function toRemarkContentRelImageError({ srcDir }: { srcDir: URL }) {
119
+ const contentDir = new URL('content/', srcDir);
120
+ return function remarkContentRelImageError() {
121
+ return (tree: any, vfile: VFile) => {
122
+ const isContentFile = pathToFileURL(vfile.path).href.startsWith(contentDir.href);
123
+ if (!isContentFile) return;
124
+
125
+ const relImagePaths = new Set<string>();
126
+ visit(tree, 'image', function raiseError(node: Image) {
127
+ if (isRelativePath(node.url)) {
128
+ relImagePaths.add(node.url);
129
+ }
130
+ });
131
+ if (relImagePaths.size === 0) return;
132
+
133
+ const errorMessage =
134
+ `Relative image paths are not supported in the content/ directory. Place local images in the public/ directory and use absolute paths (see https://docs.astro.build/en/guides/images/#in-markdown-files):\n` +
135
+ [...relImagePaths].map((path) => JSON.stringify(path)).join(',\n');
136
+
137
+ throw new Error(errorMessage);
138
+ };
139
+ };
140
+ }
117
141
 
118
142
  export async function getRemarkPlugins(
119
143
  mdxOptions: MdxOptions,
120
144
  config: AstroConfig
121
145
  ): Promise<MdxRollupPluginOptions['remarkPlugins']> {
122
- let remarkPlugins: PluggableList = [
123
- // Set "vfile.data.astro" for plugins to inject frontmatter
124
- remarkInitializeAstroData,
125
- ];
126
- switch (mdxOptions.extendPlugins) {
127
- case false:
128
- break;
129
- case 'astroDefaults':
130
- remarkPlugins = [...remarkPlugins, ...DEFAULT_REMARK_PLUGINS];
131
- break;
132
- default:
133
- remarkPlugins = [
134
- ...remarkPlugins,
135
- ...(markdownShouldExtendDefaultPlugins(config) ? DEFAULT_REMARK_PLUGINS : []),
136
- ...ignoreStringPlugins(config.markdown.remarkPlugins ?? []),
137
- ];
138
- break;
139
- }
140
- if (config.markdown.syntaxHighlight === 'shiki') {
141
- remarkPlugins.push([await remarkShiki(config.markdown.shikiConfig)]);
146
+ let remarkPlugins: PluggableList = [];
147
+ if (mdxOptions.syntaxHighlight === 'shiki') {
148
+ remarkPlugins.push([await remarkShiki(mdxOptions.shikiConfig)]);
142
149
  }
143
- if (config.markdown.syntaxHighlight === 'prism') {
150
+ if (mdxOptions.syntaxHighlight === 'prism') {
144
151
  remarkPlugins.push(remarkPrism);
145
152
  }
153
+ if (mdxOptions.gfm) {
154
+ remarkPlugins.push(remarkGfm);
155
+ }
156
+
157
+ remarkPlugins = [...remarkPlugins, ...ignoreStringPlugins(mdxOptions.remarkPlugins)];
146
158
 
147
- remarkPlugins = [...remarkPlugins, ...(mdxOptions.remarkPlugins ?? [])];
159
+ // Apply last in case user plugins resolve relative image paths
160
+ if (config.experimental.contentCollections) {
161
+ remarkPlugins.push(toRemarkContentRelImageError(config));
162
+ }
148
163
  return remarkPlugins;
149
164
  }
150
165
 
151
- export function getRehypePlugins(
152
- mdxOptions: MdxOptions,
153
- config: AstroConfig
154
- ): MdxRollupPluginOptions['rehypePlugins'] {
166
+ export function getRehypePlugins(mdxOptions: MdxOptions): MdxRollupPluginOptions['rehypePlugins'] {
155
167
  let rehypePlugins: PluggableList = [
156
- // getHeadings() is guaranteed by TS, so we can't allow user to override
157
- rehypeCollectHeadings,
158
168
  // ensure `data.meta` is preserved in `properties.metastring` for rehype syntax highlighters
159
169
  rehypeMetaString,
160
170
  // rehypeRaw allows custom syntax highlighters to work without added config
161
171
  [rehypeRaw, { passThrough: nodeTypes }] as any,
162
172
  ];
163
- switch (mdxOptions.extendPlugins) {
164
- case false:
165
- break;
166
- case 'astroDefaults':
167
- rehypePlugins = [...rehypePlugins, ...DEFAULT_REHYPE_PLUGINS];
168
- break;
169
- default:
170
- rehypePlugins = [
171
- ...rehypePlugins,
172
- ...(markdownShouldExtendDefaultPlugins(config) ? DEFAULT_REHYPE_PLUGINS : []),
173
- ...ignoreStringPlugins(config.markdown.rehypePlugins ?? []),
174
- ];
175
- break;
176
- }
177
173
 
178
- rehypePlugins = [...rehypePlugins, ...(mdxOptions.rehypePlugins ?? [])];
174
+ rehypePlugins = [
175
+ ...rehypePlugins,
176
+ ...ignoreStringPlugins(mdxOptions.rehypePlugins),
177
+ // getHeadings() is guaranteed by TS, so this must be included.
178
+ // We run `rehypeHeadingIds` _last_ to respect any custom IDs set by user plugins.
179
+ rehypeHeadingIds,
180
+ rehypeInjectHeadingsExport,
181
+ // computed from `astro.data.frontmatter` in VFile data
182
+ rehypeApplyFrontmatterExport,
183
+ ];
179
184
  return rehypePlugins;
180
185
  }
181
186
 
182
- function markdownShouldExtendDefaultPlugins(config: AstroConfig): boolean {
183
- return (
184
- config.markdown.extendDefaultPlugins ||
185
- (config.markdown.remarkPlugins.length === 0 && config.markdown.rehypePlugins.length === 0)
186
- );
187
- }
188
-
189
187
  function ignoreStringPlugins(plugins: any[]) {
190
188
  let validPlugins: PluggableList = [];
191
189
  let hasInvalidPlugin = false;
@@ -208,41 +206,6 @@ function ignoreStringPlugins(plugins: any[]) {
208
206
  return validPlugins;
209
207
  }
210
208
 
211
- /**
212
- * Copied from markdown utils
213
- * @see "vite-plugin-utils"
214
- */
215
- function isValidAstroData(obj: unknown): obj is MarkdownAstroData {
216
- if (typeof obj === 'object' && obj !== null && obj.hasOwnProperty('frontmatter')) {
217
- const { frontmatter } = obj as any;
218
- try {
219
- // ensure frontmatter is JSON-serializable
220
- JSON.stringify(frontmatter);
221
- } catch {
222
- return false;
223
- }
224
- return typeof frontmatter === 'object' && frontmatter !== null;
225
- }
226
- return false;
227
- }
228
-
229
- /**
230
- * Copied from markdown utils
231
- * @see "vite-plugin-utils"
232
- */
233
- function safelyGetAstroData(vfileData: Data): MarkdownAstroData {
234
- const { astro } = vfileData;
235
-
236
- if (!astro) return { frontmatter: {} };
237
- if (!isValidAstroData(astro)) {
238
- throw Error(
239
- `[MDX] A remark or rehype plugin tried to add invalid frontmatter. Ensure "astro.frontmatter" is a JSON object!`
240
- );
241
- }
242
-
243
- return astro;
244
- }
245
-
246
209
  /**
247
210
  * Check if estree entry is "import.meta.env.VARIABLE"
248
211
  * If it is, return the variable name (i.e. "VARIABLE")
@@ -1,48 +1,9 @@
1
- import Slugger from 'github-slugger';
2
- import { visit } from 'unist-util-visit';
1
+ import { MarkdownHeading, MarkdownVFile } from '@astrojs/markdown-remark';
3
2
  import { jsToTreeNode } from './utils.js';
4
3
 
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
- });
4
+ export function rehypeInjectHeadingsExport() {
5
+ return function (tree: any, file: MarkdownVFile) {
6
+ const headings: MarkdownHeading[] = file.data.__astroHeadings || [];
46
7
  tree.children.unshift(
47
8
  jsToTreeNode(`export function getHeadings() { return ${JSON.stringify(headings)} }`)
48
9
  );
package/src/utils.ts CHANGED
@@ -83,15 +83,20 @@ export function jsToTreeNode(
83
83
  };
84
84
  }
85
85
 
86
- // TODO: remove for 1.0
87
- export function handleExtendsNotSupported(pluginConfig: any) {
88
- if (
89
- typeof pluginConfig === 'object' &&
90
- pluginConfig !== null &&
91
- (pluginConfig as any).hasOwnProperty('extends')
92
- ) {
93
- throw new Error(
94
- `[MDX] The "extends" plugin option is no longer supported! Astro now extends your project's \`markdown\` plugin configuration by default. To customize this behavior, see the \`extendPlugins\` option instead: https://docs.astro.build/en/guides/integrations-guide/mdx/#extendplugins`
95
- );
96
- }
86
+ // Following utils taken from `packages/astro/src/core/path.ts`:
87
+ export function isRelativePath(path: string) {
88
+ return startsWithDotDotSlash(path) || startsWithDotSlash(path);
89
+ }
90
+
91
+ function startsWithDotDotSlash(path: string) {
92
+ const c1 = path[0];
93
+ const c2 = path[1];
94
+ const c3 = path[2];
95
+ return c1 === '.' && c2 === '.' && c3 === '/';
96
+ }
97
+
98
+ function startsWithDotSlash(path: string) {
99
+ const c1 = path[0];
100
+ const c2 = path[1];
101
+ return c1 === '.' && c2 === '/';
97
102
  }
@@ -1,12 +1,12 @@
1
1
  import { defineConfig } from 'astro/config';
2
2
  import mdx from '@astrojs/mdx';
3
- import { rehypeReadingTime, remarkTitle } from './src/markdown-plugins.mjs';
3
+ import { rehypeReadingTime, remarkDescription, remarkTitle } from './src/markdown-plugins.mjs';
4
4
 
5
5
  // https://astro.build/config
6
6
  export default defineConfig({
7
7
  site: 'https://astro.build/',
8
8
  integrations: [mdx({
9
- remarkPlugins: [remarkTitle],
9
+ remarkPlugins: [remarkTitle, remarkDescription],
10
10
  rehypePlugins: [rehypeReadingTime],
11
11
  })],
12
12
  });
@@ -18,3 +18,10 @@ export function remarkTitle() {
18
18
  });
19
19
  };
20
20
  }
21
+
22
+ export function remarkDescription() {
23
+ return function (tree, vfile) {
24
+ const { frontmatter } = vfile.data.astro;
25
+ frontmatter.description = `Processed by remarkDescription plugin: ${frontmatter.description}`
26
+ };
27
+ }
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  layout: '../layouts/Base.astro'
3
+ description: Page 1 description
3
4
  ---
4
5
 
5
6
  # Page 1
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  layout: '../layouts/Base.astro'
3
+ description: Page 2 description
3
4
  ---
4
5
 
5
6
  # Page 2
@@ -33,14 +33,11 @@ describe('MDX frontmatter injection', () => {
33
33
  }
34
34
  });
35
35
 
36
- it('overrides injected frontmatter with user frontmatter', async () => {
36
+ it('allow user frontmatter mutation', async () => {
37
37
  const frontmatterByPage = JSON.parse(await fixture.readFile('/glob.json'));
38
- const readingTimes = frontmatterByPage.map(
39
- (frontmatter = {}) => frontmatter.injectedReadingTime?.text
40
- );
41
- const titles = frontmatterByPage.map((frontmatter = {}) => frontmatter.title);
42
- expect(titles).to.contain('Overridden title');
43
- expect(readingTimes).to.contain('1000 min read');
38
+ const descriptions = frontmatterByPage.map((frontmatter = {}) => frontmatter.description);
39
+ expect(descriptions).to.contain('Processed by remarkDescription plugin: Page 1 description');
40
+ expect(descriptions).to.contain('Processed by remarkDescription plugin: Page 2 description');
44
41
  });
45
42
 
46
43
  it('passes injected frontmatter to layouts', async () => {
@@ -1,4 +1,6 @@
1
+ import { rehypeHeadingIds } from '@astrojs/markdown-remark';
1
2
  import mdx from '@astrojs/mdx';
3
+ import { visit } from 'unist-util-visit';
2
4
 
3
5
  import { expect } from 'chai';
4
6
  import { parseHTML } from 'linkedom';
@@ -58,3 +60,92 @@ describe('MDX getHeadings', () => {
58
60
  );
59
61
  });
60
62
  });
63
+
64
+ describe('MDX heading IDs can be customized by user plugins', () => {
65
+ let fixture;
66
+
67
+ before(async () => {
68
+ fixture = await loadFixture({
69
+ root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
70
+ integrations: [mdx()],
71
+ markdown: {
72
+ rehypePlugins: [
73
+ () => (tree) => {
74
+ let count = 0;
75
+ visit(tree, 'element', (node, index, parent) => {
76
+ if (!/^h\d$/.test(node.tagName)) return;
77
+ if (!node.properties?.id) {
78
+ node.properties = { ...node.properties, id: String(count++) };
79
+ }
80
+ });
81
+ },
82
+ ],
83
+ },
84
+ });
85
+
86
+ await fixture.build();
87
+ });
88
+
89
+ it('adds user-specified IDs to HTML output', async () => {
90
+ const html = await fixture.readFile('/test/index.html');
91
+ const { document } = parseHTML(html);
92
+
93
+ const h1 = document.querySelector('h1');
94
+ expect(h1?.textContent).to.equal('Heading test');
95
+ expect(h1?.getAttribute('id')).to.equal('0');
96
+
97
+ const headingIDs = document.querySelectorAll('h1,h2,h3').map((el) => el.id);
98
+ expect(JSON.stringify(headingIDs)).to.equal(
99
+ JSON.stringify(Array.from({ length: headingIDs.length }, (_, idx) => String(idx)))
100
+ );
101
+ });
102
+
103
+ it('generates correct getHeadings() export', async () => {
104
+ const { headingsByPage } = JSON.parse(await fixture.readFile('/pages.json'));
105
+ expect(JSON.stringify(headingsByPage['./test.mdx'])).to.equal(
106
+ JSON.stringify([
107
+ { depth: 1, slug: '0', text: 'Heading test' },
108
+ { depth: 2, slug: '1', text: 'Section 1' },
109
+ { depth: 3, slug: '2', text: 'Subsection 1' },
110
+ { depth: 3, slug: '3', text: 'Subsection 2' },
111
+ { depth: 2, slug: '4', text: 'Section 2' },
112
+ ])
113
+ );
114
+ });
115
+ });
116
+
117
+ describe('MDX heading IDs can be injected before user plugins', () => {
118
+ let fixture;
119
+
120
+ before(async () => {
121
+ fixture = await loadFixture({
122
+ root: new URL('./fixtures/mdx-get-headings/', import.meta.url),
123
+ integrations: [
124
+ mdx({
125
+ rehypePlugins: [
126
+ rehypeHeadingIds,
127
+ () => (tree) => {
128
+ visit(tree, 'element', (node, index, parent) => {
129
+ if (!/^h\d$/.test(node.tagName)) return;
130
+ if (node.properties?.id) {
131
+ node.children.push({ type: 'text', value: ' ' + node.properties.id });
132
+ }
133
+ });
134
+ },
135
+ ],
136
+ }),
137
+ ],
138
+ });
139
+
140
+ await fixture.build();
141
+ });
142
+
143
+ it('adds user-specified IDs to HTML output', async () => {
144
+ const html = await fixture.readFile('/test/index.html');
145
+ const { document } = parseHTML(html);
146
+
147
+ const h1 = document.querySelector('h1');
148
+ expect(h1?.textContent).to.equal('Heading test heading-test');
149
+ expect(h1?.id).to.equal('heading-test');
150
+ });
151
+ });
@@ -80,91 +80,57 @@ describe('MDX plugins', () => {
80
80
  expect(selectTocLink(document)).to.be.null;
81
81
  });
82
82
 
83
- it('respects "extendDefaultPlugins" when extending markdown', async () => {
84
- const fixture = await buildFixture({
85
- markdown: {
86
- remarkPlugins: [remarkExamplePlugin],
87
- rehypePlugins: [rehypeExamplePlugin],
88
- extendDefaultPlugins: true,
89
- },
90
- integrations: [mdx()],
91
- });
92
-
93
- const html = await fixture.readFile(FILE);
94
- const { document } = parseHTML(html);
95
-
96
- expect(selectRemarkExample(document)).to.not.be.null;
97
- expect(selectRehypeExample(document)).to.not.be.null;
98
- expect(selectGfmLink(document)).to.not.be.null;
99
- });
100
-
101
- it('extends markdown config with extendPlugins: "markdown"', async () => {
102
- const fixture = await buildFixture({
103
- markdown: {
104
- remarkPlugins: [remarkExamplePlugin],
105
- rehypePlugins: [rehypeExamplePlugin],
106
- },
107
- integrations: [
108
- mdx({
109
- extendPlugins: 'markdown',
110
- remarkPlugins: [remarkToc],
111
- }),
112
- ],
113
- });
114
-
115
- const html = await fixture.readFile(FILE);
116
- const { document } = parseHTML(html);
117
-
118
- expect(selectRemarkExample(document)).to.not.be.null;
119
- expect(selectRehypeExample(document)).to.not.be.null;
120
- expect(selectTocLink(document)).to.not.be.null;
121
- });
122
-
123
- it('extends default plugins with extendPlugins: "astroDefaults"', async () => {
124
- const fixture = await buildFixture({
125
- markdown: {
126
- // should NOT be applied to MDX
127
- remarkPlugins: [remarkToc],
128
- },
129
- integrations: [
130
- mdx({
131
- remarkPlugins: [remarkExamplePlugin],
132
- rehypePlugins: [rehypeExamplePlugin],
133
- extendPlugins: 'astroDefaults',
134
- }),
135
- ],
136
- });
137
-
138
- const html = await fixture.readFile(FILE);
139
- const { document } = parseHTML(html);
140
-
141
- expect(selectGfmLink(document)).to.not.be.null;
142
- // remark and rehype plugins still respected
143
- expect(selectRemarkExample(document)).to.not.be.null;
144
- expect(selectRehypeExample(document)).to.not.be.null;
145
- // Does NOT inherit TOC from markdown config
146
- expect(selectTocLink(document)).to.be.null;
147
- });
148
-
149
- it('does not extend default plugins with extendPlugins: false', async () => {
150
- const fixture = await buildFixture({
151
- markdown: {
152
- remarkPlugins: [remarkExamplePlugin],
153
- },
154
- integrations: [
155
- mdx({
156
- remarkPlugins: [],
157
- extendPlugins: false,
158
- }),
159
- ],
83
+ for (const extendMarkdownConfig of [true, false]) {
84
+ describe(`extendMarkdownConfig = ${extendMarkdownConfig}`, () => {
85
+ let fixture;
86
+ before(async () => {
87
+ fixture = await buildFixture({
88
+ markdown: {
89
+ remarkPlugins: [remarkToc],
90
+ gfm: false,
91
+ },
92
+ integrations: [
93
+ mdx({
94
+ extendMarkdownConfig,
95
+ remarkPlugins: [remarkExamplePlugin],
96
+ rehypePlugins: [rehypeExamplePlugin],
97
+ }),
98
+ ],
99
+ });
100
+ });
101
+
102
+ it('Handles MDX plugins', async () => {
103
+ const html = await fixture.readFile(FILE);
104
+ const { document } = parseHTML(html);
105
+
106
+ expect(selectRemarkExample(document, 'MDX remark plugins not applied.')).to.not.be.null;
107
+ expect(selectRehypeExample(document, 'MDX rehype plugins not applied.')).to.not.be.null;
108
+ });
109
+
110
+ it('Handles Markdown plugins', async () => {
111
+ const html = await fixture.readFile(FILE);
112
+ const { document } = parseHTML(html);
113
+
114
+ expect(
115
+ selectTocLink(
116
+ document,
117
+ '`remarkToc` plugin applied unexpectedly. Should override Markdown config.'
118
+ )
119
+ ).to.be.null;
120
+ });
121
+
122
+ it('Handles gfm', async () => {
123
+ const html = await fixture.readFile(FILE);
124
+ const { document } = parseHTML(html);
125
+
126
+ if (extendMarkdownConfig === true) {
127
+ expect(selectGfmLink(document), 'Does not respect `markdown.gfm` option.').to.be.null;
128
+ } else {
129
+ expect(selectGfmLink(document), 'Respects `markdown.gfm` unexpectedly.').to.not.be.null;
130
+ }
131
+ });
160
132
  });
161
-
162
- const html = await fixture.readFile(FILE);
163
- const { document } = parseHTML(html);
164
-
165
- expect(selectGfmLink(document)).to.be.null;
166
- expect(selectRemarkExample(document)).to.be.null;
167
- });
133
+ }
168
134
 
169
135
  it('supports custom recma plugins', async () => {
170
136
  const fixture = await buildFixture({
@@ -67,6 +67,32 @@ describe('MDX syntax highlighting', () => {
67
67
  const prismCodeBlock = document.querySelector('pre.language-astro');
68
68
  expect(prismCodeBlock).to.not.be.null;
69
69
  });
70
+
71
+ for (const extendMarkdownConfig of [true, false]) {
72
+ it(`respects syntaxHighlight when extendMarkdownConfig = ${extendMarkdownConfig}`, async () => {
73
+ const fixture = await loadFixture({
74
+ root: FIXTURE_ROOT,
75
+ markdown: {
76
+ syntaxHighlight: 'shiki',
77
+ },
78
+ integrations: [
79
+ mdx({
80
+ extendMarkdownConfig,
81
+ syntaxHighlight: 'prism',
82
+ }),
83
+ ],
84
+ });
85
+ await fixture.build();
86
+
87
+ const html = await fixture.readFile('/index.html');
88
+ const { document } = parseHTML(html);
89
+
90
+ const shikiCodeBlock = document.querySelector('pre.astro-code');
91
+ expect(shikiCodeBlock, 'Markdown config syntaxHighlight used unexpectedly').to.be.null;
92
+ const prismCodeBlock = document.querySelector('pre.language-astro');
93
+ expect(prismCodeBlock).to.not.be.null;
94
+ });
95
+ }
70
96
  });
71
97
 
72
98
  it('supports custom highlighter - shiki-twoslash', async () => {
@@ -1,7 +0,0 @@
1
- ---
2
- title: 'Overridden title'
3
- injectedReadingTime:
4
- text: '1000 min read'
5
- ---
6
-
7
- # Working!