summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar horo <143025439+horo-fox@users.noreply.github.com> 2023-11-08 23:42:05 +0900
committerGravatar GitHub <noreply@github.com> 2023-11-08 22:42:05 +0800
commitc5010aad3475669648dc939e00f88bbb52489d0d (patch)
tree07d31ee297ac7c69c06519b9cea00d26fd7cfb4a
parent1ecc9aa3240b79a3879b1329aa4f671d80e87649 (diff)
downloadastro-c5010aad3475669648dc939e00f88bbb52489d0d.tar.gz
astro-c5010aad3475669648dc939e00f88bbb52489d0d.tar.zst
astro-c5010aad3475669648dc939e00f88bbb52489d0d.zip
Light/dark theming for shikiji's codeblocks (#8903)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
-rw-r--r--.changeset/tender-suits-glow.md6
-rw-r--r--packages/astro/components/Code.astro17
-rw-r--r--packages/astro/src/core/config/schema.ts9
-rw-r--r--packages/astro/src/vite-plugin-markdown/index.ts1
-rw-r--r--packages/markdown/remark/src/index.ts1
-rw-r--r--packages/markdown/remark/src/remark-shiki.ts11
-rw-r--r--packages/markdown/remark/src/types.ts1
-rw-r--r--packages/markdown/remark/test/shiki.js24
8 files changed, 62 insertions, 8 deletions
diff --git a/.changeset/tender-suits-glow.md b/.changeset/tender-suits-glow.md
new file mode 100644
index 000000000..a01662ce6
--- /dev/null
+++ b/.changeset/tender-suits-glow.md
@@ -0,0 +1,6 @@
+---
+'@astrojs/markdown-remark': minor
+'astro': minor
+---
+
+Adds experimental support for multiple shiki themes with the new `markdown.shikiConfig.experimentalThemes` option.
diff --git a/packages/astro/components/Code.astro b/packages/astro/components/Code.astro
index 0a4fff6b9..b1d21fd9e 100644
--- a/packages/astro/components/Code.astro
+++ b/packages/astro/components/Code.astro
@@ -33,6 +33,11 @@ interface Props {
*/
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.
* - false: disabled.
@@ -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') {
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 5a5964a12..ea656b9bf 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -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({}),
diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts
index 3c8a1af46..fdfe3db37 100644
--- a/packages/astro/src/vite-plugin-markdown/index.ts
+++ b/packages/astro/src/vite-plugin-markdown/index.ts
@@ -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,
})
diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts
index 89c9ca8bd..61f97072b 100644
--- a/packages/markdown/remark/src/index.ts
+++ b/packages/markdown/remark/src/index.ts
@@ -39,6 +39,7 @@ export const markdownConfigDefaults: Omit<Required<AstroMarkdownOptions>, 'draft
shikiConfig: {
langs: [],
theme: 'github-dark',
+ experimentalThemes: {},
wrap: false,
},
remarkPlugins: [],
diff --git a/packages/markdown/remark/src/remark-shiki.ts b/packages/markdown/remark/src/remark-shiki.ts
index bf3dd0b78..4eaae5ff2 100644
--- a/packages/markdown/remark/src/remark-shiki.ts
+++ b/packages/markdown/remark/src/remark-shiki.ts
@@ -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.
diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts
index 4abcf578d..7038e2425 100644
--- a/packages/markdown/remark/src/types.ts
+++ b/packages/markdown/remark/src/types.ts
@@ -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;
}
diff --git a/packages/markdown/remark/test/shiki.js b/packages/markdown/remark/test/shiki.js
index cc5c6b771..c7ace6187 100644
--- a/packages/markdown/remark/test/shiki.js
+++ b/packages/markdown/remark/test/shiki.js
@@ -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');
+ });
});