@peaceroad/markdown-it-renderer-fence
v0.8.0
Published
A markdown-it fence renderer with code/samp output, line features, Custom Highlight API support, and optional themes.
Readme
p7d-markdown-it-renderer-fence
A markdown-it fence renderer for safer <pre><code> / <pre><samp> output, line features, optional syntax highlighting, and advanced Custom Highlight API integration.
Default mode is markup. Custom Highlight API mode is available for advanced browser-runtime highlighting.
Install
npm i @peaceroad/markdown-it-renderer-fence markdown-it markdown-it-attrsInstall a highlighter separately when you need syntax highlighting, for example highlight.js or shiki.
Quick Start (Markup Mode)
import MarkdownIt from 'markdown-it'
import markdownItAttrs from 'markdown-it-attrs'
import hljs from 'highlight.js'
import rendererFence from '@peaceroad/markdown-it-renderer-fence/markup-highlight'
const md = MarkdownIt({
html: true,
langPrefix: 'language-',
highlight: (code, lang) => {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(code, { language: lang }).value
} catch (e) {}
}
return md.utils.escapeHtml(code)
},
})
.use(markdownItAttrs)
.use(rendererFence)Shiki in Markup Mode
Preload the languages you render, then return Shiki HTML from md.options.highlight.
import MarkdownIt from 'markdown-it'
import markdownItAttrs from 'markdown-it-attrs'
import { createHighlighter } from 'shiki'
import rendererFence from '@peaceroad/markdown-it-renderer-fence/markup-highlight'
const highlighter = await createHighlighter({
themes: ['github-light'],
langs: ['javascript', 'typescript', 'json', 'text'],
})
const md = MarkdownIt({
html: true,
langPrefix: 'language-',
highlight: (code, lang) => {
try {
return highlighter.codeToHtml(code, { lang: lang || 'text', theme: 'github-light' })
} catch (e) {
return md.utils.escapeHtml(code)
}
},
})
.use(markdownItAttrs)
.use(rendererFence)Entry Points
| Import | Purpose |
| --- | --- |
| @peaceroad/markdown-it-renderer-fence | compatibility dispatcher; highlightRenderer selects mode |
| @peaceroad/markdown-it-renderer-fence/markup-highlight | markup-only renderer path |
| @peaceroad/markdown-it-renderer-fence/custom-highlight | Custom Highlight API renderer + payload/runtime helpers |
| @peaceroad/markdown-it-renderer-fence/custom-highlight-runtime | browser runtime only (applyCustomHighlights, observeCustomHighlights, clearCustomHighlights) |
| @peaceroad/markdown-it-renderer-fence/theme/rf-basic | color preset Shiki theme object and API provider helpers |
| @peaceroad/markdown-it-renderer-fence/theme/rf-basic.css | complete color preset CSS |
| @peaceroad/markdown-it-renderer-fence/theme/rf-monochrome | one-color Shiki markup theme object |
| @peaceroad/markdown-it-renderer-fence/theme/rf-monochrome.css | complete one-color markup preset CSS |
Use @peaceroad/markdown-it-renderer-fence/theme/rf-basic for JavaScript helpers. CSS assets are exposed as CSS subpaths.
Split preset CSS is also available:
theme/rf-basic/base.csstheme/rf-basic/api/highlightjs.csstheme/rf-basic/api/shiki.csstheme/rf-basic/markup/shiki.csstheme/rf-basic/markup/highlightjs.csstheme/rf-monochrome.csstheme/rf-monochrome/markup/highlightjs.csstheme/rf-monochrome/markup/shiki.css
The plugin does not auto-load CSS or browser runtime JavaScript.
Features
- markup mode with highlighter-owned HTML (
md.options.highlight) <samp>rendering forsamp,shell, andconsolefence languages- per-fence
{samp}/{code}tag overrides - line numbers via
start/line-number-start - advanced line-number controls via
line-number-skip/line-number-set - emphasized lines via
em-lines/emphasize-lines - optional long-line spacer via
lineEndSpanThreshold - optional pre-wrap via
{wrap}/{pre-wrap} - comment markers via
comment-mark - sidecar line notes via an immediate
line-notesfence (notesalias) - advanced Custom Highlight API mode for span-free code text plus browser-applied ranges
- optional first-party theme CSS and Shiki/provider helper exports
Fence Attribute Examples
```js {start="1" em-lines="2"}
const a = 1
console.log(a)
``````bash {samp comment-mark="#"}
# setup
export NODE_ENV=test
``````shell {code}
# render as <code> even though shell is normally sampLang
``````js {start="5"}
const a = 1
console.log(a)
```
```line-notes
1: setup {width="7em"}
2: result {width="10em"}
```For emitted HTML contracts and visual samples, see the files listed in Docs and Examples.
Main Options
Markup / shared options
| Option | Summary |
| --- | --- |
| attrsOrder | output attribute order; supports data-* wildcard |
| setHighlight | call md.options.highlight when available |
| setLineNumber | enable line wrapping when start / line-number-start is valid |
| setEmphasizeLines | enable em-lines / emphasize-lines |
| lineEndSpanThreshold | append line-end spacer spans for visually long lines |
| setLineEndSpan | alias of lineEndSpanThreshold |
| lineEndSpanClass | CSS class for line-end spacer spans |
| setPreWrapStyle | inject inline pre-wrap style for wrap-enabled blocks |
| sampLang | comma-separated languages rendered as <samp>; default: shell,console |
| {samp} / {code} | per-fence tag override attrs |
| langPrefix | language class prefix; defaults to md.options.langPrefix || 'language-' |
| useHighlightPre | preserve highlighter-owned <pre><code> wrappers; disables line-splitting features |
| onFenceDecision | debug hook for per-fence branch decisions |
| onFenceDecisionTiming | include timing fields in onFenceDecision payloads |
Line features fail closed when highlighter output line counts do not match source line counts. In useHighlightPre passthrough, line-splitting features and <samp> conversion are intentionally skipped.
Custom Highlight API options
| Option | Summary |
| --- | --- |
| highlightRenderer | dispatcher mode: markup, api, or custom-highlight-api |
| customHighlight.provider | shiki, hljs, or custom |
| customHighlight.highlighter | Shiki highlighter with synchronous codeToTokens |
| customHighlight.hljsHighlight | highlight.js-style function for the hljs provider |
| customHighlight.getRanges | synchronous custom range provider |
| customHighlight.theme | Shiki theme name or { light, dark, default? } |
| customHighlight.shikiScopeMode | auto, color, semantic, or role |
| customHighlight.includeScopeStyles | include payload scopeStyles when available |
| customHighlight.transport | env or inline-script |
| customHighlight.fallback | provider-error fallback: plain or markup |
| customHighlight.lineFeatureStrategy | hybrid or disable |
For detailed API-mode styling, runtime, role-mode, and payload guidance, see Custom Highlight Styling Guide and Code Highlighting Design.
Custom Highlight API Mode
API mode renders escaped code text and emits payload ranges. It does not activate browser highlights by itself; call the runtime in the browser.
import MarkdownIt from 'markdown-it'
import markdownItAttrs from 'markdown-it-attrs'
import { createHighlighter } from 'shiki'
import rendererFenceApi, {
applyCustomHighlights,
renderCustomHighlightPayloadScript,
} from '@peaceroad/markdown-it-renderer-fence/custom-highlight'
import {
createRfBasicShikiCustomHighlightOptions,
rfBasicShikiTheme,
rfBasicThemeName,
} from '@peaceroad/markdown-it-renderer-fence/theme/rf-basic'
const highlighter = await createHighlighter({
themes: [rfBasicShikiTheme],
langs: ['javascript', 'typescript', 'json'],
})
const md = MarkdownIt({ html: true })
.use(markdownItAttrs)
.use(rendererFenceApi, {
customHighlight: createRfBasicShikiCustomHighlightOptions({
highlighter,
theme: rfBasicThemeName,
shikiScopeMode: 'role',
includeScopeStyles: false,
transport: 'env',
}),
})
const env = {}
const html = md.render('```js
const x = 1
```', env)
const payloadScript = renderCustomHighlightPayloadScript(env)
// Browser side:
applyCustomHighlights(document)For lazy apply and color-scheme watching:
import { observeCustomHighlights } from '@peaceroad/markdown-it-renderer-fence/custom-highlight-runtime'
observeCustomHighlights(document, {
applyOptions: { colorScheme: 'auto', incremental: true },
watchColorScheme: true,
})Use markup mode instead when the target page cannot run runtime JavaScript.
Theme
The optional preset theme is a package artifact, not an automatically loaded dependency.
@import "@peaceroad/markdown-it-renderer-fence/theme/rf-basic.css";The color preset covers base code blocks, samp, line features, highlight.js markup classes, Shiki API scopes, and highlight.js API scopes.
For one-color print/monochrome output, use markup mode and import the opt-in monochrome override:
@import "@peaceroad/markdown-it-renderer-fence/theme/rf-monochrome.css";For theme design details, see theme/rf-basic/README.md and theme/rf-monochrome/README.md.
Helper Exports
From @peaceroad/markdown-it-renderer-fence/custom-highlight:
applyCustomHighlightsobserveCustomHighlightsclearCustomHighlightsshouldRuntimeFallbackgetCustomHighlightPayloadMaprenderCustomHighlightPayloadScriptrenderCustomHighlightScopeStyleTagcustomHighlightPayloadSchemaVersioncustomHighlightPayloadSupportedVersions
From @peaceroad/markdown-it-renderer-fence/theme/rf-basic:
rfBasicShikiThemerfBasicThemeNamecreateRfBasicShikiThemecreateRfBasicShikiCustomHighlightOptionscreateRfBasicHighlightjsCustomHighlightOptionsrfBasicSyntaxCssVarscreateRfBasicShikiRoleScopeColorVars
From @peaceroad/markdown-it-renderer-fence/theme/rf-monochrome:
rfMonochromeShikiThemerfMonochromeShikiThemeNamerfMonochromeThemeNamecreateRfMonochromeShikiTheme
Docs and Examples
- Docs overview
- Code Highlighting Design
- Custom Highlight Styling Guide
- rf-basic Theme README
- rf-monochrome Theme README
- Example README
example/custom-highlight-provider-matrix.htmlexample/monochrome-highlight-compare.htmlexample/line-number-sample.htmlexample/line-notes-sample.htmlexample/samp-sample.html
Examples are demonstration and verification assets. Do not import runtime or theme helpers from example/; use the public package entry points above.
Tests and Benchmarks
npm test
npm run test:provider:contract
npm run test:provider:role
npm run test:provider:role:holdout
npm run test:provider:matrix
npm run test:performance
npm run test:performance:runtimeLicense
MIT (LICENSE)
