summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/lemon-needles-count.md6
-rw-r--r--examples/blog/src/styles/blog.css21
-rw-r--r--examples/docs/src/components/HeadCommon.astro1
-rw-r--r--examples/docs/src/styles/code.css96
-rw-r--r--examples/docs/src/styles/index.css28
-rw-r--r--examples/with-markdown/src/styles/global.css169
-rw-r--r--packages/astro/test/astro-markdown.test.js36
-rw-r--r--packages/markdown/remark/src/index.ts8
-rw-r--r--packages/markdown/remark/src/remark-shiki.ts15
-rw-r--r--packages/markdown/remark/src/types.ts2
10 files changed, 70 insertions, 312 deletions
diff --git a/.changeset/lemon-needles-count.md b/.changeset/lemon-needles-count.md
new file mode 100644
index 000000000..c7770447d
--- /dev/null
+++ b/.changeset/lemon-needles-count.md
@@ -0,0 +1,6 @@
+---
+'@astrojs/markdown-remark': minor
+'astro': minor
+---
+
+Change shiki to our default markdown syntax highlighter. This includes updates to all relevant starter projects that used Prism-specific styles.
diff --git a/examples/blog/src/styles/blog.css b/examples/blog/src/styles/blog.css
index 12bf5390b..234e0162a 100644
--- a/examples/blog/src/styles/blog.css
+++ b/examples/blog/src/styles/blog.css
@@ -163,14 +163,14 @@ a {
gap: 0.5rem;
}
-a > code:not([class*='language']) {
+a > code {
position: relative;
color: var(--theme-accent);
background: transparent;
text-underline-offset: var(--padding-block);
}
-a > code:not([class*='language'])::before {
+a > code::before {
content: '';
position: absolute;
top: 0;
@@ -200,7 +200,7 @@ strong {
/* Supporting Content */
-code:not([class*='language']) {
+code {
--border-radius: 3px;
--padding-block: 0.2rem;
--padding-inline: 0.33rem;
@@ -215,28 +215,17 @@ code:not([class*='language']) {
word-break: break-word;
}
-pre > code:not([class*='language']) {
- background-color: transparent;
- padding: 0;
- margin: 0;
- border-radius: 0;
- color: inherit;
+pre.astro-code > code {
+ all: unset;
}
pre {
position: relative;
- background-color: var(--theme-code-bg);
- color: var(--theme-code-text);
--padding-block: 1rem;
--padding-inline: 2rem;
padding: var(--padding-block) var(--padding-inline);
padding-right: calc(var(--padding-inline) * 2);
- margin-left: calc(50vw - var(--padding-inline));
- transform: translateX(-50);
- line-height: 1.414;
- width: calc(100vw + (var(--padding-inline) * 2));
- max-width: calc(100% + (var(--padding-inline) * 2));
overflow-y: hidden;
overflow-x: auto;
}
diff --git a/examples/docs/src/components/HeadCommon.astro b/examples/docs/src/components/HeadCommon.astro
index 4906aaf7f..21504cf89 100644
--- a/examples/docs/src/components/HeadCommon.astro
+++ b/examples/docs/src/components/HeadCommon.astro
@@ -1,6 +1,5 @@
---
import '../styles/theme.css';
-import '../styles/code.css';
import '../styles/index.css';
---
diff --git a/examples/docs/src/styles/code.css b/examples/docs/src/styles/code.css
deleted file mode 100644
index b4275adab..000000000
--- a/examples/docs/src/styles/code.css
+++ /dev/null
@@ -1,96 +0,0 @@
-.language-css > code,
-.language-sass > code,
-.language-scss > code {
- color: #fd9170;
-}
-
-[class*='language-'] .namespace {
- opacity: 0.7;
-}
-
-.token.plain-text,
-[class*='language-bash'] span.token,
-[class*='language-shell'] span.token {
- color: hsla(var(--color-gray-90), 1);
-}
-
-[class*='language-bash'] span.token,
-[class*='language-shell'] span.token {
- font-style: bold;
-}
-
-.token.prolog,
-.token.comment,
-[class*='language-bash'] span.token.comment,
-[class*='language-shell'] span.token.comment {
- color: hsla(var(--color-gray-70), 1);
-}
-
-.token.selector,
-.token.tag,
-.token.unit,
-.token.url,
-.token.variable,
-.token.entity,
-.token.deleted {
- color: #fa5e5b;
-}
-
-.token.boolean,
-.token.constant,
-.token.doctype,
-.token.number,
-.token.regex,
-.token.builtin,
-.token.class,
-.token.hexcode,
-.token.class-name,
-.token.attr-name {
- color: hsla(var(--color-yellow), 1);
-}
-
-.token.atrule,
-.token.attribute,
-.token.attr-value .token.punctuation,
-.token.attr-value,
-.token.pseudo-class,
-.token.pseudo-element,
-.token.string {
- color: hsla(var(--color-green), 1);
-}
-
-.token.symbol,
-.token.function,
-.token.id,
-.token.important {
- color: hsla(var(--color-blue), 1);
-}
-
-.token.important,
-.token.id {
- font-weight: bold;
-}
-
-.token.cdata,
-.token.char,
-.token.property {
- color: #23b1af;
-}
-
-.token.inserted {
- color: hsla(var(--color-green), 1);
-}
-
-.token.keyword {
- color: #ff657c;
- font-style: italic;
-}
-
-.token.operator {
- color: hsla(var(--color-gray-70), 1);
-}
-
-.token.attr-value .token.attr-equals,
-.token.punctuation {
- color: hsla(var(--color-gray-80), 1);
-}
diff --git a/examples/docs/src/styles/index.css b/examples/docs/src/styles/index.css
index ad0a5adf7..971ccf9e5 100644
--- a/examples/docs/src/styles/index.css
+++ b/examples/docs/src/styles/index.css
@@ -151,14 +151,14 @@ article > section iframe {
aspect-ratio: 16 / 9;
}
-a > code:not([class*='language']) {
+a > code {
position: relative;
color: var(--theme-text-accent);
background: transparent;
text-underline-offset: var(--padding-block);
}
-a > code:not([class*='language'])::before {
+a > code::before {
content: '';
position: absolute;
top: 0;
@@ -187,30 +187,24 @@ strong {
}
/* Supporting Content */
-code {
- font-family: var(--font-mono);
- font-size: 0.85em;
-}
-code:not([class*='language']) {
+code {
--border-radius: 3px;
--padding-block: 0.2rem;
- --padding-inline: 0.4rem;
- color: var(--theme-code-inline-text);
+ --padding-inline: 0.33rem;
+
+ font-family: var(--font-mono);
+ font-size: 0.85em;
+ color: inherit;
background-color: var(--theme-code-inline-bg);
padding: var(--padding-block) var(--padding-inline);
margin: calc(var(--padding-block) * -1) -0.125em;
border-radius: var(--border-radius);
- box-shadow: 0 2px 1px 0 rgba(0, 0, 0, 0.08);
word-break: break-word;
}
-pre > code:not([class*='language']) {
- background-color: transparent;
- padding: 0;
- margin: 0;
- border-radius: 0;
- color: inherit;
+pre.astro-code > code {
+ all: unset;
}
pre > code {
@@ -261,7 +255,7 @@ pre {
color: var(--theme-code-text);
}
-blockquote code:not([class*='language']) {
+blockquote code {
background-color: var(--theme-bg);
}
diff --git a/examples/with-markdown/src/styles/global.css b/examples/with-markdown/src/styles/global.css
index 577e06182..ac9323747 100644
--- a/examples/with-markdown/src/styles/global.css
+++ b/examples/with-markdown/src/styles/global.css
@@ -40,7 +40,7 @@ pre {
border-radius: 4px;
}
-:not(pre) > code {
+code {
padding: 0.1em 0.3em;
color: #db4c69;
background: #f9f2f4;
@@ -48,151 +48,10 @@ pre {
white-space: pre-wrap;
}
-/*********************************************************
-* Tokens
-*/
-.namespace {
- opacity: 0.7;
-}
-
-.token.comment,
-.token.prolog,
-.token.doctype,
-.token.cdata {
- color: #6a9955;
-}
-
-.token.punctuation {
- color: #d4d4d4;
-}
-
-.token.property,
-.token.tag,
-.token.boolean,
-.token.number,
-.token.constant,
-.token.symbol,
-.token.deleted {
- color: #b5cea8;
-}
-
-.token.selector,
-.token.attr-name,
-.token.string,
-.token.char,
-.token.builtin,
-.token.inserted {
- color: #ce9178;
+pre.astro-code > code {
+ all: unset;
}
-.token.operator,
-.token.entity,
-.token.url,
-.language-css .token.string,
-.style .token.string {
- color: #d4d4d4;
- background: #2d3748;
-}
-
-.token.atrule,
-.token.attr-value,
-.token.keyword {
- color: #c586c0;
-}
-
-.token.function {
- color: #dcdcaa;
-}
-
-.token.regex,
-.token.important,
-.token.variable {
- color: #d16969;
-}
-
-.token.important,
-.token.bold {
- font-weight: bold;
-}
-
-.token.italic {
- font-style: italic;
-}
-
-.token.constant {
- color: #9cdcfe;
-}
-
-.token.class-name {
- color: #4ec9b0;
-}
-
-.token.parameter {
- color: #9cdcfe;
-}
-
-.token.interpolation {
- color: #9cdcfe;
-}
-
-.token.punctuation.interpolation-punctuation {
- color: #569cd6;
-}
-
-.token.boolean {
- color: #569cd6;
-}
-
-.token.property {
- color: #9cdcfe;
-}
-
-.token.selector {
- color: #d7ba7d;
-}
-
-.token.tag {
- color: #569cd6;
-}
-
-.token.attr-name {
- color: #9cdcfe;
-}
-
-.token.attr-value {
- color: #ce9178;
-}
-
-.token.entity {
- color: #4ec9b0;
- cursor: unset;
-}
-
-.token.namespace {
- color: #4ec9b0;
-}
-
-/*********************************************************
-* Language Specific
-*/
-pre[class*='language-javascript'],
-code[class*='language-javascript'] {
- color: #4ec9b0;
-}
-
-pre[class*='language-css'],
-code[class*='language-css'] {
- color: #ce9178;
-}
-
-pre[class*='language-html'],
-code[class*='language-html'] {
- color: #d4d4d4;
-}
-
-.language-html .token.punctuation {
- color: #808080;
-}
/*********************************************************
* Line highlighting
@@ -206,28 +65,6 @@ pre > code {
z-index: 1;
}
-.line-highlight {
- position: absolute;
- right: 0;
- left: 0;
- z-index: 0;
- margin-top: 1em;
- padding: inherit 0;
- line-height: inherit;
- white-space: pre;
- background: #f7ebc6;
- box-shadow: inset 5px 0 0 #f7d87c;
- pointer-events: none;
-}
-
-pre[class*='language-bash'] .token.function {
- color: #d4d4d4;
-}
-
-.token.comment {
- color: #fff7;
-}
-
body {
max-width: 900px;
margin: auto;
diff --git a/packages/astro/test/astro-markdown.test.js b/packages/astro/test/astro-markdown.test.js
index 2bccf8d87..44315a510 100644
--- a/packages/astro/test/astro-markdown.test.js
+++ b/packages/astro/test/astro-markdown.test.js
@@ -57,18 +57,36 @@ describe('Astro Markdown', () => {
const html = await fixture.readFile('/scopedStyles-code/index.html');
const $ = cheerio.load(html);
- // test 1: <pre> tag has scopedStyle class passed down
- expect($('pre').is('[class]')).to.equal(true);
- expect($('pre').attr('class').split(' ').length).to.equal(2);
+ // test 1: <pre> tag has correct shiki class
+ expect($('pre').hasClass('astro-code')).to.equal(true);
+
+ // test 2: inline styles are still applied
+ expect($('pre').is('[style]')).to.equal(true);
+
+ // test 3: There are styled child spans in code blocks
+ expect($('pre code span').length).to.be.greaterThan(0);
+ expect($('pre code span').is('[style]')).to.equal(true);
+ });
- // test 2: <pre> tag has correct language
- expect($('pre').hasClass('language-js')).to.equal(true);
+ function isAstroScopedClass(cls) {
+ return /^astro-.*/.test(cls)
+ }
- // test 3: <code> tag has correct language
- expect($('code').hasClass('language-js')).to.equal(true);
+ it('Scoped styles should be applied to syntax highlighted lines', async () => {
+ const html = await fixture.readFile('/scopedStyles-code/index.html');
+ const $ = cheerio.load(html);
- // test 4: There are child spans in code blocks
- expect($('code span').length).to.be.greaterThan(0);
+ // test 1: the "pre" tag receives scoped style
+ const preClassList = $('pre').attr('class').split(/\s+/);
+ expect(preClassList.length).to.equal(2);
+ const preAstroClass = preClassList.find(isAstroScopedClass);
+ expect(Boolean(preAstroClass)).to.equal(true);
+
+ // test 2: each "span" line receives scoped style
+ const spanClassList = $('pre code span').attr('class').split(/\s+/);
+ expect(spanClassList.length).to.equal(2);
+ const spanAstroClass = spanClassList.find(isAstroScopedClass);
+ expect(Boolean(spanAstroClass)).to.equal(true);
});
it('Renders correctly when deeply nested on a page', async () => {
diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts
index e8a315ef1..3ea436795 100644
--- a/packages/markdown/remark/src/index.ts
+++ b/packages/markdown/remark/src/index.ts
@@ -38,7 +38,7 @@ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOp
let { remarkPlugins = [], rehypePlugins = [] } = opts ?? {};
const scopedClassName = opts?.$?.scopedClassName;
const mode = opts?.mode ?? 'mdx';
- const syntaxHighlight = opts?.syntaxHighlight ?? 'prism';
+ const syntaxHighlight = opts?.syntaxHighlight ?? 'shiki';
const shikiConfig = opts?.shikiConfig ?? {};
const isMDX = mode === 'mdx';
const { headers, rehypeCollectHeaders } = createCollectHeaders();
@@ -67,10 +67,10 @@ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOp
parser.use([scopedStyles(scopedClassName)]);
}
- if (syntaxHighlight === 'prism') {
+ if (syntaxHighlight === 'shiki') {
+ parser.use([await remarkShiki(shikiConfig, scopedClassName)]);
+ } else if (syntaxHighlight === 'prism') {
parser.use([remarkPrism(scopedClassName)]);
- } else if (syntaxHighlight === 'shiki') {
- parser.use([await remarkShiki(shikiConfig)]);
}
parser.use([[markdownToHtml as any, { allowDangerousHtml: true, passThrough: ['raw', 'mdxTextExpression', 'mdxJsxTextElement', 'mdxJsxFlowElement'] }]]);
diff --git a/packages/markdown/remark/src/remark-shiki.ts b/packages/markdown/remark/src/remark-shiki.ts
index 5bee7ef6e..ebbe4032c 100644
--- a/packages/markdown/remark/src/remark-shiki.ts
+++ b/packages/markdown/remark/src/remark-shiki.ts
@@ -36,7 +36,7 @@ export interface ShikiConfig {
*/
const highlighterCache = new Map<string, shiki.Highlighter>();
-const remarkShiki = async ({ langs = [], theme = 'github-dark', wrap = false }: ShikiConfig) => {
+const remarkShiki = async ({ langs = [], theme = 'github-dark', wrap = false }: ShikiConfig, scopedClassName?: string | null) => {
const cacheID: string = typeof theme === 'string' ? theme : theme.name;
let highlighter = highlighterCache.get(cacheID);
if (!highlighter) {
@@ -50,8 +50,14 @@ const remarkShiki = async ({ langs = [], theme = 'github-dark', wrap = false }:
visit(tree, 'code', (node) => {
let html = highlighter!.codeToHtml(node.value, { lang: node.lang ?? 'plaintext' });
+ // Q: Couldn't these regexes match on a user's inputted code blocks?
+ // A: Nope! All rendered HTML is properly escaped.
+ // Ex. If a user typed `<span class="line"` into a code block,
+ // It would become this before hitting our regexes:
+ // &lt;span class=&quot;line&quot;
+
// Replace "shiki" class naming with "astro" and add "is:raw".
- html = html.replace('<pre class="shiki"', '<pre is:raw class="astro-code"');
+ html = html.replace('<pre class="shiki"', `<pre is:raw class="astro-code${scopedClassName ? ' ' + scopedClassName : ''}"`);
// Replace "shiki" css variable naming with "astro".
html = html.replace(/style="(background-)?color: var\(--shiki-/g, 'style="$1color: var(--astro-code-');
// Handle code wrapping
@@ -62,6 +68,11 @@ const remarkShiki = async ({ langs = [], theme = 'github-dark', wrap = false }:
html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"');
}
+ // Apply scopedClassName to all nested lines
+ if (scopedClassName) {
+ html = html.replace(/\<span class="line"\>/g, `<span class="line ${scopedClassName}"`);
+ }
+
node.type = 'html';
node.value = html;
node.children = [];
diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts
index 040c33b79..eb0eb5d3e 100644
--- a/packages/markdown/remark/src/types.ts
+++ b/packages/markdown/remark/src/types.ts
@@ -5,7 +5,7 @@ export type Plugin = string | [string, any] | unified.Plugin | [unified.Plugin,
export interface AstroMarkdownOptions {
mode?: 'md' | 'mdx';
- syntaxHighlight?: 'prism' | 'shiki' | false;
+ syntaxHighlight?: 'shiki' | 'prism' | false;
shikiConfig?: ShikiConfig;
remarkPlugins?: Plugin[];
rehypePlugins?: Plugin[];