mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Light/dark theming for shikiji's codeblocks (#8903)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
parent
1ecc9aa324
commit
c5010aad34
8 changed files with 62 additions and 8 deletions
6
.changeset/tender-suits-glow.md
Normal file
6
.changeset/tender-suits-glow.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@astrojs/markdown-remark': minor
|
||||
'astro': minor
|
||||
---
|
||||
|
||||
Adds experimental support for multiple shiki themes with the new `markdown.shikiConfig.experimentalThemes` option.
|
|
@ -32,6 +32,11 @@ interface Props {
|
|||
* @default "github-dark"
|
||||
*/
|
||||
theme?: BuiltinTheme | ThemeRegistration | ThemeRegistrationRaw;
|
||||
/**
|
||||
* Multiple themes to style with -- alternative to "theme" option.
|
||||
* Supports all themes found above; see https://github.com/antfu/shikiji#lightdark-dual-themes for more information.
|
||||
*/
|
||||
experimentalThemes?: Record<string, BuiltinTheme | ThemeRegistration | ThemeRegistrationRaw>;
|
||||
/**
|
||||
* Enable word wrapping.
|
||||
* - true: enabled.
|
||||
|
@ -53,6 +58,7 @@ const {
|
|||
code,
|
||||
lang = 'plaintext',
|
||||
theme = 'github-dark',
|
||||
experimentalThemes = {},
|
||||
wrap = false,
|
||||
inline = false,
|
||||
} = Astro.props;
|
||||
|
@ -88,12 +94,15 @@ if (typeof lang === 'object') {
|
|||
|
||||
const highlighter = await getCachedHighlighter({
|
||||
langs: [lang],
|
||||
themes: [theme],
|
||||
themes: Object.values(experimentalThemes).length ? Object.values(experimentalThemes) : [theme],
|
||||
});
|
||||
|
||||
const themeOptions = Object.values(experimentalThemes).length
|
||||
? { themes: experimentalThemes }
|
||||
: { theme };
|
||||
const html = highlighter.codeToHtml(code, {
|
||||
lang: typeof lang === 'string' ? lang : lang.name,
|
||||
theme,
|
||||
...themeOptions,
|
||||
transforms: {
|
||||
pre(node) {
|
||||
// Swap to `code` tag if inline
|
||||
|
@ -123,6 +132,10 @@ const html = highlighter.codeToHtml(code, {
|
|||
}
|
||||
},
|
||||
root(node) {
|
||||
if (Object.values(experimentalThemes).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// theme.id for shiki -> shikiji compat
|
||||
const themeName = typeof theme === 'string' ? theme : theme.name;
|
||||
if (themeName === 'css-variables') {
|
||||
|
|
|
@ -292,7 +292,14 @@ export const AstroConfigSchema = z.object({
|
|||
theme: z
|
||||
.enum(Object.keys(bundledThemes) as [BuiltinTheme, ...BuiltinTheme[]])
|
||||
.or(z.custom<ShikiTheme>())
|
||||
.default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.theme as BuiltinTheme),
|
||||
.default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.theme!),
|
||||
experimentalThemes: z
|
||||
.record(
|
||||
z
|
||||
.enum(Object.keys(bundledThemes) as [BuiltinTheme, ...BuiltinTheme[]])
|
||||
.or(z.custom<ShikiTheme>())
|
||||
)
|
||||
.default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.experimentalThemes!),
|
||||
wrap: z.boolean().or(z.null()).default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.wrap!),
|
||||
})
|
||||
.default({}),
|
||||
|
|
|
@ -81,6 +81,7 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug
|
|||
|
||||
const renderResult = await processor
|
||||
.render(raw.content, {
|
||||
// @ts-expect-error passing internal prop
|
||||
fileURL,
|
||||
frontmatter: raw.data,
|
||||
})
|
||||
|
|
|
@ -39,6 +39,7 @@ export const markdownConfigDefaults: Omit<Required<AstroMarkdownOptions>, 'draft
|
|||
shikiConfig: {
|
||||
langs: [],
|
||||
theme: 'github-dark',
|
||||
experimentalThemes: {},
|
||||
wrap: false,
|
||||
},
|
||||
remarkPlugins: [],
|
||||
|
|
|
@ -30,9 +30,15 @@ const highlighterCacheAsync = new Map<string, Promise<Highlighter>>();
|
|||
export function remarkShiki({
|
||||
langs = [],
|
||||
theme = 'github-dark',
|
||||
experimentalThemes = {},
|
||||
wrap = false,
|
||||
}: ShikiConfig = {}): ReturnType<RemarkPlugin> {
|
||||
const themes = experimentalThemes;
|
||||
|
||||
const cacheId =
|
||||
Object.values(themes)
|
||||
.map((t) => (typeof t === 'string' ? t : t.name ?? ''))
|
||||
.join(',') +
|
||||
(typeof theme === 'string' ? theme : theme.name ?? '') +
|
||||
langs.map((l) => l.name ?? (l as any).id).join(',');
|
||||
|
||||
|
@ -40,7 +46,7 @@ export function remarkShiki({
|
|||
if (!highlighterAsync) {
|
||||
highlighterAsync = getHighlighter({
|
||||
langs: langs.length ? langs : Object.keys(bundledLanguages),
|
||||
themes: [theme],
|
||||
themes: Object.values(themes).length ? Object.values(themes) : [theme],
|
||||
});
|
||||
highlighterCacheAsync.set(cacheId, highlighterAsync);
|
||||
}
|
||||
|
@ -64,7 +70,8 @@ export function remarkShiki({
|
|||
lang = 'plaintext';
|
||||
}
|
||||
|
||||
let html = highlighter.codeToHtml(node.value, { lang, theme });
|
||||
let themeOptions = Object.values(themes).length ? { themes } : { theme };
|
||||
let html = highlighter.codeToHtml(node.value, { ...themeOptions, lang });
|
||||
|
||||
// Q: Couldn't these regexes match on a user's inputted code blocks?
|
||||
// A: Nope! All rendered HTML is properly escaped.
|
||||
|
|
|
@ -42,6 +42,7 @@ export type RemarkRehype = Omit<RemarkRehypeOptions, 'handlers' | 'unknownHandle
|
|||
export interface ShikiConfig {
|
||||
langs?: LanguageRegistration[];
|
||||
theme?: BuiltinTheme | ThemeRegistration | ThemeRegistrationRaw;
|
||||
experimentalThemes?: Record<string, BuiltinTheme | ThemeRegistration | ThemeRegistrationRaw>;
|
||||
wrap?: boolean | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,30 @@
|
|||
import { createMarkdownProcessor } from '../dist/index.js';
|
||||
import chai from 'chai';
|
||||
|
||||
describe('shiki syntax highlighting', async () => {
|
||||
const processor = await createMarkdownProcessor();
|
||||
|
||||
describe('shiki syntax highlighting', () => {
|
||||
it('does not add is:raw to the output', async () => {
|
||||
const processor = await createMarkdownProcessor();
|
||||
const { code } = await processor.render('```\ntest\n```');
|
||||
|
||||
chai.expect(code).not.to.contain('is:raw');
|
||||
});
|
||||
|
||||
it('supports light/dark themes', async () => {
|
||||
const processor = await createMarkdownProcessor({
|
||||
shikiConfig: {
|
||||
experimentalThemes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
},
|
||||
});
|
||||
const { code } = await processor.render('```\ntest\n```');
|
||||
|
||||
// light theme is there:
|
||||
chai.expect(code).to.contain('background-color:');
|
||||
chai.expect(code).to.contain('github-light');
|
||||
// dark theme is there:
|
||||
chai.expect(code).to.contain('--shiki-dark-bg:');
|
||||
chai.expect(code).to.contain('github-dark');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue