Fix css variables theme

This commit is contained in:
bluwy 2023-10-06 21:29:07 +08:00
parent 6e497d87b1
commit ef5a72ecae
5 changed files with 100 additions and 13 deletions

View file

@ -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);
}
});
}
},
},
});
---

View file

@ -2,9 +2,34 @@ import { type Highlighter, getHighlighter } from 'shikiji';
type HighlighterOptions = NonNullable<Parameters<typeof getHighlighter>[0]>;
const ASTRO_COLOR_REPLACEMENTS: Record<string, string> = {
'#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<Highlighter> 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<Highlighter> {
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
const key = JSON.stringify(opts, Object.keys(opts).sort());

View file

@ -15,7 +15,7 @@ describe('<Code>', () => {
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('<Code>', () => {
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('<Code>', () => {
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)',
]);
});

View file

@ -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<string, string> = {
'#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 = /<pre class="(.*?)shiki(.*?)"/;
const LINE_SELECTOR = /<span class="line"><span style="(.*?)">([\+|\-])/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);
}

View file

@ -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<string, string> = {
'#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);
}