diff --git a/docs/themes.md b/docs/themes.md index 6472f34ab..ec4c5163b 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -26,11 +26,64 @@ shiki.getHighlighter({ theme: t }) ``` +## Dark Mode Support +Because Shiki generates themes at build time, client-side theme switching support is not built in. There are two popular two options for supporting something like Dark Mode with Shiki. + +#### 1. Use the "css-variables" theme. + +This gives you access to CSS variable styling, which you can control across Dark and Light mode. See the [Theming with CSS Variables](#theming-with-css-variables) section below for more details. +#### 2. Generate two Shiki code blocks, one for each theme. + +```css +@media (prefers-color-scheme: light) { + .shiki.dark-plus { + display: none; + } +} +@media (prefers-color-scheme: dark) { + .shiki.light-plus { + display: none; + } +} +``` + +## Theming with CSS Variables + +Shiki handles all theme logic at build-time, so that the browser only ever sees already-computed `style="color: #XXXXXX"` attributes. This allows more granular theme support in a way that doesn't require any additional steps to add global CSS to your page. + +In some cases, a user may require custom client-side theming via CSS. To support this, you may use the `css-variables` theme with Shiki. This is a special theme that uses CSS variables for colors instead of hardcoded values. Each token in your code block is given an attribute of `style="color: var(--shiki-XXX)"` which you can use to style your code blocks using CSS. + + +```js +const shiki = require('shiki') +shiki.getHighlighter({theme: 'css-variables'}) +``` + +Note that this client-side theme is less granular than most other supported VSCode themes. Also, be aware that this will generate unstyled code if you do not define these CSS variables somewhere else on your page: + +```html + +``` ## All Themes ```ts export type Theme = + | 'css-variables' | 'dark-plus' | 'dracula-soft' | 'dracula' diff --git a/packages/shiki/src/__tests__/__snapshots__/cssVars.test.ts.snap b/packages/shiki/src/__tests__/__snapshots__/cssVars.test.ts.snap new file mode 100644 index 000000000..f9f9568a2 --- /dev/null +++ b/packages/shiki/src/__tests__/__snapshots__/cssVars.test.ts.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`The theme with css-variables renders correctly 1`] = ` +"

+import { getHighlighter } from '../index'
+
+test('The theme with css-variables renders correctly', async () => {
+  const highlighter = await getHighlighter({
+    theme: 'css-variables'
+  })
+  const out = highlighter.codeToHtml("console.log('shiki');", 'js')
+  expect(out).toMatchSnapshot()
+})
+
" +`; diff --git a/packages/shiki/src/__tests__/cssVars.test.ts b/packages/shiki/src/__tests__/cssVars.test.ts new file mode 100644 index 000000000..d97d8162f --- /dev/null +++ b/packages/shiki/src/__tests__/cssVars.test.ts @@ -0,0 +1,22 @@ +import { getHighlighter } from '../index' + +test('The theme with css-variables renders correctly', async () => { + const highlighter = await getHighlighter({ + theme: 'css-variables' + }) + + const kindOfAQuine = ` +import { getHighlighter } from '../index' + +test('The theme with css-variables renders correctly', async () => { + const highlighter = await getHighlighter({ + theme: 'css-variables' + }) + const out = highlighter.codeToHtml("console.log('shiki');", 'js') + expect(out).toMatchSnapshot() +}) +` + + const out = highlighter.codeToHtml(kindOfAQuine, 'js') + expect(out).toMatchSnapshot() +}) diff --git a/packages/shiki/src/highlighter.ts b/packages/shiki/src/highlighter.ts index fc989a2bf..41739e4a4 100644 --- a/packages/shiki/src/highlighter.ts +++ b/packages/shiki/src/highlighter.ts @@ -56,6 +56,33 @@ export async function getHighlighter(options: HighlighterOptions): Promise = { + '#000001': 'var(--shiki-color-text)', + '#000002': 'var(--shiki-color-background)', + '#000004': 'var(--shiki-token-constant)', + '#000005': 'var(--shiki-token-string)', + '#000006': 'var(--shiki-token-comment)', + '#000007': 'var(--shiki-token-keyword)', + '#000008': 'var(--shiki-token-parameter)', + '#000009': 'var(--shiki-token-function)', + '#000010': 'var(--shiki-token-string-expression)', + '#000011': 'var(--shiki-token-punctuation)', + '#000012': 'var(--shiki-token-link)' + } + + function fixCssVariablesColorMap(colorMap: string[]) { + colorMap.forEach((val, i) => { + colorMap[i] = COLOR_REPLACEMENTS[val] || val + }) + } + function getTheme(theme?: IThemeRegistration) { const _theme = theme ? _registry.getTheme(theme) : _defaultTheme if (!_theme) { @@ -66,6 +93,9 @@ export async function getHighlighter(options: HighlighterOptions): Promise