A tiny, predictable parser for Markdown fenced code block info strings. It extracts language, attributes, and line highlights with zero dependencies.
- Language:
ts - Attributes:
key=valueand bare flags; quoted strings with escaping (\n,\t,\",\',\\) - Line highlights:
{1,3-5}and attribute aliaseshighlight="1,3-5",lines="1,3-5",hl="1,3-5" - Extras:
extractBlocks,parseBlocks,normalize,parseLineSpec
Note: GitHub (GFM) ignores everything after the language in the info string. {...} and attrs are meant for site generators (VitePress, Nuxt Content, Astro, rehype-pretty-code, etc.), not for github.com.
sample.md:
# axios
```js
// GET request
const response = await fetch('https://api.example.com/data');
```Script:
import { readFile } from 'node:fs/promises'
import { parseBlocks, normalize } from 'md-fence' // adjust import path if local
const md = await readFile('sample.md', 'utf8')
const blocks = parseBlocks(md)
const results = blocks.map(b => ({ info: b.info, meta: normalize(b.meta), code: b.code }))
console.log(JSON.stringify(results, null, 2))Output:
[
{
"info": "js",
"meta": { "lang": "js", "highlight": [], "attrs": {} },
"code": "// GET request\nconst response = await fetch('https://api.example.com/data');\n"
}
]Add metadata:
```ts {1,3-5} title="App.tsx" line-numbers
const a = 1
function foo() {}
foo()
bar()
baz()
```Normalized meta:
{ "lang": "ts", "title": "App.tsx", "lineNumbers": true, "highlight": [1,3,4,5], "attrs": {} }- Language: first bare token (e.g.
js,ts,bash) - Attributes:
key=value(numbers andtrue/falseare coerced)key(boolean flag →true)- Strings:
"..."or'...'with escapes:\n,\t,\",\',\\
- Line highlights:
- Curly:
{1,3-5} - Or attributes:
highlight="1,3-5",lines="1,3-5",hl="1,3-5"
- Curly:
parseInfo(info: string, opts?)- Returns
{ lang?: string; attrs: Record<string, string|number|boolean>; highlight: number[] } - Options:
{ lowerCaseKeys?: boolean }(defaulttrue),{ treatFirstBareAsLang?: boolean }(defaulttrue)
- Returns
extractBlocks(md: string)- Returns
Array<{ info: string; code: string }> - Supports backtick and tilde fences with matching lengths and up to 3 spaces indent
- Returns
parseBlocks(md: string, opts?)- Returns
Array<{ info: string; code: string; meta: ReturnType<typeof parseInfo> }>
- Returns
normalize(meta, options?)- Maps known keys and merges highlight aliases
- Default key map:
title→meta.titlefilename|file|name→meta.filenameline-numbers|linenumbers|linenos→meta.lineNumberspinned→meta.pinnedcollapsed→meta.collapsedhighlight|lines|hl→ merged intometa.highlight
- Returns:
{ lang?, title?, filename?, lineNumbers?, pinned?, collapsed?, highlight: number[], attrs: Record<string, string|number|boolean> }
parseLineSpec(value)"1,3-5"→[1,3,4,5]