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';
+
+---
+