From ef5a72ecae0f9b8cde41513c6a557d10179504b0 Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 6 Oct 2023 21:29:07 +0800 Subject: [PATCH] Fix css variables theme --- packages/astro/components/Code.astro | 15 ++++++++- packages/astro/components/shiki.ts | 25 +++++++++++++++ .../astro/test/astro-component-code.test.js | 21 +++++++------ .../markdoc/src/extensions/shiki.ts | 21 +++++++++++-- packages/markdown/remark/src/remark-shiki.ts | 31 +++++++++++++++++++ 5 files changed, 100 insertions(+), 13 deletions(-) diff --git a/packages/astro/components/Code.astro b/packages/astro/components/Code.astro index 81c23d3be..b3dbb07aa 100644 --- a/packages/astro/components/Code.astro +++ b/packages/astro/components/Code.astro @@ -7,7 +7,8 @@ import type { ThemeRegistration, ThemeRegistrationRaw, } from 'shikiji'; -import { getCachedHighlighter } from './shiki.js'; +import { visit } from 'unist-util-visit'; +import { getCachedHighlighter, replaceCssVariables } from './shiki.js'; interface Props { /** The code to highlight. Required. */ @@ -100,6 +101,18 @@ const html = highlighter.codeToHtml(code, { return node.children[0] as typeof node; } }, + root(node) { + // theme.id for shiki -> shikiji compat + const themeName = typeof theme === 'string' ? theme : (theme as any).id || theme.name; + if (themeName === 'css-variables') { + // Replace special color tokens to CSS variables + visit(node as any, 'element', (child) => { + if (child.properties?.style) { + child.properties.style = replaceCssVariables(child.properties.style); + } + }); + } + }, }, }); --- diff --git a/packages/astro/components/shiki.ts b/packages/astro/components/shiki.ts index ed320c481..4ce27c51d 100644 --- a/packages/astro/components/shiki.ts +++ b/packages/astro/components/shiki.ts @@ -2,9 +2,34 @@ import { type Highlighter, getHighlighter } from 'shikiji'; type HighlighterOptions = NonNullable[0]>; +const ASTRO_COLOR_REPLACEMENTS: Record = { + '#000001': 'var(--astro-code-color-text)', + '#000002': 'var(--astro-code-color-background)', + '#000004': 'var(--astro-code-token-constant)', + '#000005': 'var(--astro-code-token-string)', + '#000006': 'var(--astro-code-token-comment)', + '#000007': 'var(--astro-code-token-keyword)', + '#000008': 'var(--astro-code-token-parameter)', + '#000009': 'var(--astro-code-token-function)', + '#000010': 'var(--astro-code-token-string-expression)', + '#000011': 'var(--astro-code-token-punctuation)', + '#000012': 'var(--astro-code-token-link)', +}; +const COLOR_REPLACEMENT_REGEX = new RegExp( + `(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`, + 'g' +); + // Caches Promise for reuse when the same theme and langs are provided const cachedHighlighters = new Map(); +/** + * shiki -> shikiji compat as we need to manually replace it + */ +export function replaceCssVariables(str: string) { + return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match); +} + export function getCachedHighlighter(opts: HighlighterOptions): Promise { // Always sort keys before stringifying to make sure objects match regardless of parameter ordering const key = JSON.stringify(opts, Object.keys(opts).sort()); diff --git a/packages/astro/test/astro-component-code.test.js b/packages/astro/test/astro-component-code.test.js index d20669047..dbca3f56d 100644 --- a/packages/astro/test/astro-component-code.test.js +++ b/packages/astro/test/astro-component-code.test.js @@ -15,7 +15,7 @@ describe('', () => { const $ = cheerio.load(html); expect($('pre')).to.have.lengthOf(1); expect($('pre').attr('style')).to.equal( - '-background-color:#24292e;color:#e1e4e8; overflow-x: auto;', + 'background-color:#24292e;color:#e1e4e8; overflow-x: auto;', 'applies default and overflow' ); expect($('pre > code')).to.have.lengthOf(1); @@ -60,14 +60,16 @@ describe('', () => { const $ = cheerio.load(html); expect($('pre')).to.have.lengthOf(1); // test: applies wrap overflow - expect($('pre').attr('style')).to.equal('background-color: #24292e; overflow-x: auto;'); + expect($('pre').attr('style')).to.equal( + 'background-color:#24292e;color:#e1e4e8; overflow-x: auto;' + ); } { let html = await fixture.readFile('/wrap-null/index.html'); const $ = cheerio.load(html); expect($('pre')).to.have.lengthOf(1); // test: applies wrap overflow - expect($('pre').attr('style')).to.equal('background-color: #24292e'); + expect($('pre').attr('style')).to.equal('background-color:#24292e;color:#e1e4e8'); } }); @@ -76,18 +78,17 @@ describe('', () => { const $ = cheerio.load(html); expect($('pre')).to.have.lengthOf(1); expect($('pre').attr('class')).to.equal('astro-code css-variables'); - // TODO: We can't specify CSS vars with shikiji expect( $('pre, pre span') .map((i, f) => (f.attribs ? f.attribs.style : 'no style found')) .toArray() ).to.deep.equal([ - 'background-color: var(--astro-code-color-background); overflow-x: auto;', - 'color: var(--astro-code-token-constant)', - 'color: var(--astro-code-token-function)', - 'color: var(--astro-code-color-text)', - 'color: var(--astro-code-token-string-expression)', - 'color: var(--astro-code-color-text)', + 'background-color:var(--astro-code-color-background);color:var(--astro-code-color-text); overflow-x: auto;', + 'color:var(--astro-code-token-constant)', + 'color:var(--astro-code-token-function)', + 'color:var(--astro-code-color-text)', + 'color:var(--astro-code-token-string-expression)', + 'color:var(--astro-code-color-text)', ]); }); diff --git a/packages/integrations/markdoc/src/extensions/shiki.ts b/packages/integrations/markdoc/src/extensions/shiki.ts index 0326118fb..dcdd05e7b 100644 --- a/packages/integrations/markdoc/src/extensions/shiki.ts +++ b/packages/integrations/markdoc/src/extensions/shiki.ts @@ -4,7 +4,7 @@ import { unescapeHTML } from 'astro/runtime/server/index.js'; import { bundledLanguages, getHighlighter, type Highlighter } from 'shikiji'; import type { AstroMarkdocConfig } from '../config.js'; -const ASTRO_COLOR_REPLACEMENTS = { +const ASTRO_COLOR_REPLACEMENTS: Record = { '#000001': 'var(--astro-code-color-text)', '#000002': 'var(--astro-code-color-background)', '#000004': 'var(--astro-code-token-constant)', @@ -16,7 +16,11 @@ const ASTRO_COLOR_REPLACEMENTS = { '#000010': 'var(--astro-code-token-string-expression)', '#000011': 'var(--astro-code-token-punctuation)', '#000012': 'var(--astro-code-token-link)', -} as const; +}; +const COLOR_REPLACEMENT_REGEX = new RegExp( + `(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`, + 'g' +); const PRE_SELECTOR = /
([\+|\-])/g;
@@ -92,6 +96,12 @@ export default async function shiki({
 						);
 					}
 
+					// theme.id for shiki -> shikiji compat
+					const themeName = typeof theme === 'string' ? theme : (theme as any).id || theme.name;
+					if (themeName === 'css-variables') {
+						html = html.replace(/style="(.*?)"/g, (m) => replaceCssVariables(m));
+					}
+
 					// Use `unescapeHTML` to return `HTMLString` for Astro renderer to inline as HTML
 					return unescapeHTML(html) as any;
 				},
@@ -99,3 +109,10 @@ export default async function shiki({
 		},
 	};
 }
+
+/**
+ * shiki -> shikiji compat as we need to manually replace it
+ */
+export function replaceCssVariables(str: string) {
+	return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
+}
diff --git a/packages/markdown/remark/src/remark-shiki.ts b/packages/markdown/remark/src/remark-shiki.ts
index d6c4093ef..3ffea0cc6 100644
--- a/packages/markdown/remark/src/remark-shiki.ts
+++ b/packages/markdown/remark/src/remark-shiki.ts
@@ -2,6 +2,24 @@ import { bundledLanguages, getHighlighter, type Highlighter } from 'shikiji';
 import { visit } from 'unist-util-visit';
 import type { RemarkPlugin, ShikiConfig } from './types.js';
 
+const ASTRO_COLOR_REPLACEMENTS: Record = {
+	'#000001': 'var(--astro-code-color-text)',
+	'#000002': 'var(--astro-code-color-background)',
+	'#000004': 'var(--astro-code-token-constant)',
+	'#000005': 'var(--astro-code-token-string)',
+	'#000006': 'var(--astro-code-token-comment)',
+	'#000007': 'var(--astro-code-token-keyword)',
+	'#000008': 'var(--astro-code-token-parameter)',
+	'#000009': 'var(--astro-code-token-function)',
+	'#000010': 'var(--astro-code-token-string-expression)',
+	'#000011': 'var(--astro-code-token-punctuation)',
+	'#000012': 'var(--astro-code-token-link)',
+};
+const COLOR_REPLACEMENT_REGEX = new RegExp(
+	`(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`,
+	'g'
+);
+
 /**
  * getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page,
  * cache it here as much as possible. Make sure that your highlighters can be cached, state-free.
@@ -74,9 +92,22 @@ export function remarkShiki({
 				);
 			}
 
+			// theme.id for shiki -> shikiji compat
+			const themeName = typeof theme === 'string' ? theme : (theme as any).id || theme.name;
+			if (themeName === 'css-variables') {
+				html = html.replace(/style="(.*?)"/g, (m) => replaceCssVariables(m));
+			}
+
 			node.type = 'html';
 			node.value = html;
 			node.children = [];
 		});
 	};
 }
+
+/**
+ * shiki -> shikiji compat as we need to manually replace it
+ */
+export function replaceCssVariables(str: string) {
+	return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
+}