@peaceroad/markdown-it-renderer-fence 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -471
- package/docs/README.md +37 -0
- package/docs/code-highlighting-design.md +317 -0
- package/docs/custom-highlight-styling-guide.md +204 -0
- package/package.json +21 -11
- package/src/custom-highlight/option-validator.js +5 -5
- package/src/custom-highlight/{shiki-keyword-rules.js → shiki-role-rules.js} +229 -44
- package/src/custom-highlight/{shiki-keyword.js → shiki-role.js} +64 -56
- package/src/custom-highlight/shiki-theme-role.js +176 -0
- package/src/fence/line-notes.js +27 -9
- package/src/fence/render-api-provider.js +59 -50
- package/src/fence/render-api-renderer.js +7 -6
- package/src/fence/render-api.js +20 -12
- package/src/fence/render-markup.js +4 -9
- package/src/fence/render-shared.js +78 -8
- package/theme/_shared/rf-core.css +164 -0
- package/theme/rf-basic/README.md +91 -0
- package/theme/rf-basic/api/highlightjs.css +71 -0
- package/theme/rf-basic/api/shiki.css +67 -0
- package/theme/rf-basic/base.css +3 -0
- package/theme/rf-basic/index.css +7 -0
- package/theme/rf-basic/index.js +155 -0
- package/theme/rf-basic/markup/highlightjs.css +77 -0
- package/theme/rf-basic/markup/shiki.css +11 -0
- package/theme/rf-monochrome/README.md +71 -0
- package/theme/rf-monochrome/base.css +3 -0
- package/theme/rf-monochrome/index.css +5 -0
- package/theme/rf-monochrome/index.js +72 -0
- package/theme/rf-monochrome/markup/highlightjs.css +134 -0
- package/theme/rf-monochrome/markup/shiki.css +27 -0
package/docs/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
This directory contains the long-form documentation for
|
|
4
|
+
`@peaceroad/markdown-it-renderer-fence`.
|
|
5
|
+
|
|
6
|
+
The top-level [README](../README.md) is intentionally a quick-start and API
|
|
7
|
+
surface overview. Keep details here unless they are needed for first use.
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
- [Code Highlighting Design](code-highlighting-design.md)
|
|
12
|
+
- mode selection
|
|
13
|
+
- renderer/provider/runtime/theme responsibility boundaries
|
|
14
|
+
- Shiki role-mode policy
|
|
15
|
+
- theme packaging policy
|
|
16
|
+
- line-feature and performance trade-offs
|
|
17
|
+
- [Custom Highlight Styling Guide](custom-highlight-styling-guide.md)
|
|
18
|
+
- API-mode color sources
|
|
19
|
+
- recommended Shiki/highlight.js profiles
|
|
20
|
+
- runtime apply and light/dark behavior
|
|
21
|
+
- preset CSS usage
|
|
22
|
+
|
|
23
|
+
Theme-specific documentation lives next to each packaged theme:
|
|
24
|
+
|
|
25
|
+
- [rf-basic Theme](../theme/rf-basic/README.md)
|
|
26
|
+
- [rf-monochrome Theme](../theme/rf-monochrome/README.md)
|
|
27
|
+
|
|
28
|
+
Repository examples are described in [example/README.md](../example/README.md).
|
|
29
|
+
|
|
30
|
+
## Documentation ownership
|
|
31
|
+
|
|
32
|
+
- Put runnable first-use snippets in the top-level README.
|
|
33
|
+
- Put operational API-mode guidance in the styling guide.
|
|
34
|
+
- Put durable design policy and maintenance trade-offs in the design document.
|
|
35
|
+
- Put palette/file-role details in each theme README.
|
|
36
|
+
- Keep generated example pages under `example/`; do not treat them as package
|
|
37
|
+
implementation dependencies.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Code Highlighting Design
|
|
2
|
+
|
|
3
|
+
This document describes how code highlighting should be designed, configured, and maintained in this package.
|
|
4
|
+
The README keeps the quick-start surface small; this document records the deeper policy and trade-offs.
|
|
5
|
+
|
|
6
|
+
## 1. What This Plugin Owns
|
|
7
|
+
|
|
8
|
+
`@peaceroad/markdown-it-renderer-fence` is a `markdown-it` fence renderer plugin.
|
|
9
|
+
It is an integration layer around Markdown fence tokens, optional syntax highlighters, line features, and optional browser Custom Highlight API payloads.
|
|
10
|
+
|
|
11
|
+
The plugin owns:
|
|
12
|
+
|
|
13
|
+
- fence info/attribute normalization
|
|
14
|
+
- safe `<pre><code>` / `<pre><samp>` rendering
|
|
15
|
+
- integration with `md.options.highlight` in markup mode
|
|
16
|
+
- optional API-mode payload generation from supported providers
|
|
17
|
+
- line features such as line numbers, emphasized lines, comment marks, and sidecar line notes
|
|
18
|
+
- fail-closed behavior when highlighted line counts or sidecar syntax cannot be mapped safely
|
|
19
|
+
- optional preset CSS and provider option helpers
|
|
20
|
+
|
|
21
|
+
The plugin does not own:
|
|
22
|
+
|
|
23
|
+
- a full syntax parser for every language
|
|
24
|
+
- asynchronous highlighter initialization inside `markdown-it` rendering
|
|
25
|
+
- automatic CSS or runtime JavaScript injection into the host page
|
|
26
|
+
- a huge language keyword database that tries to clone every upstream grammar
|
|
27
|
+
- replacing Shiki or highlight.js as the primary syntax highlighter
|
|
28
|
+
- importing runtime or theme implementation from `example/`
|
|
29
|
+
|
|
30
|
+
This boundary is important: the best default is a stable renderer with good integration points, not a second highlighter implementation hidden inside a Markdown plugin.
|
|
31
|
+
Examples may import public package entry points for demonstrations and generated comparison pages, but implementation and public integration helpers must remain independent from `example/`.
|
|
32
|
+
|
|
33
|
+
## 2. Mode Selection
|
|
34
|
+
|
|
35
|
+
Choose the renderer mode before choosing the theme.
|
|
36
|
+
|
|
37
|
+
| Use case | Recommended mode | Why |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Static HTML, README-like output, blogs, docs sites | `markup` | Simple, portable HTML. No browser runtime required. |
|
|
40
|
+
| Existing highlight.js or Shiki HTML output is enough | `markup` | Let the highlighter own token spans and CSS classes. |
|
|
41
|
+
| You need `CSS.highlights` ranges and external runtime control | `api` | Produces escaped text plus payload ranges for browser-side application. |
|
|
42
|
+
| You need cleaner copy behavior with line notes outside code text | `api` or markup with structural line features | API mode avoids highlighter spans in text; sidecar notes stay outside `<code>` / `<samp>`. |
|
|
43
|
+
| You need theme-faithful Shiki colors | `api` + Shiki `color` or `semantic`, or markup Shiki | Keep Shiki as the color authority. |
|
|
44
|
+
| You need CSS-managed light/dark buckets | `api` + Shiki `role` | Stable role buckets controlled by package/user CSS. |
|
|
45
|
+
| You use normal highlight.js class CSS | `markup` | The highlight.js provider bridge is useful, but markup mode is usually simpler. |
|
|
46
|
+
|
|
47
|
+
Defaulting to `markup` is intentional. API mode is advanced because it has a browser runtime contract.
|
|
48
|
+
|
|
49
|
+
## 3. Markup Mode
|
|
50
|
+
|
|
51
|
+
Markup mode is the default and should remain the recommended path for most users.
|
|
52
|
+
|
|
53
|
+
In markup mode:
|
|
54
|
+
|
|
55
|
+
1. the plugin normalizes fence attrs and language classes
|
|
56
|
+
2. `md.options.highlight` may return escaped HTML, `<code>...</code>`, or `<pre><code>...</code>`
|
|
57
|
+
3. the plugin merges safe wrapper attrs when possible
|
|
58
|
+
4. line features are applied only when the output can be line-split safely
|
|
59
|
+
|
|
60
|
+
Design rules:
|
|
61
|
+
|
|
62
|
+
- Keep `md.options.highlight` as the highlighter boundary.
|
|
63
|
+
- Do not require a package-specific highlighter helper for normal markup mode.
|
|
64
|
+
- Treat `<samp>` as an output semantics decision, not as a syntax language. Use `sampLang` for site-wide terminal defaults and `{samp}` / `{code}` for per-fence overrides.
|
|
65
|
+
- If `useHighlightPre` passes through highlighter-owned `<pre><code>`, structural line features must be skipped.
|
|
66
|
+
- If the highlighted logical line count differs from the source logical line count, line-number controls, comment marks, and line notes must be skipped rather than mapped incorrectly.
|
|
67
|
+
- Attribute parsing should be conservative; unusual highlighter wrapper HTML should fall back safely.
|
|
68
|
+
|
|
69
|
+
Markup mode is the right choice when the output needs to work in static environments or when the host page already has highlighter CSS.
|
|
70
|
+
|
|
71
|
+
## 4. Custom Highlight API Mode
|
|
72
|
+
|
|
73
|
+
API mode is opt-in. It renders escaped code text and emits a payload describing highlight ranges.
|
|
74
|
+
The browser then applies ranges with `CSS.highlights` through `applyCustomHighlights()` or `observeCustomHighlights()`.
|
|
75
|
+
|
|
76
|
+
In API mode:
|
|
77
|
+
|
|
78
|
+
1. provider output is normalized into ranges
|
|
79
|
+
2. scope names are sanitized and de-collided
|
|
80
|
+
3. payloads are emitted via `env` or inline JSON script transport
|
|
81
|
+
4. the runtime applies ranges to `pre > code` and `pre > samp`
|
|
82
|
+
5. optional CSS or payload `scopeStyles` controls visual color
|
|
83
|
+
|
|
84
|
+
API mode is useful when:
|
|
85
|
+
|
|
86
|
+
- a page wants text content to remain clean and span-free
|
|
87
|
+
- line notes and other overlays should remain outside copied code text
|
|
88
|
+
- a SPA can explicitly re-apply highlights after DOM updates
|
|
89
|
+
- light/dark styling should be controlled by CSS variables and runtime variant selection
|
|
90
|
+
|
|
91
|
+
API mode is not ideal when:
|
|
92
|
+
|
|
93
|
+
- the host cannot load runtime JavaScript
|
|
94
|
+
- the rendered HTML must be fully styled without post-processing
|
|
95
|
+
- the highlighter already emits satisfactory HTML and CSS
|
|
96
|
+
|
|
97
|
+
Runtime responsibilities should remain explicit. The Markdown render step should not automatically emit a general-purpose runtime script because bundlers, CSP policies, and page lifecycle rules differ across hosts.
|
|
98
|
+
|
|
99
|
+
## 5. Provider Policy
|
|
100
|
+
|
|
101
|
+
### Shiki Provider
|
|
102
|
+
|
|
103
|
+
The Shiki provider requires a synchronous highlighter object with `codeToTokens`.
|
|
104
|
+
Highlighter creation and language/theme loading are caller responsibilities.
|
|
105
|
+
|
|
106
|
+
Supported Shiki scope modes:
|
|
107
|
+
|
|
108
|
+
- `color`: derive scope names from Shiki theme colors; closest to theme-owned visual output
|
|
109
|
+
- `semantic`: use more semantic Shiki explanation scopes; useful when users want scope-like names
|
|
110
|
+
- `role`: map Shiki scopes and token text to stable rf-basic role buckets
|
|
111
|
+
|
|
112
|
+
`role` mode is a CSS-managed role adapter, not a full replacement for Shiki's token taxonomy.
|
|
113
|
+
It should be judged by practical visual role parity, not by one-to-one upstream scope identity.
|
|
114
|
+
|
|
115
|
+
### highlight.js Provider
|
|
116
|
+
|
|
117
|
+
The highlight.js provider converts highlight.js output into API payload ranges.
|
|
118
|
+
It is a compatibility bridge for users who want API mode while using highlight.js.
|
|
119
|
+
|
|
120
|
+
If normal highlight.js HTML is enough, prefer markup mode.
|
|
121
|
+
That keeps highlight.js in its usual ownership model: markup classes plus CSS.
|
|
122
|
+
|
|
123
|
+
### Custom Provider
|
|
124
|
+
|
|
125
|
+
The custom provider is an escape hatch for precomputed ranges or application-specific highlighters.
|
|
126
|
+
It must still follow the same payload invariants: valid range bounds, valid scope indexes, and safe sanitized scope names.
|
|
127
|
+
|
|
128
|
+
## 6. Role Mode Policy
|
|
129
|
+
|
|
130
|
+
Shiki `role` mode exists to provide stable CSS buckets such as:
|
|
131
|
+
|
|
132
|
+
- `keyword`
|
|
133
|
+
- `string`
|
|
134
|
+
- `number`
|
|
135
|
+
- `comment`
|
|
136
|
+
- `type`
|
|
137
|
+
- `title`
|
|
138
|
+
- `variable`
|
|
139
|
+
- `literal`
|
|
140
|
+
- `tag`
|
|
141
|
+
- `attribute`
|
|
142
|
+
- `punctuation`
|
|
143
|
+
- `text`
|
|
144
|
+
|
|
145
|
+
The goal is broad visual parity with the package preset theme, not exhaustive grammar cloning.
|
|
146
|
+
|
|
147
|
+
Rules for maintaining role mode:
|
|
148
|
+
|
|
149
|
+
- Prefer role buckets over language-specific taxonomy.
|
|
150
|
+
- Keep the bucket list small and themeable.
|
|
151
|
+
- Use Shiki explanation scopes first, then token text and language-specific rules only when they clarify a role.
|
|
152
|
+
- Do not classify all `keyword.operator` scopes as `punctuation`; only demote safe punctuation-like operators.
|
|
153
|
+
- Avoid adding framework, library, or application names as keywords.
|
|
154
|
+
- Avoid bulk-importing large upstream keyword lists unless the data has a clear maintenance owner and test value.
|
|
155
|
+
- Favor local correction rules for known over-classification patterns, such as JSON property quotes, HTML text, CSS property values, SQL identifiers, or shell punctuation.
|
|
156
|
+
- Treat unknown languages safely: fall back to scope heuristics and `text` rather than aggressive coloring.
|
|
157
|
+
|
|
158
|
+
Role parity should improve through:
|
|
159
|
+
|
|
160
|
+
1. fixture and holdout samples that represent real documents
|
|
161
|
+
2. mismatch reports that identify role-level residuals
|
|
162
|
+
3. small rules with clear comments and tests
|
|
163
|
+
4. package theme tuning when a mismatch is visual rather than semantic
|
|
164
|
+
|
|
165
|
+
The target is high practical parity, not 100% identical Shiki output for every token.
|
|
166
|
+
For the curated matrix samples, it is reasonable to keep each language around 98% or better non-whitespace parity when the correction is role-level and easy to explain.
|
|
167
|
+
The holdout regression gate can stay more conservative than that target: it should catch broad regressions with an average non-whitespace floor plus a minimum-language floor, because some grammars expose coarse or theme-specific scopes.
|
|
168
|
+
Forcing 100% everywhere would likely require cloning theme-specific and grammar-specific behavior, which would make the plugin heavier and more brittle.
|
|
169
|
+
|
|
170
|
+
## 7. Theme Policy
|
|
171
|
+
|
|
172
|
+
The optional preset theme is a convenience layer, not an implicit dependency.
|
|
173
|
+
|
|
174
|
+
Exports:
|
|
175
|
+
|
|
176
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic`
|
|
177
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic.css`
|
|
178
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/base.css`
|
|
179
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/api/shiki.css`
|
|
180
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/api/highlightjs.css`
|
|
181
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/markup/shiki.css`
|
|
182
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/markup/highlightjs.css`
|
|
183
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-monochrome`
|
|
184
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-monochrome.css`
|
|
185
|
+
|
|
186
|
+
The theme should cover:
|
|
187
|
+
|
|
188
|
+
- base code block styling
|
|
189
|
+
- inline code styling
|
|
190
|
+
- line numbers
|
|
191
|
+
- emphasized lines
|
|
192
|
+
- comment marks
|
|
193
|
+
- sidecar line notes
|
|
194
|
+
- `<samp>` terminal-like blocks
|
|
195
|
+
- Shiki markup `<samp>` surface correction, because Shiki emits inline
|
|
196
|
+
`pre` background/color styles
|
|
197
|
+
- Shiki API `::highlight(...)` names
|
|
198
|
+
- highlight.js markup/API class names
|
|
199
|
+
|
|
200
|
+
Design rules:
|
|
201
|
+
|
|
202
|
+
- CSS must not be auto-loaded by the renderer.
|
|
203
|
+
- CSS variables should be stable enough for consumers to override.
|
|
204
|
+
- Light/dark variants should share the same role names.
|
|
205
|
+
- `samp` should read as terminal output; using an inverted-looking surface is acceptable when contrast remains strong.
|
|
206
|
+
- A one-color print theme should be markup-mode-only when it relies on bold/italic, because Custom Highlight API
|
|
207
|
+
cannot apply `font-weight` or `font-style`.
|
|
208
|
+
- In one-color terminal-like `samp` blocks, token-level bold/italic can make command/output boundaries ambiguous. Prefer suppressing token emphasis unless the terminal renderer has structural prompt/input/output markup.
|
|
209
|
+
- Provider-specific CSS can be split, but base layout and shared variables should stay central.
|
|
210
|
+
- An internal shared CSS source under `theme/_shared/` is acceptable when public theme files
|
|
211
|
+
import it and package dry-runs verify it is shipped. Consumers should import public theme
|
|
212
|
+
paths such as `theme/rf-basic/base.css`, not private shared files.
|
|
213
|
+
- Header comments should identify the package/theme and license briefly; deeper rationale belongs in docs.
|
|
214
|
+
|
|
215
|
+
The theme JS helper is a render-side helper for provider options and Shiki theme data.
|
|
216
|
+
It does not replace the browser runtime apply step for API mode.
|
|
217
|
+
|
|
218
|
+
## 8. Line Features and Copy Semantics
|
|
219
|
+
|
|
220
|
+
Line features must preserve safe mapping from source lines to rendered lines.
|
|
221
|
+
|
|
222
|
+
Important behavior:
|
|
223
|
+
|
|
224
|
+
- `line-notes` / `notes` are immediate sidecar fences.
|
|
225
|
+
- Sidecar notes render outside `<code>` / `<samp>` to keep copied code cleaner.
|
|
226
|
+
- Malformed sidecar fences fail closed and remain literal code blocks.
|
|
227
|
+
- Line features are skipped when highlighter output line counts do not match source line counts.
|
|
228
|
+
- `useHighlightPre` passthrough disables structural line splitting by design.
|
|
229
|
+
- `comment-mark` should avoid extra scans unless the feature is enabled and line splitting is safe.
|
|
230
|
+
|
|
231
|
+
This conservative behavior is better than visually plausible but semantically wrong annotations.
|
|
232
|
+
|
|
233
|
+
## 9. Performance Policy
|
|
234
|
+
|
|
235
|
+
Performance work should keep the default path lean.
|
|
236
|
+
|
|
237
|
+
Current principles:
|
|
238
|
+
|
|
239
|
+
- Normalize options at plugin setup where possible.
|
|
240
|
+
- Keep per-render state in `env` or render-local objects, not global mutable state.
|
|
241
|
+
- Reset API payload maps at render start when reusing `env`.
|
|
242
|
+
- Use cheap source sentinels before scanning tokens for sidecar fences.
|
|
243
|
+
- Run line splitting only when a line feature needs it.
|
|
244
|
+
- Parse advanced line-number ranges lazily.
|
|
245
|
+
- Keep role-mode caches bounded.
|
|
246
|
+
- Avoid payload digest/state work unless incremental runtime apply is requested.
|
|
247
|
+
- Keep markup-only and custom-highlight entries available for load isolation.
|
|
248
|
+
|
|
249
|
+
Benchmark changes with:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm run test:performance
|
|
253
|
+
npm run test:performance:provider-modes
|
|
254
|
+
npm run test:performance:runtime
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Compare before/after runs in both orders when judging micro-optimizations.
|
|
258
|
+
Shiki is normally much heavier than highlight.js in this repository's benchmark corpus, so avoid turning Shiki-specific work into the default markup hot path.
|
|
259
|
+
|
|
260
|
+
## 10. Configuration Recipes
|
|
261
|
+
|
|
262
|
+
This document intentionally does not duplicate full setup recipes. Keep runnable examples in the README and API-specific operational guidance in the styling guide.
|
|
263
|
+
|
|
264
|
+
Use these entry points:
|
|
265
|
+
|
|
266
|
+
- markup with highlight.js: README quick start
|
|
267
|
+
- markup with Shiki HTML: README Shiki example
|
|
268
|
+
- API mode profiles and runtime behavior: [Custom Highlight Styling Guide](custom-highlight-styling-guide.md)
|
|
269
|
+
- concrete preset theme imports and color rationale: [rf-basic Theme README](../theme/rf-basic/README.md)
|
|
270
|
+
|
|
271
|
+
When adding a new recipe, place it where the reader expects to copy it:
|
|
272
|
+
|
|
273
|
+
- short first-use snippets: README
|
|
274
|
+
- API runtime/color/payload choices: styling guide
|
|
275
|
+
- long-term policy and trade-offs: this design document
|
|
276
|
+
- palette and file-role details: theme README
|
|
277
|
+
|
|
278
|
+
## 11. Testing and Regression Checklist
|
|
279
|
+
|
|
280
|
+
For code highlighting changes, prefer this order:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
npm run test:theme:contrast
|
|
284
|
+
npm run test:provider:role
|
|
285
|
+
npm run test:provider:role:holdout
|
|
286
|
+
npm run test:provider:matrix
|
|
287
|
+
npm test
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Use the mismatch report when improving role mode:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
node test/custom-highlight/provider-role-mismatch-report.js
|
|
294
|
+
node test/custom-highlight/provider-role-mismatch-report.js example/custom-highlight-provider-matrix.html javascript --detail
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Before accepting a new rule, check:
|
|
298
|
+
|
|
299
|
+
- Does it improve multiple realistic samples, or only one overfit case?
|
|
300
|
+
- Does it keep unknown languages safe?
|
|
301
|
+
- Does it increase the hot path for all modes or only for role mode?
|
|
302
|
+
- Does it preserve payload invariants?
|
|
303
|
+
- Does it keep the public bucket list stable?
|
|
304
|
+
- Does it require new CSS variables, and are light/dark contrast checks updated?
|
|
305
|
+
|
|
306
|
+
## 12. Future Direction
|
|
307
|
+
|
|
308
|
+
The best long-term direction is:
|
|
309
|
+
|
|
310
|
+
1. keep markup mode simple and stable
|
|
311
|
+
2. keep API mode explicit about runtime responsibilities
|
|
312
|
+
3. make the optional theme good enough to stand beside common Shiki/highlight.js themes for rf-basic use cases
|
|
313
|
+
4. improve Shiki role parity through measured, role-level rules rather than unbounded keyword growth
|
|
314
|
+
5. maintain provider contract tests and holdout fixtures as the source of truth for practical compatibility
|
|
315
|
+
|
|
316
|
+
Large algorithm rewrites are justified only when they simplify these responsibilities or clearly improve correctness/performance across realistic documents.
|
|
317
|
+
If a change mostly chases a matrix percentage by adding brittle language-specific exceptions, it should stay out of the default implementation.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Custom Highlight Styling Guide
|
|
2
|
+
|
|
3
|
+
This guide explains practical styling choices for `highlightRenderer: 'api'`.
|
|
4
|
+
For broader mode selection, provider responsibility boundaries, role-mode policy, and theme design,
|
|
5
|
+
see [Code Highlighting Design](code-highlighting-design.md).
|
|
6
|
+
For the documentation map, see [Documentation](README.md).
|
|
7
|
+
|
|
8
|
+
## 1. Choose Your Mode First
|
|
9
|
+
|
|
10
|
+
- Most users should stay on `markup` mode.
|
|
11
|
+
- Use API mode only when you need browser runtime ranges (`CSS.highlights`) and custom runtime control.
|
|
12
|
+
|
|
13
|
+
Runtime note:
|
|
14
|
+
|
|
15
|
+
- API mode requires browser-side apply (`applyCustomHighlights` or `observeCustomHighlights`).
|
|
16
|
+
- `test/custom-highlight/pre-highlight.js` is a demo helper, not the package runtime API contract.
|
|
17
|
+
- runtime-only import path is `@peaceroad/markdown-it-renderer-fence/custom-highlight-runtime`.
|
|
18
|
+
- markdown-it render itself does not emit runtime JS files; CLI/static generators should output a small runtime bridge asset separately.
|
|
19
|
+
- optional render-side theme helpers live at `@peaceroad/markdown-it-renderer-fence/theme/rf-basic`; they do not replace browser-side runtime apply.
|
|
20
|
+
|
|
21
|
+
## 2. Where API-Mode Colors Come From
|
|
22
|
+
|
|
23
|
+
In API mode, color comes from one of two sources:
|
|
24
|
+
|
|
25
|
+
1. payload `scopeStyles` (`customHighlight.includeScopeStyles: true`)
|
|
26
|
+
2. external `::highlight(...)` CSS (`customHighlight.includeScopeStyles: false`)
|
|
27
|
+
|
|
28
|
+
The plugin does not auto-load theme CSS files.
|
|
29
|
+
The complete optional preset theme CSS is exported as `@peaceroad/markdown-it-renderer-fence/theme/rf-basic.css`.
|
|
30
|
+
Provider-specific split files are also available:
|
|
31
|
+
`theme/rf-basic/base.css`, `theme/rf-basic/api/shiki.css`, `theme/rf-basic/api/highlightjs.css`,
|
|
32
|
+
`theme/rf-basic/markup/shiki.css`, and `theme/rf-basic/markup/highlightjs.css`.
|
|
33
|
+
The optional JS theme helper uses CSS variable color values so Shiki can emit payload `scopeStyles` like `var(--rf-syntax-keyword)`.
|
|
34
|
+
Your page still needs CSS that defines those variables, either by loading the preset CSS or by defining compatible variables in your app.
|
|
35
|
+
|
|
36
|
+
## 3. Recommended Profiles
|
|
37
|
+
|
|
38
|
+
### Profile A (recommended for production)
|
|
39
|
+
|
|
40
|
+
Use CSS-managed role buckets:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
customHighlight: {
|
|
44
|
+
provider: 'shiki',
|
|
45
|
+
shikiScopeMode: 'role',
|
|
46
|
+
includeScopeStyles: false,
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Why:
|
|
51
|
+
|
|
52
|
+
- stable scope names (`hl-shiki-role-keyword`, `hl-shiki-role-string`, ...)
|
|
53
|
+
- easiest light/dark control with CSS variables and media queries
|
|
54
|
+
- smaller payloads
|
|
55
|
+
|
|
56
|
+
### Profile B (theme-faithful embedded colors)
|
|
57
|
+
|
|
58
|
+
Use Shiki color scopes with embedded styles:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
import {
|
|
62
|
+
createRfBasicShikiCustomHighlightOptions,
|
|
63
|
+
rfBasicShikiTheme,
|
|
64
|
+
} from '@peaceroad/markdown-it-renderer-fence/theme/rf-basic'
|
|
65
|
+
|
|
66
|
+
const highlighter = await createHighlighter({
|
|
67
|
+
themes: [rfBasicShikiTheme],
|
|
68
|
+
langs: ['javascript'],
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
customHighlight: {
|
|
72
|
+
...createRfBasicShikiCustomHighlightOptions({
|
|
73
|
+
highlighter,
|
|
74
|
+
shikiScopeMode: 'color',
|
|
75
|
+
includeScopeStyles: true,
|
|
76
|
+
}),
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use this only when you need self-contained per-theme styles in payloads.
|
|
81
|
+
If the Shiki theme uses CSS variables, the payload stays small and can follow light/dark CSS variables without a second Shiki tokenization pass.
|
|
82
|
+
|
|
83
|
+
## 4. Shiki Scope Modes
|
|
84
|
+
|
|
85
|
+
### `shikiScopeMode: 'color'`
|
|
86
|
+
|
|
87
|
+
- scope name is color-derived
|
|
88
|
+
- closest to selected Shiki theme output
|
|
89
|
+
- less maintainable for long-term CSS governance
|
|
90
|
+
|
|
91
|
+
### `shikiScopeMode: 'semantic'`
|
|
92
|
+
|
|
93
|
+
- scope name is TextMate-like semantic scope
|
|
94
|
+
- useful for analysis and fine-grained mapping
|
|
95
|
+
- usually more complex than needed for site-wide design tokens
|
|
96
|
+
|
|
97
|
+
### `shikiScopeMode: 'role'`
|
|
98
|
+
|
|
99
|
+
- scope name is plugin bucket name
|
|
100
|
+
- best for long-term CSS-managed theming
|
|
101
|
+
|
|
102
|
+
## 5. Light/Dark Behavior (Important)
|
|
103
|
+
|
|
104
|
+
### Initial apply
|
|
105
|
+
|
|
106
|
+
- `applyCustomHighlights(root, { colorScheme: 'auto' })` resolves current scheme at apply time.
|
|
107
|
+
|
|
108
|
+
### After page is already open and OS/browser theme changes
|
|
109
|
+
|
|
110
|
+
- `applyCustomHighlights(...)` does not automatically rerun by itself.
|
|
111
|
+
- You need either:
|
|
112
|
+
1. explicit re-apply call, or
|
|
113
|
+
2. observer with color-scheme watch.
|
|
114
|
+
|
|
115
|
+
Example with automatic watch:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
const lazy = observeCustomHighlights(document, {
|
|
119
|
+
applyOptions: { colorScheme: 'auto', incremental: true },
|
|
120
|
+
watchColorScheme: true,
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
With `watchColorScheme: true`, after the first apply, `prefers-color-scheme` changes trigger re-apply.
|
|
125
|
+
|
|
126
|
+
## 6. Theme Option Shapes
|
|
127
|
+
|
|
128
|
+
`customHighlight.theme` supports:
|
|
129
|
+
|
|
130
|
+
- `string` (single-theme payload)
|
|
131
|
+
- `{ light, dark, default? }` (dual-theme payload with additive `v:1` fields)
|
|
132
|
+
|
|
133
|
+
Notes:
|
|
134
|
+
|
|
135
|
+
- object form runs Shiki tokenization twice (`light` + `dark`)
|
|
136
|
+
- payload size can grow (especially in `shikiScopeMode: 'color'`)
|
|
137
|
+
- a single CSS-variable Shiki theme can be a simpler alternative when your CSS variables already switch between light/dark values.
|
|
138
|
+
|
|
139
|
+
## 7. Runtime and CSS Constraints
|
|
140
|
+
|
|
141
|
+
`::highlight(...)` reliably supports only a subset of properties.
|
|
142
|
+
This plugin emits/uses:
|
|
143
|
+
|
|
144
|
+
- `color`
|
|
145
|
+
- `background-color`
|
|
146
|
+
- `text-decoration`
|
|
147
|
+
- `text-shadow`
|
|
148
|
+
|
|
149
|
+
Do not expect full token-span parity for properties like:
|
|
150
|
+
|
|
151
|
+
- `font-style`
|
|
152
|
+
- `font-weight`
|
|
153
|
+
|
|
154
|
+
## 8. highlight.js Provider Notes
|
|
155
|
+
|
|
156
|
+
For `customHighlight.provider: 'hljs'`:
|
|
157
|
+
|
|
158
|
+
- scope names are hljs-derived (`hl-hljs-*`)
|
|
159
|
+
- `scopeStyles` is typically not emitted
|
|
160
|
+
- external `::highlight(...)` CSS is the standard approach
|
|
161
|
+
|
|
162
|
+
For normal markup highlighting with highlight.js, continue using highlight.js theme CSS.
|
|
163
|
+
|
|
164
|
+
## 9. Preset Theme
|
|
165
|
+
|
|
166
|
+
This package ships an optional preset theme. It is not auto-loaded, but it is a maintained package artifact rather than an example-only file.
|
|
167
|
+
|
|
168
|
+
CSS:
|
|
169
|
+
|
|
170
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic.css`
|
|
171
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/base.css`
|
|
172
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/api/shiki.css`
|
|
173
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/api/highlightjs.css`
|
|
174
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/markup/shiki.css`
|
|
175
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic/markup/highlightjs.css`
|
|
176
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-monochrome.css`
|
|
177
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-monochrome/markup/highlightjs.css`
|
|
178
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-monochrome/markup/shiki.css`
|
|
179
|
+
|
|
180
|
+
JS helper:
|
|
181
|
+
|
|
182
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-basic`
|
|
183
|
+
- `@peaceroad/markdown-it-renderer-fence/theme/rf-monochrome`
|
|
184
|
+
|
|
185
|
+
The JS helper provides:
|
|
186
|
+
|
|
187
|
+
- `rfBasicShikiTheme`: a Shiki theme object that uses `--rf-*` CSS variables.
|
|
188
|
+
- `createRfBasicShikiCustomHighlightOptions(...)`: a small Shiki provider option helper.
|
|
189
|
+
- `createRfBasicHighlightjsCustomHighlightOptions(...)`: a small highlight.js provider option helper.
|
|
190
|
+
- role maps used by repository examples to keep Shiki, highlight.js, and `::highlight(...)` CSS aligned.
|
|
191
|
+
|
|
192
|
+
See `theme/rf-basic/README.md` and `theme/rf-monochrome/README.md` for the color design and file roles.
|
|
193
|
+
|
|
194
|
+
Note: `rf-monochrome.css` is a markup-mode theme. It uses the shared code surface plus bold/italic instead of
|
|
195
|
+
syntax colors, so it is useful for print or one-color output. It is not a Custom Highlight API theme because
|
|
196
|
+
`::highlight(...)` cannot apply `font-weight` or `font-style`.
|
|
197
|
+
|
|
198
|
+
The repository keeps `example/theme.css` only as a tiny local wrapper around the distributed preset CSS.
|
|
199
|
+
|
|
200
|
+
For production:
|
|
201
|
+
|
|
202
|
+
- Markup mode: use this preset, the CSS theme from your highlighter, or your design system.
|
|
203
|
+
- API mode with `includeScopeStyles: true`: provider style data may be emitted in payloads.
|
|
204
|
+
- API mode with `includeScopeStyles: false`: use this preset or define your own `::highlight(...)` rules.
|
package/package.json
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-it-renderer-fence",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "A markdown-it fence renderer with code/samp output, line features, Custom Highlight API support, and optional themes.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
8
8
|
"./custom-highlight": "./src/entry/custom-highlight.js",
|
|
9
9
|
"./custom-highlight-runtime": "./src/entry/custom-highlight-runtime.js",
|
|
10
|
-
"./markup-highlight": "./src/entry/markup-highlight.js"
|
|
10
|
+
"./markup-highlight": "./src/entry/markup-highlight.js",
|
|
11
|
+
"./theme/rf-basic": "./theme/rf-basic/index.js",
|
|
12
|
+
"./theme/rf-basic.css": "./theme/rf-basic/index.css",
|
|
13
|
+
"./theme/rf-basic/*": "./theme/rf-basic/*",
|
|
14
|
+
"./theme/rf-monochrome": "./theme/rf-monochrome/index.js",
|
|
15
|
+
"./theme/rf-monochrome.css": "./theme/rf-monochrome/index.css",
|
|
16
|
+
"./theme/rf-monochrome/*": "./theme/rf-monochrome/*"
|
|
11
17
|
},
|
|
12
18
|
"files": [
|
|
13
19
|
"index.js",
|
|
14
20
|
"src",
|
|
21
|
+
"docs",
|
|
22
|
+
"theme",
|
|
15
23
|
"LICENSE",
|
|
16
24
|
"README.md"
|
|
17
25
|
],
|
|
@@ -23,28 +31,30 @@
|
|
|
23
31
|
]
|
|
24
32
|
},
|
|
25
33
|
"scripts": {
|
|
26
|
-
"test": "node test/test.js && node test/custom-highlight/provider-contract.test.js && node test/custom-highlight/provider-
|
|
34
|
+
"test": "node test/test.js && node test/custom-highlight/provider-contract.test.js && node test/theme/contrast-check.js && node test/custom-highlight/provider-role-coverage.test.js && node test/custom-highlight/provider-role-holdout-parity.test.js && node -e \"console.log('Passed all test.')\"",
|
|
27
35
|
"test:provider:contract": "node test/custom-highlight/provider-contract.test.js",
|
|
28
|
-
"test:provider:
|
|
29
|
-
"test:provider:
|
|
30
|
-
"test:provider:
|
|
36
|
+
"test:provider:role": "node test/custom-highlight/provider-role-coverage.test.js",
|
|
37
|
+
"test:provider:role:holdout": "node test/custom-highlight/provider-role-holdout-parity.test.js",
|
|
38
|
+
"test:provider:role:mismatch": "node test/custom-highlight/provider-role-mismatch-report.js",
|
|
31
39
|
"test:provider:matrix": "node test/custom-highlight/provider-matrix-audit.js",
|
|
32
|
-
"build:demo:provider:
|
|
40
|
+
"build:demo:provider:role": "node test/custom-highlight/build-provider-role-demos.js",
|
|
33
41
|
"build:example:custom-highlight": "node example/build-custom-highlight-compare.js",
|
|
42
|
+
"build:example:monochrome-highlight": "node example/build-monochrome-highlight-compare.js",
|
|
34
43
|
"test:performance": "node test/performance/benchmark.js",
|
|
35
44
|
"test:performance:highlighter": "node test/performance/highlighter-benchmark.js",
|
|
36
45
|
"test:performance:provider-modes": "node test/performance/provider-mode-benchmark.js",
|
|
37
46
|
"test:performance:runtime": "node test/performance/runtime-apply-benchmark.js",
|
|
38
|
-
"test:performance:
|
|
47
|
+
"test:performance:role": "node test/performance/provider-mode-benchmark.js",
|
|
48
|
+
"test:theme:contrast": "node test/theme/contrast-check.js"
|
|
39
49
|
},
|
|
40
50
|
"author": "peaceroad <peaceroad@gmail.com>",
|
|
41
51
|
"repository": "https://github.com/peaceroad/p7d-markdown-it-renderer-fence",
|
|
42
52
|
"license": "MIT",
|
|
43
53
|
"devDependencies": {
|
|
44
|
-
"@peaceroad/markdown-it-figure-with-p-caption": "^0.
|
|
54
|
+
"@peaceroad/markdown-it-figure-with-p-caption": "^0.18.0",
|
|
45
55
|
"highlight.js": "^11.11.1",
|
|
46
56
|
"markdown-it": "^14.1.1",
|
|
47
57
|
"markdown-it-attrs": "^4.3.1",
|
|
48
|
-
"shiki": "^4.0
|
|
58
|
+
"shiki": "^4.1.0"
|
|
49
59
|
}
|
|
50
60
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const warnedOptionIssues = new Set()
|
|
2
2
|
|
|
3
3
|
const isWarnEnabled = () => {
|
|
4
|
-
if (typeof process === 'undefined' || !process || !process.env) return
|
|
4
|
+
if (typeof process === 'undefined' || !process || !process.env) return false
|
|
5
5
|
const mode = process.env.NODE_ENV
|
|
6
6
|
return mode === 'development'
|
|
7
7
|
}
|
|
@@ -20,7 +20,7 @@ const validProviderSet = new Set(['shiki', 'hljs', 'custom'])
|
|
|
20
20
|
const validFallbackSet = new Set(['plain', 'markup'])
|
|
21
21
|
const validTransportSet = new Set(['env', 'inline-script'])
|
|
22
22
|
const validLineFeatureStrategySet = new Set(['hybrid', 'disable'])
|
|
23
|
-
const validShikiScopeModeSet = new Set(['auto', 'color', 'semantic', '
|
|
23
|
+
const validShikiScopeModeSet = new Set(['auto', 'color', 'semantic', 'role'])
|
|
24
24
|
const knownCustomHighlightKeys = new Set([
|
|
25
25
|
'provider',
|
|
26
26
|
'getRanges',
|
|
@@ -32,9 +32,9 @@ const knownCustomHighlightKeys = new Set([
|
|
|
32
32
|
'scopePrefix',
|
|
33
33
|
'includeScopeStyles',
|
|
34
34
|
'shikiScopeMode',
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
35
|
+
'shikiRoleClassifier',
|
|
36
|
+
'shikiRoleLangResolver',
|
|
37
|
+
'shikiRoleLangAliases',
|
|
38
38
|
'highlighter',
|
|
39
39
|
'hljsHighlight',
|
|
40
40
|
'highlight',
|