Skip to content

Commit

Permalink
Use shikiji (#8502)
Browse files Browse the repository at this point in the history
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
bluwy and sarah11918 committed Oct 11, 2023
1 parent f369fa2 commit c4270e4
Show file tree
Hide file tree
Showing 23 changed files with 464 additions and 585 deletions.
33 changes: 33 additions & 0 deletions .changeset/few-peas-hunt.md
@@ -0,0 +1,33 @@
---
'@astrojs/markdoc': minor
'@astrojs/markdown-remark': minor
'astro': minor
---

Updates the internal `shiki` syntax highlighter to `shikiji`, an ESM-focused alternative that simplifies bundling and maintenance.

There are no new options and no changes to how you author code blocks and syntax highlighting.

**Potentially breaking change:** While this refactor should be transparent for most projects, the transition to `shikiji` now produces a smaller HTML markup by attaching a fallback `color` style to the `pre` or `code` element, instead of to the line `span` directly. For example:

Before:

```html
<code class="astro-code" style="background-color: #24292e">
<pre>
<span class="line" style="color: #e1e4e8">my code</span>
</pre>
</code>
```

After:

```html
<code class="astro-code" style="background-color: #24292e; color: #e1e4e8">
<pre>
<span class="line">my code<span>
</pre>
</code>
```

This does not affect the colors as the `span` will inherit the `color` from the parent, but if you're relying on a specific HTML markup, please check your site carefully after upgrading to verify the styles.
89 changes: 60 additions & 29 deletions packages/astro/components/Code.astro
@@ -1,7 +1,14 @@
---
import type * as shiki from 'shiki';
import { renderToHtml } from 'shiki';
import { getHighlighter } from './Shiki.js';
import type {
BuiltinLanguage,
BuiltinTheme,
LanguageRegistration,
SpecialLanguage,
ThemeRegistration,
ThemeRegistrationRaw,
} from 'shikiji';
import { visit } from 'unist-util-visit';
import { getCachedHighlighter, replaceCssVariables } from './shiki.js';
interface Props {
/** The code to highlight. Required. */
Expand All @@ -13,15 +20,15 @@ interface Props {
*
* @default "plaintext"
*/
lang?: shiki.Lang | shiki.ILanguageRegistration;
lang?: BuiltinLanguage | SpecialLanguage | LanguageRegistration;
/**
* The styling theme.
* Supports all themes listed here: https://github.com/shikijs/shiki/blob/main/docs/themes.md#all-themes
* Instructions for loading a custom theme: https://github.com/shikijs/shiki/blob/main/docs/themes.md#loading-theme
*
* @default "github-dark"
*/
theme?: shiki.IThemeRegistration;
theme?: BuiltinTheme | ThemeRegistration | ThemeRegistrationRaw;
/**
* Enable word wrapping.
* - true: enabled.
Expand All @@ -47,41 +54,65 @@ const {
inline = false,
} = Astro.props;
// 1. Get the shiki syntax highlighter
const highlighter = await getHighlighter({
theme,
// Load custom lang if passed an object, otherwise load the default
langs: typeof lang !== 'string' ? [lang] : undefined,
// shiki -> shikiji compat
if (typeof lang === 'object') {
// `id` renamed to `name
if ((lang as any).id && !lang.name) {
lang.name = (lang as any).id;
}
// `grammar` flattened to lang itself
if ((lang as any).grammar) {
Object.assign(lang, (lang as any).grammar);
}
}
const highlighter = await getCachedHighlighter({
langs: [lang],
themes: [theme],
});
// 2. Turn code into shiki theme tokens
const tokens = highlighter.codeToThemedTokens(code, typeof lang === 'string' ? lang : lang.id);
const html = highlighter.codeToHtml(code, {
lang: typeof lang === 'string' ? lang : lang.name,
theme,
transforms: {
pre(node) {
// Swap to `code` tag if inline
if (inline) {
node.tagName = 'code';
}
// 3. Get shiki theme object
const _theme = highlighter.getTheme();
// Cast to string as shikiji will always pass them as strings instead of any other types
const classValue = (node.properties.class as string) ?? '';
const styleValue = (node.properties.style as string) ?? '';
// 4. Render the theme tokens as html
const html = renderToHtml(tokens, {
themeName: _theme.name,
fg: _theme.fg,
bg: _theme.bg,
elements: {
pre({ className, style, children }) {
// Swap to `code` tag if inline
const tag = inline ? 'code' : 'pre';
// Replace "shiki" class naming with "astro-code"
className = className.replace(/shiki/g, 'astro-code');
node.properties.class = classValue.replace(/shiki/g, 'astro-code');
// Handle code wrapping
// if wrap=null, do nothing.
if (wrap === false) {
style += '; overflow-x: auto;';
node.properties.style = styleValue + '; overflow-x: auto;';
} else if (wrap === true) {
style += '; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;';
node.properties.style =
styleValue + '; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;';
}
return `<${tag} class="${className}" style="${style}" tabindex="0">${children}</${tag}>`;
},
code({ children }) {
return inline ? children : `<code>${children}</code>`;
code(node) {
if (inline) {
return node.children[0] as typeof node;
}
},
root(node) {
// theme.id for shiki -> shikiji compat
const themeName = typeof theme === 'string' ? theme : 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);
}
});
}
},
},
});
Expand Down
97 changes: 0 additions & 97 deletions packages/astro/components/Shiki.js

This file was deleted.

0 comments on commit c4270e4

Please sign in to comment.