Skip to content

Commit 096048b

Browse files
authored
Merge pull request #6 from shellui-dev/feat/codeblock-shiki
feat: swap Prism for Shiki - VSCode-quality highlighting via WASM
2 parents 49c209c + 9baddab commit 096048b

5 files changed

Lines changed: 70 additions & 31 deletions

File tree

docs/ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Extracts the palette + scale from `ShellDocs.Components/wwwroot/shelldocs-theme.
9494
- **ShellUI integration path (NuGet install):** ShellUI's RCL detects `ShellDocs.Tokens` at runtime and skips emitting its own token file. Deferred to Phase 3 — needs a small opt-in flag on `AddShellUI()`.
9595
- Unit tests: token file emits, dark-mode class toggling, no duplicate declarations across bundles
9696

97-
### `feat/codeblock-shiki`
97+
### `feat/codeblock-shiki` — shipped
9898
Ships to `ShellDocs.Components`.
9999

100100
- `CodeBlock` component — takes `Language`, `Code`, `Filename`, `HighlightLines`

examples/ShellDocs.Preview/Components/App.razor

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
<link rel="stylesheet" href="_content/ShellDocs.Tokens/tokens.css" />
1010
<link rel="stylesheet" href="_content/ShellDocs.Components/shelldocs-theme.css" />
1111
<link rel="stylesheet" href="ShellDocs.Preview.styles.css" />
12-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" media="(prefers-color-scheme: dark)" />
13-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" media="(prefers-color-scheme: light)" />
1412
<script>
1513
(function () {
1614
var q = new URLSearchParams(location.search).get('theme');
@@ -26,13 +24,17 @@
2624
<body>
2725
<Routes @rendermode="RenderMode.InteractiveServer" />
2826
<script src="_content/ShellDocs.Components/shelldocs.js"></script>
29-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
30-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-csharp.min.js"></script>
31-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
32-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
33-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
34-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup-templating.min.js"></script>
35-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
27+
<script type="module">
28+
/* Shiki singleton highlighter — VSCode-quality syntax colouring via
29+
WASM. Loads the fixed subset of languages we support; dual-theme
30+
(github-light + github-dark) resolves through CSS vars. */
31+
import { createHighlighter } from 'https://esm.sh/shiki@1.24.0';
32+
window.__shiki = await createHighlighter({
33+
themes: ['github-light', 'github-dark'],
34+
langs: ['razor', 'csharp', 'html', 'json', 'yaml', 'bash', 'typescript', 'javascript', 'markdown']
35+
});
36+
if (window.shelldocsHighlight) window.shelldocsHighlight();
37+
</script>
3638
<script src="_framework/blazor.web.js"></script>
3739
</body>
3840
</html>

src/ShellDocs.Components/Content/PreviewFrame.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
}
3838
else
3939
{
40-
@* Prism ships no razor grammar; markup/html grammar handles the tag+attr+string structure just fine. *@
41-
<pre class="preview-code language-markup" @ref="_codeEl"><code class="language-markup">@Preview!.Code</code></pre>
40+
@* Shiki ships a razor grammar — full razor highlighting for previews. *@
41+
<pre class="preview-code language-razor" @ref="_codeEl"><code class="language-razor">@Preview!.Code</code></pre>
4242
}
4343
</div>
4444
</div>

src/ShellDocs.Components/wwwroot/shelldocs-theme.css

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -164,25 +164,32 @@ body { margin: 0; min-height: 100vh; }
164164
font-feature-settings: 'ss01', 'cv02';
165165
}
166166

167-
/* Prism.js overridelet Prism color tokens, keep our frame chrome */
168-
.shelldocs-codeblock pre[class*="language-"],
169-
.shelldocs-prose pre[class*="language-"] {
170-
background: transparent !important;
167+
/* Shiki outputnormalise its inline styles into our chrome. */
168+
.shelldocs-codeblock pre.shiki,
169+
.shelldocs-prose pre.shiki,
170+
.preview-body pre.shiki {
171171
margin: 0 !important;
172172
padding: 1rem 1.125rem !important;
173173
border: 0 !important;
174174
border-radius: 0 !important;
175175
font-family: var(--font-mono) !important;
176176
font-size: 0.85rem !important;
177177
line-height: 1.7 !important;
178-
text-shadow: none !important;
178+
overflow-x: auto;
179179
}
180-
.shelldocs-codeblock code[class*="language-"],
181-
.shelldocs-prose code[class*="language-"] {
180+
pre.shiki code {
182181
background: transparent !important;
183-
border: 0 !important;
184182
padding: 0 !important;
183+
border: 0 !important;
185184
font-family: var(--font-mono) !important;
186185
font-size: inherit !important;
187-
text-shadow: none !important;
188186
}
187+
188+
/* Dual-theme: Shiki emits --shiki-light + --shiki-dark on each token span
189+
with defaultColor:false. Only set `color` — NEVER background on spans, or
190+
every .line wrapper paints a bg strip across the block. */
191+
pre.shiki { background-color: transparent !important; }
192+
pre.shiki,
193+
pre.shiki span { color: var(--shiki-light); }
194+
:root.dark pre.shiki,
195+
:root.dark pre.shiki span { color: var(--shiki-dark); }

src/ShellDocs.Components/wwwroot/shelldocs.js

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,51 @@
11
window.ShellDocs = window.ShellDocs || {};
22

3-
// Re-highlight all code blocks. Called from MarkdownContent after render.
4-
window.shelldocsHighlight = function () {
5-
if (window.Prism) {
6-
try { window.Prism.highlightAll(); } catch (e) {}
3+
/* Shiki-backed highlighters. Both functions replace <pre><code class="language-X">
4+
with Shiki's rendered <pre> so we get VSCode-parity colouring. Idempotent —
5+
a data-shiki flag prevents re-highlighting. */
6+
7+
function langOf(codeEl) {
8+
var cls = (codeEl.className || '').split(/\s+/);
9+
for (var i = 0; i < cls.length; i++) {
10+
if (cls[i].indexOf('language-') === 0) return cls[i].substring(9);
711
}
8-
};
12+
return null;
13+
}
914

10-
// Highlight a specific <pre> element — used by PreviewFrame when the code tab
11-
// mounts, so we don't re-scan the whole page on every tab flip.
12-
window.shelldocsHighlightElement = function (preEl) {
13-
if (!preEl || !window.Prism) return;
15+
function highlightOne(preEl) {
16+
if (!preEl || !window.__shiki) return;
17+
if (preEl.dataset.shiki === 'done') return;
1418
var code = preEl.querySelector('code');
1519
if (!code) return;
16-
try { window.Prism.highlightElement(code); } catch (e) {}
20+
var lang = langOf(code);
21+
if (!lang) return;
22+
/* Shiki doesn't know every Prism alias — silently fall back. */
23+
if (!window.__shiki.getLoadedLanguages().includes(lang)) return;
24+
25+
var source = code.textContent;
26+
try {
27+
var html = window.__shiki.codeToHtml(source, {
28+
lang: lang,
29+
themes: { light: 'github-light', dark: 'github-dark' },
30+
defaultColor: false
31+
});
32+
var tpl = document.createElement('template');
33+
tpl.innerHTML = html.trim();
34+
var newPre = tpl.content.firstElementChild;
35+
if (!newPre) return;
36+
newPre.dataset.shiki = 'done';
37+
preEl.parentNode.replaceChild(newPre, preEl);
38+
} catch (e) { /* skip on grammar error */ }
39+
}
40+
41+
window.shelldocsHighlight = function () {
42+
if (!window.__shiki) return;
43+
document.querySelectorAll('pre:not([data-shiki]) > code[class*="language-"]')
44+
.forEach(function (code) { highlightOne(code.parentElement); });
1745
};
1846

47+
window.shelldocsHighlightElement = function (preEl) { highlightOne(preEl); };
48+
1949
// Copy-to-clipboard for code blocks.
2050
window.shelldocsCopyCode = function (button) {
2151
var block = button.closest('.shelldocs-codeblock');

0 commit comments

Comments
 (0)