diff --git a/.changeset/heavy-kangaroos-sin.md b/.changeset/heavy-kangaroos-sin.md new file mode 100644 index 000000000000..65dce57a3674 --- /dev/null +++ b/.changeset/heavy-kangaroos-sin.md @@ -0,0 +1,5 @@ +--- +'@astrojs/mdx': patch +--- + +Preserve code element node `data.meta` in `properties.metastring` for rehype syntax highlighters, like `rehype-pretty-code`` diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index 6c4c921a7926..1cbb2d8d5846 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -39,6 +39,7 @@ "github-slugger": "^1.4.0", "gray-matter": "^4.0.3", "kleur": "^4.1.4", + "rehype-pretty-code": "^0.4.0", "rehype-raw": "^6.1.1", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", diff --git a/packages/integrations/mdx/src/plugins.ts b/packages/integrations/mdx/src/plugins.ts index eb53fa629db8..70c5b15458f6 100644 --- a/packages/integrations/mdx/src/plugins.ts +++ b/packages/integrations/mdx/src/plugins.ts @@ -11,6 +11,7 @@ import remarkSmartypants from 'remark-smartypants'; import type { Data, VFile } from 'vfile'; import { MdxOptions } from './index.js'; import rehypeCollectHeadings from './rehype-collect-headings.js'; +import rehypeMetaString from './rehype-meta-string.js'; import remarkPrism from './remark-prism.js'; import remarkShiki from './remark-shiki.js'; import { jsToTreeNode } from './utils.js'; @@ -150,6 +151,8 @@ export function getRehypePlugins( let rehypePlugins: PluggableList = [ // getHeadings() is guaranteed by TS, so we can't allow user to override rehypeCollectHeadings, + // ensure `data.meta` is preserved in `properties.metastring` for rehype syntax highlighters + rehypeMetaString, // rehypeRaw allows custom syntax highlighters to work without added config [rehypeRaw, { passThrough: nodeTypes }] as any, ]; diff --git a/packages/integrations/mdx/src/rehype-meta-string.ts b/packages/integrations/mdx/src/rehype-meta-string.ts new file mode 100644 index 000000000000..c3f2dbd2fe31 --- /dev/null +++ b/packages/integrations/mdx/src/rehype-meta-string.ts @@ -0,0 +1,17 @@ +import { visit } from 'unist-util-visit'; + +/** + * Moves `data.meta` to `properties.metastring` for the `code` element node + * as `rehype-raw` strips `data` from all nodes, which may contain useful information. + * e.g. ```js {1:3} => metastring: "{1:3}" + */ +export default function rehypeMetaString() { + return function (tree: any) { + visit(tree, (node) => { + if (node.type === 'element' && node.tagName === 'code' && node.data?.meta) { + node.properties ??= {}; + node.properties.metastring = node.data.meta; + } + }); + }; +} diff --git a/packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx index 23338ffd8b0c..866387e570db 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx +++ b/packages/integrations/mdx/test/fixtures/mdx-syntax-hightlighting/src/pages/index.mdx @@ -1,6 +1,6 @@ # Syntax highlighting -```astro +```astro {2} --- const handlesAstroSyntax = true --- diff --git a/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js b/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js index 70da75357214..6203ed82ccab 100644 --- a/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js +++ b/packages/integrations/mdx/test/mdx-syntax-highlighting.test.js @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { parseHTML } from 'linkedom'; import { loadFixture } from '../../../astro/test/test-utils.js'; import shikiTwoslash from 'remark-shiki-twoslash'; +import rehypePrettyCode from 'rehype-pretty-code'; const FIXTURE_ROOT = new URL('./fixtures/mdx-syntax-hightlighting/', import.meta.url); @@ -88,4 +89,31 @@ describe('MDX syntax highlighting', () => { const twoslashCodeBlock = document.querySelector('pre.shiki'); expect(twoslashCodeBlock).to.not.be.null; }); + + it('supports custom highlighter - rehype-pretty-code', async () => { + const fixture = await loadFixture({ + root: FIXTURE_ROOT, + markdown: { + syntaxHighlight: false, + }, + integrations: [ + mdx({ + rehypePlugins: [ + [ + rehypePrettyCode, + { + onVisitHighlightedLine(node) { + node.properties.style = 'background-color:#000000'; + }, + }, + ], + ], + }), + ], + }); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + expect(html).to.include('style="background-color:#000000"') + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b76e967bee6..fc1705719c2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2782,6 +2782,7 @@ importers: mdast-util-to-string: ^3.1.0 mocha: ^9.2.2 reading-time: ^1.5.0 + rehype-pretty-code: ^0.4.0 rehype-raw: ^6.1.1 remark-frontmatter: ^4.0.1 remark-gfm: ^3.0.1 @@ -2802,6 +2803,7 @@ importers: github-slugger: 1.5.0 gray-matter: 4.0.3 kleur: 4.1.5 + rehype-pretty-code: 0.4.0_shiki@0.11.1 rehype-raw: 6.1.1 remark-frontmatter: 4.0.1 remark-gfm: 3.0.1 @@ -15280,6 +15282,10 @@ packages: unist-util-visit-children: 1.1.4 dev: false + /parse-numeric-range/1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + dev: false + /parse-package-name/1.0.0: resolution: {integrity: sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==} dev: true @@ -16210,6 +16216,16 @@ packages: unified: 10.1.2 dev: false + /rehype-pretty-code/0.4.0_shiki@0.11.1: + resolution: {integrity: sha512-Bp91nfo4blpgCXlvGP1hsG+kRFfjqBVU09o1RFcnNA62u+iIzJiJRGzpfBj4FaItq7CEQL5ASGB7vLxN5xCvyA==} + engines: {node: ^12.16.0 || >=13.2.0} + peerDependencies: + shiki: '*' + dependencies: + parse-numeric-range: 1.3.0 + shiki: 0.11.1 + dev: false + /rehype-raw/6.1.1: resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==} dependencies: