@astrojs/mdx 0.9.0 → 0.10.2-next.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.
@@ -1,5 +1,5 @@
1
- @astrojs/mdx:build: cache hit, replaying output 5d04eed831bc49e1
2
- @astrojs/mdx:build: 
3
- @astrojs/mdx:build: > @astrojs/mdx@0.9.0 build /home/runner/work/astro/astro/packages/integrations/mdx
4
- @astrojs/mdx:build: > astro-scripts build "src/**/*.ts" && tsc
5
- @astrojs/mdx:build: 
1
+ @astrojs/mdx:build: cache hit, replaying output 9bf2042a57eb9fcc
2
+ @astrojs/mdx:build: 
3
+ @astrojs/mdx:build: > @astrojs/mdx@0.10.2-next.0 build /home/runner/work/astro/astro/packages/integrations/mdx
4
+ @astrojs/mdx:build: > astro-scripts build "src/**/*.ts" && tsc
5
+ @astrojs/mdx:build: 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @astrojs/mdx
2
2
 
3
+ ## 0.10.2-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4423](https://github.com/withastro/astro/pull/4423) [`d4cd7a59f`](https://github.com/withastro/astro/commit/d4cd7a59fd38d411c442a818cfaab40f74106628) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Update Markdown type signature to match new markdown plugin,and update top-level layout props for better alignment
8
+
9
+ ## 0.10.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#4443](https://github.com/withastro/astro/pull/4443) [`adb207979`](https://github.com/withastro/astro/commit/adb20797962c280d4d38f335f577fd52a1b48d4b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix MDX style imports when layout is not applied
14
+
15
+ * [#4428](https://github.com/withastro/astro/pull/4428) [`a2414bf59`](https://github.com/withastro/astro/commit/a2414bf59e2e2cd633aece68e724401c4ad281b9) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix dev server reload performance when globbing from an MDX layout
16
+
17
+ ## 0.10.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [#4292](https://github.com/withastro/astro/pull/4292) [`f1a52c18a`](https://github.com/withastro/astro/commit/f1a52c18afe66e6d310743ae6884be76f69be265) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Switch from Shiki Twoslash to Astro's Shiki Markdown highlighter
22
+
3
23
  ## 0.9.0
4
24
 
5
25
  ### Minor Changes
package/README.md CHANGED
@@ -26,11 +26,11 @@ The `astro add` command-line tool automates the installation for you. Run one of
26
26
 
27
27
  ```sh
28
28
  # Using NPM
29
- npx astro add mdx
29
+ npm run astro add mdx
30
30
  # Using Yarn
31
31
  yarn astro add mdx
32
32
  # Using PNPM
33
- pnpx astro add mdx
33
+ pnpm astro add mdx
34
34
  ```
35
35
 
36
36
  Then, restart the dev server by typing `CTRL-C` and then `npm run astro dev` in the terminal window that was running Astro.
@@ -253,7 +253,7 @@ const { title, fancyJsHelper } = Astro.props;
253
253
 
254
254
  The MDX integration respects [your project's `markdown.syntaxHighlight` configuration](https://docs.astro.build/en/guides/markdown-content/#syntax-highlighting).
255
255
 
256
- We will highlight your code blocks with [Shiki](https://github.com/shikijs/shiki) by default [using Shiki twoslash](https://shikijs.github.io/twoslash/). You can customize [this remark plugin](https://www.npmjs.com/package/remark-shiki-twoslash) using the `markdown.shikiConfig` option in your `astro.config`. For example, you can apply a different built-in theme like so:
256
+ We will highlight your code blocks with [Shiki](https://github.com/shikijs/shiki) by default. You can customize this highlighter using the `markdown.shikiConfig` option in your `astro.config`. For example, you can apply a different built-in theme like so:
257
257
 
258
258
  ```js
259
259
  // astro.config.mjs
@@ -285,6 +285,23 @@ export default {
285
285
 
286
286
  This applies a minimal Prism renderer with added support for `astro` code blocks. Visit [our "Prism configuration" docs](https://docs.astro.build/en/guides/markdown-content/#prism-configuration) for more on using Prism with Astro.
287
287
 
288
+ #### Switch to a custom syntax highlighter
289
+
290
+ You may want to apply your own syntax highlighter too. If your highlighter offers a remark or rehype plugin, you can flip off our syntax highlighting by setting `markdown.syntaxHighlight: false` and wiring up your plugin. For example, say you want to apply [Shiki Twoslash's remark plugin](https://www.npmjs.com/package/remark-shiki-twoslash):
291
+
292
+ ```js
293
+ // astro.config.mjs
294
+ import shikiTwoslash from 'remark-shiki-twoslash';
295
+
296
+ export default {
297
+ markdown: {
298
+ syntaxHighlight: false,
299
+ },
300
+ integrations: [mdx({
301
+ remarkPlugins: [shikiTwoslash, { /* Shiki Twoslash config */ }],
302
+ })],
303
+ ```
304
+
288
305
  ## Configuration
289
306
 
290
307
  ### remarkPlugins
@@ -18,9 +18,9 @@ function rehypeApplyFrontmatterExport(pageFrontmatter) {
18
18
  exportNodes.unshift(
19
19
  jsToTreeNode(
20
20
  `import { jsx as layoutJsx } from 'astro/jsx-runtime';
21
- import Layout from ${JSON.stringify(frontmatter.layout)};
22
21
 
23
- export default function ({ children }) {
22
+ export default async function ({ children }) {
23
+ const Layout = (await import(${JSON.stringify(frontmatter.layout)})).default;
24
24
  const { layout, ...content } = frontmatter;
25
25
  content.file = file;
26
26
  content.url = url;
@@ -41,6 +41,8 @@ function rehypeApplyFrontmatterExport(pageFrontmatter) {
41
41
  }
42
42
  });
43
43
  return layoutJsx(Layout, {
44
+ file,
45
+ url,
44
46
  content,
45
47
  frontmatter: content,
46
48
  headings: getHeadings(),
package/dist/index.js CHANGED
@@ -3,12 +3,12 @@ import mdxPlugin from "@mdx-js/rollup";
3
3
  import { parse as parseESM } from "es-module-lexer";
4
4
  import rehypeRaw from "rehype-raw";
5
5
  import remarkGfm from "remark-gfm";
6
- import remarkShikiTwoslash from "remark-shiki-twoslash";
7
6
  import remarkSmartypants from "remark-smartypants";
8
7
  import { VFile } from "vfile";
9
8
  import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from "./astro-data-utils.js";
10
9
  import rehypeCollectHeadings from "./rehype-collect-headings.js";
11
10
  import remarkPrism from "./remark-prism.js";
11
+ import remarkShiki from "./remark-shiki.js";
12
12
  import { getFileInfo, parseFrontmatter } from "./utils.js";
13
13
  const DEFAULT_REMARK_PLUGINS = [
14
14
  remarkGfm,
@@ -22,14 +22,13 @@ function handleExtends(config, defaults = []) {
22
22
  return config;
23
23
  return [...defaults, ...(config == null ? void 0 : config.extends) ?? []];
24
24
  }
25
- function getRemarkPlugins(mdxOptions, config) {
25
+ async function getRemarkPlugins(mdxOptions, config) {
26
26
  let remarkPlugins = [
27
27
  remarkInitializeAstroData,
28
28
  ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS)
29
29
  ];
30
30
  if (config.markdown.syntaxHighlight === "shiki") {
31
- const shikiTwoslash = remarkShikiTwoslash.default ?? remarkShikiTwoslash;
32
- remarkPlugins.push([shikiTwoslash, config.markdown.shikiConfig]);
31
+ remarkPlugins.push([await remarkShiki(config.markdown.shikiConfig)]);
33
32
  }
34
33
  if (config.markdown.syntaxHighlight === "prism") {
35
34
  remarkPlugins.push(remarkPrism);
@@ -37,10 +36,10 @@ function getRemarkPlugins(mdxOptions, config) {
37
36
  return remarkPlugins;
38
37
  }
39
38
  function getRehypePlugins(mdxOptions, config) {
40
- let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
41
- if (config.markdown.syntaxHighlight === "shiki" || config.markdown.syntaxHighlight === "prism") {
42
- rehypePlugins.unshift([rehypeRaw, { passThrough: nodeTypes }]);
43
- }
39
+ let rehypePlugins = [
40
+ [rehypeRaw, { passThrough: nodeTypes }],
41
+ ...handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS)
42
+ ];
44
43
  rehypePlugins.unshift(rehypeCollectHeadings);
45
44
  return rehypePlugins;
46
45
  }
@@ -48,10 +47,10 @@ function mdx(mdxOptions = {}) {
48
47
  return {
49
48
  name: "@astrojs/mdx",
50
49
  hooks: {
51
- "astro:config:setup": ({ updateConfig, config, addPageExtension, command }) => {
50
+ "astro:config:setup": async ({ updateConfig, config, addPageExtension, command }) => {
52
51
  addPageExtension(".mdx");
53
52
  const mdxPluginOpts = {
54
- remarkPlugins: getRemarkPlugins(mdxOptions, config),
53
+ remarkPlugins: await getRemarkPlugins(mdxOptions, config),
55
54
  rehypePlugins: getRehypePlugins(mdxOptions, config),
56
55
  jsx: true,
57
56
  jsxImportSource: "astro",
@@ -86,6 +85,8 @@ function mdx(mdxOptions = {}) {
86
85
  transform(code, id) {
87
86
  if (!id.endsWith(".mdx"))
88
87
  return;
88
+ code += `
89
+ MDXContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
89
90
  const [, moduleExports] = parseESM(code);
90
91
  const { fileUrl, fileId } = getFileInfo(id, config);
91
92
  if (!moduleExports.includes("url")) {
@@ -0,0 +1,3 @@
1
+ import type { ShikiConfig } from 'astro';
2
+ declare const remarkShiki: ({ langs, theme, wrap }: ShikiConfig) => Promise<() => (tree: any) => void>;
3
+ export default remarkShiki;
@@ -0,0 +1,58 @@
1
+ import { getHighlighter } from "shiki";
2
+ import { visit } from "unist-util-visit";
3
+ const highlighterCacheAsync = /* @__PURE__ */ new Map();
4
+ const remarkShiki = async ({ langs = [], theme = "github-dark", wrap = false }) => {
5
+ const cacheID = typeof theme === "string" ? theme : theme.name;
6
+ let highlighterAsync = highlighterCacheAsync.get(cacheID);
7
+ if (!highlighterAsync) {
8
+ highlighterAsync = getHighlighter({ theme });
9
+ highlighterCacheAsync.set(cacheID, highlighterAsync);
10
+ }
11
+ const highlighter = await highlighterAsync;
12
+ for (const lang of langs) {
13
+ await highlighter.loadLanguage(lang);
14
+ }
15
+ return () => (tree) => {
16
+ visit(tree, "code", (node) => {
17
+ let lang;
18
+ if (typeof node.lang === "string") {
19
+ const langExists = highlighter.getLoadedLanguages().includes(node.lang);
20
+ if (langExists) {
21
+ lang = node.lang;
22
+ } else {
23
+ console.warn(`The language "${node.lang}" doesn't exist, falling back to plaintext.`);
24
+ lang = "plaintext";
25
+ }
26
+ } else {
27
+ lang = "plaintext";
28
+ }
29
+ let html = highlighter.codeToHtml(node.value, { lang });
30
+ html = html.replace('<pre class="shiki"', `<pre class="astro-code"`);
31
+ html = html.replace(
32
+ /style="(background-)?color: var\(--shiki-/g,
33
+ 'style="$1color: var(--astro-code-'
34
+ );
35
+ if (node.lang === "diff") {
36
+ html = html.replace(
37
+ /<span class="line"><span style="(.*?)">([\+|\-])/g,
38
+ '<span class="line"><span style="$1"><span style="user-select: none;">$2</span>'
39
+ );
40
+ }
41
+ if (wrap === false) {
42
+ html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto;"');
43
+ } else if (wrap === true) {
44
+ html = html.replace(
45
+ /style="(.*?)"/,
46
+ 'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
47
+ );
48
+ }
49
+ node.type = "html";
50
+ node.value = html;
51
+ node.children = [];
52
+ });
53
+ };
54
+ };
55
+ var remark_shiki_default = remarkShiki;
56
+ export {
57
+ remark_shiki_default as default
58
+ };
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.9.0",
4
+ "version": "0.10.2-next.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -33,7 +33,6 @@
33
33
  "rehype-raw": "^6.1.1",
34
34
  "remark-frontmatter": "^4.0.1",
35
35
  "remark-gfm": "^3.0.1",
36
- "remark-shiki-twoslash": "^3.1.0",
37
36
  "remark-smartypants": "^2.0.0",
38
37
  "shiki": "^0.10.1",
39
38
  "unist-util-visit": "^4.1.0",
@@ -43,13 +42,14 @@
43
42
  "@types/chai": "^4.3.1",
44
43
  "@types/mocha": "^9.1.1",
45
44
  "@types/yargs-parser": "^21.0.0",
46
- "astro": "1.0.4",
45
+ "astro": "1.1.0-next.0",
47
46
  "astro-scripts": "0.0.7",
48
47
  "chai": "^4.3.6",
49
48
  "linkedom": "^0.14.12",
50
49
  "mdast-util-to-string": "^3.1.0",
51
50
  "mocha": "^9.2.2",
52
51
  "reading-time": "^1.5.0",
52
+ "remark-shiki-twoslash": "^3.1.0",
53
53
  "remark-toc": "^8.0.1"
54
54
  },
55
55
  "engines": {
@@ -20,13 +20,17 @@ export function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any
20
20
  jsToTreeNode(`export const ${EXPORT_NAME} = ${JSON.stringify(frontmatter)};`),
21
21
  ];
22
22
  if (frontmatter.layout) {
23
+ // NOTE(bholmesdev) 08-22-2022
24
+ // Using an async layout import (i.e. `const Layout = (await import...)`)
25
+ // Preserves the dev server import cache when globbing a large set of MDX files
26
+ // Full explanation: 'https://github.com/withastro/astro/pull/4428'
23
27
  exportNodes.unshift(
24
28
  jsToTreeNode(
25
29
  /** @see 'vite-plugin-markdown' for layout props reference */
26
30
  `import { jsx as layoutJsx } from 'astro/jsx-runtime';
27
- import Layout from ${JSON.stringify(frontmatter.layout)};
28
31
 
29
- export default function ({ children }) {
32
+ export default async function ({ children }) {
33
+ const Layout = (await import(${JSON.stringify(frontmatter.layout)})).default;
30
34
  const { layout, ...content } = frontmatter;
31
35
  content.file = file;
32
36
  content.url = url;
@@ -47,6 +51,8 @@ export function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any
47
51
  }
48
52
  });
49
53
  return layoutJsx(Layout, {
54
+ file,
55
+ url,
50
56
  content,
51
57
  frontmatter: content,
52
58
  headings: getHeadings(),
package/src/index.ts CHANGED
@@ -4,13 +4,13 @@ import type { AstroConfig, AstroIntegration } from 'astro';
4
4
  import { parse as parseESM } from 'es-module-lexer';
5
5
  import rehypeRaw from 'rehype-raw';
6
6
  import remarkGfm from 'remark-gfm';
7
- import remarkShikiTwoslash from 'remark-shiki-twoslash';
8
7
  import remarkSmartypants from 'remark-smartypants';
9
8
  import { VFile } from 'vfile';
10
9
  import type { Plugin as VitePlugin } from 'vite';
11
10
  import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from './astro-data-utils.js';
12
11
  import rehypeCollectHeadings from './rehype-collect-headings.js';
13
12
  import remarkPrism from './remark-prism.js';
13
+ import remarkShiki from './remark-shiki.js';
14
14
  import { getFileInfo, parseFrontmatter } from './utils.js';
15
15
 
16
16
  type WithExtends<T> = T | { extends: T };
@@ -38,22 +38,17 @@ function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] =
38
38
  return [...defaults, ...(config?.extends ?? [])];
39
39
  }
40
40
 
41
- function getRemarkPlugins(
41
+ async function getRemarkPlugins(
42
42
  mdxOptions: MdxOptions,
43
43
  config: AstroConfig
44
- ): MdxRollupPluginOptions['remarkPlugins'] {
44
+ ): Promise<MdxRollupPluginOptions['remarkPlugins']> {
45
45
  let remarkPlugins = [
46
46
  // Initialize vfile.data.astroExports before all plugins are run
47
47
  remarkInitializeAstroData,
48
48
  ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS),
49
49
  ];
50
50
  if (config.markdown.syntaxHighlight === 'shiki') {
51
- // Default export still requires ".default" chaining for some reason
52
- // Workarounds tried:
53
- // - "import * as remarkShikiTwoslash"
54
- // - "import { default as remarkShikiTwoslash }"
55
- const shikiTwoslash = (remarkShikiTwoslash as any).default ?? remarkShikiTwoslash;
56
- remarkPlugins.push([shikiTwoslash, config.markdown.shikiConfig]);
51
+ remarkPlugins.push([await remarkShiki(config.markdown.shikiConfig)]);
57
52
  }
58
53
  if (config.markdown.syntaxHighlight === 'prism') {
59
54
  remarkPlugins.push(remarkPrism);
@@ -65,11 +60,11 @@ function getRehypePlugins(
65
60
  mdxOptions: MdxOptions,
66
61
  config: AstroConfig
67
62
  ): MdxRollupPluginOptions['rehypePlugins'] {
68
- let rehypePlugins = handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS);
63
+ let rehypePlugins = [
64
+ [rehypeRaw, { passThrough: nodeTypes }] as any,
65
+ ...handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS),
66
+ ];
69
67
 
70
- if (config.markdown.syntaxHighlight === 'shiki' || config.markdown.syntaxHighlight === 'prism') {
71
- rehypePlugins.unshift([rehypeRaw, { passThrough: nodeTypes }]);
72
- }
73
68
  // getHeadings() is guaranteed by TS, so we can't allow user to override
74
69
  rehypePlugins.unshift(rehypeCollectHeadings);
75
70
 
@@ -80,11 +75,11 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
80
75
  return {
81
76
  name: '@astrojs/mdx',
82
77
  hooks: {
83
- 'astro:config:setup': ({ updateConfig, config, addPageExtension, command }: any) => {
78
+ 'astro:config:setup': async ({ updateConfig, config, addPageExtension, command }: any) => {
84
79
  addPageExtension('.mdx');
85
80
 
86
81
  const mdxPluginOpts: MdxRollupPluginOptions = {
87
- remarkPlugins: getRemarkPlugins(mdxOptions, config),
82
+ remarkPlugins: await getRemarkPlugins(mdxOptions, config),
88
83
  rehypePlugins: getRehypePlugins(mdxOptions, config),
89
84
  jsx: true,
90
85
  jsxImportSource: 'astro',
@@ -124,6 +119,11 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
124
119
  // These transforms must happen *after* JSX runtime transformations
125
120
  transform(code, id) {
126
121
  if (!id.endsWith('.mdx')) return;
122
+
123
+ // Ensures styles and scripts are injected into a `<head>`
124
+ // When a layout is not applied
125
+ code += `\nMDXContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
126
+
127
127
  const [, moduleExports] = parseESM(code);
128
128
 
129
129
  const { fileUrl, fileId } = getFileInfo(id, config);
@@ -0,0 +1,85 @@
1
+ import type { ShikiConfig } from 'astro';
2
+ import type * as shiki from 'shiki';
3
+ import { getHighlighter } from 'shiki';
4
+ import { visit } from 'unist-util-visit';
5
+
6
+ /**
7
+ * getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page,
8
+ * cache it here as much as possible. Make sure that your highlighters can be cached, state-free.
9
+ * We make this async, so that multiple calls to parse markdown still share the same highlighter.
10
+ */
11
+ const highlighterCacheAsync = new Map<string, Promise<shiki.Highlighter>>();
12
+
13
+ const remarkShiki = async ({ langs = [], theme = 'github-dark', wrap = false }: ShikiConfig) => {
14
+ const cacheID: string = typeof theme === 'string' ? theme : theme.name;
15
+ let highlighterAsync = highlighterCacheAsync.get(cacheID);
16
+ if (!highlighterAsync) {
17
+ highlighterAsync = getHighlighter({ theme });
18
+ highlighterCacheAsync.set(cacheID, highlighterAsync);
19
+ }
20
+ const highlighter = await highlighterAsync;
21
+
22
+ // NOTE: There may be a performance issue here for large sites that use `lang`.
23
+ // Since this will be called on every page load. Unclear how to fix this.
24
+ for (const lang of langs) {
25
+ await highlighter.loadLanguage(lang);
26
+ }
27
+
28
+ return () => (tree: any) => {
29
+ visit(tree, 'code', (node) => {
30
+ let lang: string;
31
+
32
+ if (typeof node.lang === 'string') {
33
+ const langExists = highlighter.getLoadedLanguages().includes(node.lang);
34
+ if (langExists) {
35
+ lang = node.lang;
36
+ } else {
37
+ // eslint-disable-next-line no-console
38
+ console.warn(`The language "${node.lang}" doesn't exist, falling back to plaintext.`);
39
+ lang = 'plaintext';
40
+ }
41
+ } else {
42
+ lang = 'plaintext';
43
+ }
44
+
45
+ let html = highlighter!.codeToHtml(node.value, { lang });
46
+
47
+ // Q: Couldn't these regexes match on a user's inputted code blocks?
48
+ // A: Nope! All rendered HTML is properly escaped.
49
+ // Ex. If a user typed `<span class="line"` into a code block,
50
+ // It would become this before hitting our regexes:
51
+ // &lt;span class=&quot;line&quot;
52
+
53
+ // Replace "shiki" class naming with "astro".
54
+ html = html.replace('<pre class="shiki"', `<pre class="astro-code"`);
55
+ // Replace "shiki" css variable naming with "astro".
56
+ html = html.replace(
57
+ /style="(background-)?color: var\(--shiki-/g,
58
+ 'style="$1color: var(--astro-code-'
59
+ );
60
+ // Add "user-select: none;" for "+"/"-" diff symbols
61
+ if (node.lang === 'diff') {
62
+ html = html.replace(
63
+ /<span class="line"><span style="(.*?)">([\+|\-])/g,
64
+ '<span class="line"><span style="$1"><span style="user-select: none;">$2</span>'
65
+ );
66
+ }
67
+ // Handle code wrapping
68
+ // if wrap=null, do nothing.
69
+ if (wrap === false) {
70
+ html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto;"');
71
+ } else if (wrap === true) {
72
+ html = html.replace(
73
+ /style="(.*?)"/,
74
+ 'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
75
+ );
76
+ }
77
+
78
+ node.type = 'html';
79
+ node.value = html;
80
+ node.children = [];
81
+ });
82
+ };
83
+ };
84
+
85
+ export default remarkShiki;
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  const {
3
3
  content = { title: "content didn't work" },
4
+ file = "file didn't work",
5
+ url = "url didn't work",
4
6
  frontmatter = {
5
7
  title: "frontmatter didn't work",
6
8
  file: "file didn't work",
@@ -24,6 +26,8 @@ const {
24
26
  <p data-frontmatter-title>{frontmatter.title}</p>
25
27
  <p data-frontmatter-file>{frontmatter.file}</p>
26
28
  <p data-frontmatter-url>{frontmatter.url}</p>
29
+ <p data-file>{frontmatter.file}</p>
30
+ <p data-url>{frontmatter.url}</p>
27
31
  <p data-layout-rendered>Layout rendered!</p>
28
32
  <ul data-headings>
29
33
  {headings.map(heading => <li>{heading.slug}</li>)}
@@ -1 +1,3 @@
1
+ import '../styles.css'
2
+
1
3
  # Hello page!
@@ -0,0 +1,3 @@
1
+ p {
2
+ color: red;
3
+ }
@@ -57,17 +57,21 @@ describe('MDX frontmatter', () => {
57
57
  expect(headingSlugs).to.contain('section-2');
58
58
  });
59
59
 
60
- it('passes "file" and "url" to layout via frontmatter', async () => {
60
+ it('passes "file" and "url" to layout', async () => {
61
61
  const html = await fixture.readFile('/with-headings/index.html');
62
62
  const { document } = parseHTML(html);
63
63
 
64
64
  const frontmatterFile = document.querySelector('[data-frontmatter-file]')?.textContent;
65
65
  const frontmatterUrl = document.querySelector('[data-frontmatter-url]')?.textContent;
66
+ const file = document.querySelector('[data-file]')?.textContent;
67
+ const url = document.querySelector('[data-url]')?.textContent;
66
68
 
67
69
  expect(frontmatterFile?.endsWith('with-headings.mdx')).to.equal(
68
70
  true,
69
71
  '"file" prop does not end with correct path or is undefined'
70
72
  );
71
73
  expect(frontmatterUrl).to.equal('/with-headings');
74
+ expect(file).to.equal(frontmatterFile);
75
+ expect(url).to.equal(frontmatterUrl);
72
76
  });
73
77
  });
@@ -26,6 +26,15 @@ describe('MDX Page', () => {
26
26
 
27
27
  expect(h1.textContent).to.equal('Hello page!');
28
28
  });
29
+
30
+ it('injects style imports when layout is not applied', async () => {
31
+ const html = await fixture.readFile('/index.html');
32
+ const { document } = parseHTML(html);
33
+
34
+ const stylesheet = document.querySelector('link[rel="stylesheet"]');
35
+
36
+ expect(stylesheet).to.not.be.null;
37
+ });
29
38
  });
30
39
 
31
40
  describe('dev', () => {
@@ -3,6 +3,7 @@ import mdx from '@astrojs/mdx';
3
3
  import { expect } from 'chai';
4
4
  import { parseHTML } from 'linkedom';
5
5
  import { loadFixture } from '../../../astro/test/test-utils.js';
6
+ import shikiTwoslash from 'remark-shiki-twoslash';
6
7
 
7
8
  const FIXTURE_ROOT = new URL('./fixtures/mdx-syntax-hightlighting/', import.meta.url);
8
9
 
@@ -21,8 +22,9 @@ describe('MDX syntax highlighting', () => {
21
22
  const html = await fixture.readFile('/index.html');
22
23
  const { document } = parseHTML(html);
23
24
 
24
- const shikiCodeBlock = document.querySelector('pre.shiki');
25
+ const shikiCodeBlock = document.querySelector('pre.astro-code');
25
26
  expect(shikiCodeBlock).to.not.be.null;
27
+ expect(shikiCodeBlock.getAttribute('style')).to.contain('background-color:#0d1117');
26
28
  });
27
29
 
28
30
  it('respects markdown.shikiConfig.theme', async () => {
@@ -41,8 +43,9 @@ describe('MDX syntax highlighting', () => {
41
43
  const html = await fixture.readFile('/index.html');
42
44
  const { document } = parseHTML(html);
43
45
 
44
- const shikiCodeBlock = document.querySelector('pre.shiki.dracula');
46
+ const shikiCodeBlock = document.querySelector('pre.astro-code');
45
47
  expect(shikiCodeBlock).to.not.be.null;
48
+ expect(shikiCodeBlock.getAttribute('style')).to.contain('background-color:#282A36');
46
49
  });
47
50
  });
48
51
 
@@ -64,4 +67,25 @@ describe('MDX syntax highlighting', () => {
64
67
  expect(prismCodeBlock).to.not.be.null;
65
68
  });
66
69
  });
70
+
71
+ it('supports custom highlighter - shiki-twoslash', async () => {
72
+ const fixture = await loadFixture({
73
+ root: FIXTURE_ROOT,
74
+ markdown: {
75
+ syntaxHighlight: false,
76
+ },
77
+ integrations: [
78
+ mdx({
79
+ remarkPlugins: [shikiTwoslash.default ?? shikiTwoslash],
80
+ }),
81
+ ],
82
+ });
83
+ await fixture.build();
84
+
85
+ const html = await fixture.readFile('/index.html');
86
+ const { document } = parseHTML(html);
87
+
88
+ const twoslashCodeBlock = document.querySelector('pre.shiki');
89
+ expect(twoslashCodeBlock).to.not.be.null;
90
+ });
67
91
  });