feat(site): add shiki notation transformers (#937)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Darius Cepulis
2026-03-13 09:46:27 -05:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 62a8c389e1
commit 2c2610f168
8 changed files with 173 additions and 4 deletions
+44 -1
View File
@@ -440,6 +440,9 @@ importers:
'@sentry/astro':
specifier: ^10.32.1
version: 10.42.0(astro@5.18.0(@netlify/blobs@10.7.0)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(rollup@4.59.0)
'@shikijs/transformers':
specifier: ^4.0.2
version: 4.0.2
'@tailwindcss/vite':
specifier: ^4.2.1
version: 4.2.1(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2))
@@ -2945,6 +2948,10 @@ packages:
'@shikijs/core@3.23.0':
resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==}
'@shikijs/core@4.0.2':
resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==}
engines: {node: '>=20'}
'@shikijs/engine-javascript@3.23.0':
resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==}
@@ -2954,12 +2961,24 @@ packages:
'@shikijs/langs@3.23.0':
resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==}
'@shikijs/primitive@4.0.2':
resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==}
engines: {node: '>=20'}
'@shikijs/themes@3.23.0':
resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==}
'@shikijs/transformers@4.0.2':
resolution: {integrity: sha512-1+L0gf9v+SdDXs08vjaLb3mBFa8U7u37cwcBQIv/HCocLwX69Tt6LpUCjtB+UUTvQxI7BnjZKhN/wMjhHBcJGg==}
engines: {node: '>=20'}
'@shikijs/types@3.23.0':
resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==}
'@shikijs/types@4.0.2':
resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==}
engines: {node: '>=20'}
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
@@ -10302,6 +10321,14 @@ snapshots:
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
'@shikijs/core@4.0.2':
dependencies:
'@shikijs/primitive': 4.0.2
'@shikijs/types': 4.0.2
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
'@shikijs/engine-javascript@3.23.0':
dependencies:
'@shikijs/types': 3.23.0
@@ -10317,15 +10344,31 @@ snapshots:
dependencies:
'@shikijs/types': 3.23.0
'@shikijs/primitive@4.0.2':
dependencies:
'@shikijs/types': 4.0.2
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
'@shikijs/themes@3.23.0':
dependencies:
'@shikijs/types': 3.23.0
'@shikijs/transformers@4.0.2':
dependencies:
'@shikijs/core': 4.0.2
'@shikijs/types': 4.0.2
'@shikijs/types@3.23.0':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
'@shikijs/types@4.0.2':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
'@shikijs/vscode-textmate@10.0.2': {}
'@simple-libs/stream-utils@1.2.0': {}
@@ -10804,7 +10847,7 @@ snapshots:
std-env: 3.10.0
test-exclude: 7.0.2
tinyrainbow: 2.0.0
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2)
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.15)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2)
optionalDependencies:
'@vitest/browser': 3.2.4(playwright@1.58.2)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@3.2.4)
transitivePeerDependencies:
+2 -2
View File
@@ -15,6 +15,7 @@ import pagefind from './integrations/pagefind';
import rehypePrepareCodeBlocks from './src/utils/rehypePrepareCodeBlocks';
import remarkConditionalHeadings from './src/utils/remarkConditionalHeadings';
import { remarkReadingTime } from './src/utils/remarkReadingTime.mjs';
import { shikiNotationTransformers } from './src/utils/shikiNotationTransformers';
import shikiTransformMetadata from './src/utils/shikiTransformMetadata';
// On production deploys, use the custom domain — DEPLOY_PRIME_URL always returns
@@ -98,8 +99,7 @@ export default defineConfig({
light: 'gruvbox-dark-hard',
dark: 'gruvbox-dark-soft',
},
// TODO more shiki transformers
transformers: [shikiTransformMetadata],
transformers: [shikiTransformMetadata, ...shikiNotationTransformers],
},
remarkPlugins: [remarkConditionalHeadings, remarkReadingTime],
rehypePlugins: [rehypePrepareCodeBlocks],
+1
View File
@@ -27,6 +27,7 @@
"@nanostores/react": "^1.0.0",
"@pagefind/default-ui": "^1.4.0",
"@sentry/astro": "^10.32.1",
"@shikijs/transformers": "^4.0.2",
"@tailwindcss/vite": "^4.2.1",
"@videojs/html": "workspace:*",
"@videojs/react": "workspace:*",
+3 -1
View File
@@ -1,6 +1,7 @@
import clsx from 'clsx';
import type { BundledLanguage, Highlighter } from 'shiki';
import { hastToHtml } from 'shiki';
import { shikiNotationTransformers } from '@/utils/shikiNotationTransformers';
export interface SharedProps {
code: string;
@@ -15,6 +16,7 @@ export default function Shared({ code, lang, highlighter }: SharedProps) {
light: 'gruvbox-dark-hard',
dark: 'gruvbox-dark-soft',
},
transformers: shikiNotationTransformers,
});
// shiki gives us a root > pre > code > text structure
@@ -39,7 +41,7 @@ export default function Shared({ code, lang, highlighter }: SharedProps) {
const { class: codeClassName } = codeProps;
return (
<pre className={clsx('shiki text-manila-light', preClassName)}>
<pre className={clsx('shiki astro-code text-manila-light', preClassName)}>
<code className={clsx('font-mono text-code', codeClassName)} dangerouslySetInnerHTML={{ __html: html }} />
</pre>
);
@@ -216,6 +216,44 @@ we can use 3 backticks ``` in new line and write snippet and close with 3 backti
</html>
```
### Code annotations
You can annotate code blocks with special comments to highlight, diff, focus, or word-highlight specific lines. The annotation comments are stripped from the rendered output (and from copy/paste).
#### Diff
Show added and removed lines with `// [!code ++]` and `// [!code --]`:
```ts
function greet(name: string) {
console.log('Hello') // [!code --]
console.log(`Hello, ${name}!`) // [!code ++]
console.log(`It's great to meet you.`) // [!code ++]
}
```
#### Focus
Dim everything except the focused lines with `// [!code focus]`. Hovering the code block reveals all lines:
```ts
function greet(name: string) {
const message = `hello, ${name}!`
console.log(message) // [!code focus]
}
```
#### Word highlight
Highlight a specific word or phrase with `// [!code word:term]`. For example, `// [!code word:name]` looks like this:
```ts
// [!code word:name]
function greet(name: string) {
console.log(`hello, ${name}!`)
}
```
### List types
#### Ordered list
+1
View File
@@ -1,4 +1,5 @@
@import "tailwindcss";
@import "./shiki-transformers.css";
/**
* @license
+72
View File
@@ -0,0 +1,72 @@
@layer base {
.astro-code code {
display: flex;
flex-direction: column;
}
.astro-code .line {
display: inline-flex;
position: relative;
line-height: 1.75;
}
.astro-code .line.diff,
.astro-code .line.focused {
width: calc(100% + var(--spacing) * 12);
margin-inline: calc(var(--spacing) * -6);
padding-inline: calc(var(--spacing) * 6);
}
/* Diff: added lines */
.astro-code .line.diff.add {
background-color: rgba(184, 187, 38, 0.12);
}
.astro-code .line.diff.add::before,
.astro-code .line.diff.remove::before {
position: absolute;
font-weight: bold;
left: 0;
top: 0;
bottom: 0;
width: calc(var(--spacing) * 6);
display: flex;
align-items: center;
justify-content: center;
}
.astro-code .line.diff.add::before {
content: "+";
color: rgba(184, 187, 31, 1);
}
/* Diff: removed lines */
.astro-code .line.diff.remove {
background-color: rgba(251, 73, 52, 0.12);
}
.astro-code .line.diff.remove::before {
content: "-";
color: rgba(251, 73, 52, 1);
}
/* Word highlight */
.astro-code .highlighted-word {
background-color: var(--color-soot);
border-bottom: 1px solid var(--color-manila-dark);
padding-inline: 0.2em;
}
:where(.dark, .dark *) .astro-code .highlighted-word {
background-color: var(--color-faded-black);
}
/* Focus: mute unfocused lines, reveal on hover */
.astro-code:has(.line.focused) .line:not(.focused) span {
opacity: 0.4;
transition: opacity 0.3s;
}
.astro-code:has(.line.focused):hover .line:not(.focused) span,
.astro-code:has(.line.focused):focus-within .line:not(.focused) span {
opacity: 1;
}
}
@@ -0,0 +1,12 @@
import {
transformerNotationDiff,
transformerNotationFocus,
transformerNotationWordHighlight,
} from '@shikijs/transformers';
import type { ShikiTransformer } from 'shiki';
export const shikiNotationTransformers: ShikiTransformer[] = [
transformerNotationDiff({ matchAlgorithm: 'v3' }),
transformerNotationFocus({ matchAlgorithm: 'v3' }),
transformerNotationWordHighlight({ matchAlgorithm: 'v3' }),
];