diff --git a/.changeset/curly-queens-pay.md b/.changeset/curly-queens-pay.md new file mode 100644 index 000000000..9199a6d02 --- /dev/null +++ b/.changeset/curly-queens-pay.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +- Allow Markdown with scoped styles to coexist happily with code syntax highlighting via Prism diff --git a/packages/astro/components/Prism.astro b/packages/astro/components/Prism.astro index 9b1f99717..b96f4c358 100644 --- a/packages/astro/components/Prism.astro +++ b/packages/astro/components/Prism.astro @@ -3,7 +3,8 @@ import Prism from 'prismjs'; import { addAstro } from '@astrojs/prism'; import loadLanguages from 'prismjs/components/index.js'; -const { lang, code } = Astro.props; +const { class: className, lang, code } = Astro.props; +let classLanguage = `language-${lang}` const languageMap = new Map([ ['ts', 'typescript'] @@ -39,7 +40,6 @@ if (grammar) { html = Prism.highlight(code, grammar, lang); } -let className = lang ? `language-${lang}` : ''; --- -
{html}
+
{html}
diff --git a/packages/astro/src/compiler/transform/prism.ts b/packages/astro/src/compiler/transform/prism.ts index 0afb8194b..9fe241810 100644 --- a/packages/astro/src/compiler/transform/prism.ts +++ b/packages/astro/src/compiler/transform/prism.ts @@ -4,6 +4,7 @@ import { getAttrValue } from '../../ast.js'; export const PRISM_IMPORT = `import Prism from 'astro/components/Prism.astro';`; const prismImportExp = /import Prism from ['"]astro\/components\/Prism.astro['"]/; + /** escaping code samples that contain template string replacement parts, ${foo} or example. */ function escape(code: string) { return code @@ -22,6 +23,7 @@ function unescapeCode(code: TemplateNode) { return child; }); } + /** default export - Transform prism */ export default function (module: Script): Transformer { let usesPrism = false; @@ -43,15 +45,17 @@ export default function (module: Script): Transformer { const className = getAttrValue(codeEl.attributes, 'class') || ''; const classes = className.split(' '); - let lang; + let lang: string | undefined; for (let cn of classes) { const matches = /language-(.+)/.exec(cn); if (matches) { lang = matches[1]; + break; } } if (!lang) return; + let classesWithoutLang = classes.filter((cn) => cn !== `language-${lang}`); let codeData = codeEl.children && codeEl.children[0]; if (!codeData) return; @@ -74,6 +78,17 @@ export default function (module: Script): Transformer { }, ], }, + { + type: 'Attribute', + name: 'class', + value: [ + { + type: 'Text', + raw: classesWithoutLang.join(' '), + data: classesWithoutLang.join(' '), + }, + ], + }, { type: 'Attribute', name: 'code', diff --git a/packages/astro/test/astro-markdown.test.js b/packages/astro/test/astro-markdown.test.js index b8b3c6bb0..72b976b73 100644 --- a/packages/astro/test/astro-markdown.test.js +++ b/packages/astro/test/astro-markdown.test.js @@ -35,6 +35,16 @@ Markdown('Runs code blocks through syntax highlighter', async ({ runtime }) => { assert.ok($el.length > 0, 'There are child spans in code blocks'); }); +Markdown('Scoped styles should not break syntax highlight', async ({ runtime }) => { + const result = await runtime.load('/scopedStyles-code'); + assert.ok(!result.error, `build error: ${result.error}`); + + const $ = doc(result.contents); + assert.ok($('pre').is('[class]'), 'Pre tag has scopedStyle class passed down'); + assert.ok($('code').hasClass('language-js'), 'Code tag has correct language'); + assert.ok($('code span').length > 0, 'There are child spans in code blocks'); +}); + Markdown('Bundles client-side JS for prod', async (context) => { await context.build(); diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/scopedStyles-code.astro b/packages/astro/test/fixtures/astro-markdown/src/pages/scopedStyles-code.astro new file mode 100644 index 000000000..17986d979 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown/src/pages/scopedStyles-code.astro @@ -0,0 +1,14 @@ +--- +import { Markdown } from 'astro/components'; +import Layout from '../layouts/content.astro'; + +--- + + + ## Interesting Topic + + ```js + const thing = () => {}; + ``` + + diff --git a/packages/markdown-support/src/index.ts b/packages/markdown-support/src/index.ts index 5278d44d6..86d95a414 100644 --- a/packages/markdown-support/src/index.ts +++ b/packages/markdown-support/src/index.ts @@ -4,7 +4,7 @@ import createCollectHeaders from './rehype-collect-headers.js'; import scopedStyles from './remark-scoped-styles.js'; import remarkExpressions from './remark-expressions.js'; import rehypeExpressions from './rehype-expressions.js'; -import { rehypeCodeBlock } from './codeblock.js'; +import { remarkCodeBlock, rehypeCodeBlock } from './codeblock.js'; import { loadPlugins } from './load-plugins.js'; import raw from 'rehype-raw'; @@ -56,6 +56,7 @@ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOp parser.use(scopedStyles(scopedClassName)); } + parser.use(remarkCodeBlock()); parser.use(markdownToHtml, { allowDangerousHtml: true, passThrough: ['raw', 'mdxTextExpression'] }); parser.use(rehypeExpressions);