feat(theme): highlight code blocks at build time with Shiki - #128
Merged
Merged
Conversation
Fenced code blocks previously rendered as plain monospace text. Shiki now highlights them during prerendering, with no client JavaScript for the highlighting itself. Output uses CSS classes rather than Shiki's default inline style attributes, so a strict style-src with no unsafe-inline can serve the pages. Syntax colors are added to each of the six themes and ride the existing derived token pipeline, so theme switching restyles already-highlighted markup without re-highlighting. Each block gets a copy button; unknown languages and unfenced blocks fall back to plain rendering. The sanitizer's blanket class allowance is replaced with a scoped allowlist.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #128 +/- ##
==========================================
+ Coverage 94.40% 94.43% +0.02%
==========================================
Files 18 19 +1
Lines 483 539 +56
Branches 130 144 +14
==========================================
+ Hits 456 509 +53
Misses 12 12
- Partials 15 18 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Body: codecov/patch and codecov/project flagged two uncovered error branches. The unmapped-token-variable path, which exists so a future Shiki release that adds a variable fails to unstyled text rather than leaking an inline style past a strict style-src, is now tested through an exported transformer factory. The highlighter-throws path, which must degrade to a plain code block rather than fail the build, is tested in an isolated file that mocks the Shiki core module, since the highlighter is a module-level singleton. File line coverage goes from 90.69% to 100%.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
markedemits<pre><code class="language-ts">and the article stylesheet only set background, border and font — no highlighting at all, which is the most visible gap against Starlight, Docusaurus and VitePress.Changes
Highlighting Pipeline
createCssVariablesTheme(), which resolves every token to a small fixed set ofvar(--shiki-*)names. This makes a static style-to-class table possible with hand-authored CSS that Astro bundles normally.Why not
transformerStyleToClass@shikijs/transformersships one, but its class names are content hashes discovered lazily as blocks are highlighted, sogetCSS()only knows what it has seen. Astro renders each prerendered page independently with no "after all highlighting" hook, so the stylesheet would need per-page dynamic injection — exactly what a hash-based CSP cannot cover.createCssVariablesTheme()avoids this by resolving to a fixed variable set up front.@shikijs/transformerswas dropped as a dependency after this design change made it unused.CSP Compatibility
Verified zero
style=attributes in built output. Combined with the CSP branch from PR #127, confirmed all five inline scripts — including the copy-button script — plus the one inline style are hashed by the emitted policy, zero unauthorized. The two PRs compose.Themes
themeColorVariables,themePrepaintScriptandThemeSwitcherwith no new plumbing because all three derive fromObject.entries(theme.colors), and theThemeinterface makes an omission a compile error.Sanitizer
'*': ['class']allowing any class on any tag is replaced withallowedClassesscoped todiv/pre/code/spanwith an explicit token list.Styles
--shiki-*CSS variables to theme colors, plus copy-button styling.Review findings applied
createCssVariablesTheme()emits twelve token variables, not the ten first assumed — the three omitted ones are the entirediffgrammar, so diff blocks rendered completely unstyled with added and removed lines visually identical. Added those mappings, CSS rules and theme colors, plus a build-time warning when an unmapped variable appears so a future Shiki release is loud rather than silent.@shikijs/transformers(unused after the design change).aria-labelnaming its language.Test gaps closed
The drift test could not fail for the drift it claimed to guard, since the allowlist and the test's expectations both derive from the same table.
Build cost
+0.33s (~12%), one-time. The highlighter is a module-level singleton and grammars are bounded at 17 unique, not the full bundle.
Verification
tsc --noEmitcleanCloses #107