[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-94408":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":15,"starSnapshotCount":15,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},94408,"rangi","pi0\u002Frangi","pi0","🎨 Tiny Syntax Highlighter","",null,"TypeScript",115,1,104,0,3,41.2,"Other",false,"main",true,[],"2026-08-24 04:01:22","# 🎨 rangi\n\n![The 24 bundled themes, stacked: each card is a theme name and its full palette](.\u002Fdocs\u002Fthemes-hero.svg)\n\n- **Tiny** \u003Csmall>(**~13.2kB** min+gzip with every language and the default themes bundled, **~1.5kB** for `codeToHtml` from [`rangi\u002Fcore`](#core))\u003C\u002Fsmall>\n- **Fast** \u003Csmall>(outperforms other highlighters in our benchmarks)\u003C\u002Fsmall>\n- **Simple** \u003Csmall>(zero dependencies, fully synchronous, no stylesheet to load by default, and no global registry)\u003C\u002Fsmall>\n- **Complete** \u003Csmall>(**46 languages**, 25 themes, language detection, terminal output, and raw tokens for custom rendering)\u003C\u002Fsmall>\n\n### Quick Start 🚀\n\n```bash\nnpx nypm i rangi\n```\n\nHighlight a string and get self-contained HTML:\n\n```js\nimport { codeToHtml } from \"rangi\";\n\nhtml = codeToHtml('console.log(\"hello\")', { lang: \"js\" });\n```\n\nTheme colors are inlined as `style` attributes, so the result needs no stylesheet, client-side JavaScript, or hydration. It works the same whether you use it in Node.js, a browser, a worker, or a template.\n\n```js\nimport { codeToHtml } from \"rangi\";\nimport { githubDark, githubLight } from \"rangi\u002Fthemes\";\n\n\u002F\u002F Use a specific theme or a light\u002Fdark pair\ncodeToHtml(code, { lang: \"js\", theme: githubDark });\ncodeToHtml(code, { lang: \"js\", theme: { light: githubLight, dark: githubDark } });\n\n\u002F\u002F Return an inline `\u003Ccode>` element instead of a block (blocks are `multiline`\n\u002F\u002F when the code contains a line break and `oneline` otherwise)\ncodeToHtml(code, { lang: \"js\", inline: true });\n\n\u002F\u002F Hide the line-number gutter\ncodeToHtml(code, { lang: \"js\", lineNumbers: false });\n```\n\n`highlightText(code, opt)` returns only the contents of the block: its tokens and line numbers. Use it when you already have a wrapper element.\n\nDetect the language automatically:\n\n```js\nimport { codeToHtml, detectLanguage } from \"rangi\";\n\ncodeToHtml(code, { lang: detectLanguage(code) });\n```\n\nIt scores the code against every language it knows and returns the best match, or `plain` when nothing scores high enough. Give it the whole document: some languages are told apart by how they open and close — json has no keyword to go on and is recognised by being a single `{`, `[` or `\"` value — so a snippet cut out of the middle scores lower than the same code entire.\n\nEvery language is bundled and ready to use, with nothing to preload. All functions return their results directly instead of promises. Unknown languages fall back to plain text rather than throwing an error. To bundle only the languages you use, choose [`rangi\u002Fcore`](#core).\n\nPass a custom language directly to the call that needs it instead of registering it globally. Custom languages take precedence over bundled languages, so they can override them and are also available to sub-languages:\n\n```js\nimport { codeToHtml } from \"rangi\";\n\ncodeToHtml(code, { lang: \"mine\", languages: { custom: customLanguage } });\n```\n\nA language is an array of rules. If those rules are a module's default export, `import * as mine from \".\u002Fmine.js\"` works too.\n\n## Terminal\n\nHighlight a file from the command line:\n\n```bash\nnpx rangi src\u002Findex.ts\n```\n\n```js\nimport { printHighlight } from \"rangi\";\nimport { atomDark } from \"rangi\u002Fthemes\";\n\nprintHighlight('console.log(\"hello\")', { lang: \"js\", theme: atomDark });\n```\n\nEvery theme with real colors works in the terminal, where they are emitted as 24-bit escape sequences. For a light\u002Fdark pair, the terminal uses the dark theme. `codeToAnsi` returns the highlighted string instead of printing it. Colors a terminal cannot be given—anything that is not hex, such as the custom properties of [`cssVariables`](#css-variables)—leave their tokens uncolored rather than producing broken escape sequences.\n\n## Core\n\nThe main entry point bundles every language and both default themes, so they are ready to use. `rangi\u002Fcore` provides the same API with **nothing bundled**. Its `languages` and `theme` options are required, so your bundle includes only what you provide.\n\nEach bundled grammar is available as a named export from `rangi\u002Flanguages`, so your bundle includes only the ones you import:\n\n```js\nimport { codeToHtml } from \"rangi\u002Fcore\";\nimport { js, ts } from \"rangi\u002Flanguages\";\nimport { githubDark } from \"rangi\u002Fthemes\";\n\ncodeToHtml(code, { lang: \"js\", languages: { js, ts }, theme: githubDark });\n```\n\nExport names match their language keys, so you can pass `{ js, ts }` directly as the `languages` option. Add a custom grammar alongside them as `{ js, custom }`, just as you would with the main entry point. If you need every grammar through the core API, import the full `languages` object used by the main entry point.\n\nThe highlighter uses the grammars provided in `languages` for both the selected language and any sub-languages it needs. Nothing is registered globally or loaded implicitly: `languages: {}` applies no highlighting, and unknown languages fall back to plain text instead of throwing an error.\n\nKeep in mind that **grammars can delegate to other grammars**. If you omit a required grammar, its region remains unhighlighted:\n\n```js\nimport { js, js_template_literals, jsdoc, regex, todo } from \"rangi\u002Flanguages\";\n\n\u002F\u002F `js` on its own: the code is highlighted, but template literals and\n\u002F\u002F comments come back as plain text\ntokenize(code, { lang: \"js\", languages: { js } });\n\n\u002F\u002F Complete JavaScript highlighting\ntokenize(code, { lang: \"js\", languages: { js, jsdoc, js_template_literals, regex, todo } });\n```\n\nThe core entry exports the same functions as the main entry.\n\n## Languages supported 🌐\n\n| Name    | Aliases                     | Language detection |\n| ------- | --------------------------- | ------------------ |\n| asm     |                             | ✅                 |\n| astro   |                             | ✅                 |\n| bash    | sh, shell, zsh              | ✅                 |\n| c       | h                           | ✅                 |\n| cpp     | cc, cxx, hpp                | ✅                 |\n| cs      | csharp                      | ✅                 |\n| css     |                             | ✅                 |\n| csv     |                             |                    |\n| dart    |                             | ✅                 |\n| diff    | patch                       | ✅                 |\n| docker  | dockerfile                  | ✅                 |\n| go      | golang                      | ✅                 |\n| graphql | gql                         | ✅                 |\n| html    | htm                         | ✅                 |\n| http    |                             | ✅                 |\n| ini     |                             |                    |\n| java    |                             | ✅                 |\n| js      | javascript, mjs, cjs        | ✅                 |\n| jsdoc   |                             |                    |\n| json    | jsonc, json5, jsonl, ndjson | ✅                 |\n| jsx     |                             | ✅                 |\n| kt      | kotlin, kts                 | ✅                 |\n| less    |                             | ✅                 |\n| log     |                             |                    |\n| lua     |                             | ✅                 |\n| make    | makefile, mk                | ✅                 |\n| md      | markdown                    | ✅                 |\n| php     |                             | ✅                 |\n| pl      | perl                        | ✅                 |\n| plain   | text, txt                   |                    |\n| ps1     | powershell, pwsh            | ✅                 |\n| py      | python                      | ✅                 |\n| rb      | ruby                        | ✅                 |\n| regex   |                             |                    |\n| rs      | rust                        | ✅                 |\n| scss    |                             | ✅                 |\n| sql     |                             | ✅                 |\n| svelte  |                             | ✅                 |\n| swift   |                             | ✅                 |\n| toml    |                             |                    |\n| ts      | typescript, mts, cts        | ✅                 |\n| tsx     |                             | ✅                 |\n| uri     | url                         | ✅                 |\n| vue     |                             | ✅                 |\n| xml     | svg                         | ✅                 |\n| yaml    | yml                         | ✅                 |\n\nAn alias is the same grammar under another name, so it works everywhere the name itself does — as the `lang` option, as the language of a markdown code fence, and as a named export of `rangi\u002Flanguages`:\n\n```js\ncodeToHtml(code, { lang: \"yml\" }); \u002F\u002F the same as `lang: \"yaml\"`\n\nimport { python } from \"rangi\u002Flanguages\"; \u002F\u002F the `py` grammar itself\n```\n\n## Themes 🌈\n\nA theme is a plain object that assigns a color to each token type. The same object powers both inline styles and terminal output.\n\nBy default, rangi uses its **two bundled themes**: `default` for light mode and `dark` for dark mode. Their colors are inlined with [`light-dark()`][light-dark], so each code block automatically follows the reader's color scheme. These are the only themes included by the main entry point.\n\nAll other themes are available from `rangi\u002Fthemes` and are included in your bundle only when you import them:\n\n```js\nimport { codeToHtml } from \"rangi\";\nimport { githubDark } from \"rangi\u002Fthemes\";\n\ncodeToHtml(code, { lang: \"js\", theme: githubDark });\n```\n\n![Every bundled theme, as a highlighted sample and a swatch per token type](.\u002Fdocs\u002Fthemes.svg)\n\n| Name                | Export              |\n| ------------------- | ------------------- |\n| default             | `defaultTheme`      |\n| dark                | `dark`              |\n| atom-dark           | `atomDark`          |\n| catppuccin-latte    | `catppuccinLatte`   |\n| catppuccin-mocha    | `catppuccinMocha`   |\n| css-variables       | `cssVariables`      |\n| dracula             | `dracula`           |\n| everforest-dark     | `everforestDark`    |\n| everforest-light    | `everforestLight`   |\n| geist-dark          | `geistDark`         |\n| geist-light         | `geistLight`        |\n| github-dark         | `githubDark`        |\n| github-dim          | `githubDim`         |\n| github-light        | `githubLight`       |\n| gruvbox-dark        | `gruvboxDark`       |\n| gruvbox-light       | `gruvboxLight`      |\n| monokai             | `monokai`           |\n| night-owl           | `nightOwl`          |\n| nord                | `nord`              |\n| one-light           | `oneLight`          |\n| solarized-dark      | `solarizedDark`     |\n| solarized-light     | `solarizedLight`    |\n| tokyo-night         | `tokyoNight`        |\n| vesper              | `vesper`            |\n| visual-studio-dark  | `visualStudioDark`  |\n| vscode-dark-modern  | `vscodeDarkModern`  |\n| vscode-light-modern | `vscodeLightModern` |\n\nEach theme is a named export.\n\nAny two themes can be passed together as a `{ light, dark }` pair, which is inlined with `light-dark()` and follows the reader's color scheme just like the default. Every family with both a light and a dark theme is also exported as a ready-made pair: `geist`, `catppuccin`, `everforest`, `github`, `gruvbox`, `solarized`, and `vscodeModern`.\n\n```js\nimport { codeToHtml } from \"rangi\";\nimport { geist } from \"rangi\u002Fthemes\";\n\n\u002F\u002F same as { light: geistLight, dark: geistDark }\ncodeToHtml(code, { lang: \"js\", theme: geist });\n```\n\nA terminal has no color scheme to follow, so `codeToAnsi` reads a pair as its dark theme.\n\nA custom theme is simply an object:\n\n```js\ncodeToHtml(code, {\n  lang: \"js\",\n  theme: {\n    name: \"my-theme\",\n    scheme: \"dark\",\n    bg: \"#000\",\n    fg: \"#fff\",\n    tokens: { kwd: \"#f92672\", str: \"#e6db74\", cmnt: \"#75715e\" \u002F* … *\u002F },\n  },\n});\n```\n\n### CSS variables\n\nThe `cssVariables` theme colors nothing itself. Every slot is a custom property, so the markup stays self-contained—the layout, the font, and the box are still inlined—while the palette resolves from your stylesheet:\n\n```js\nimport { codeToHtml } from \"rangi\";\nimport { cssVariables } from \"rangi\u002Fthemes\";\n\ncodeToHtml(\"const a = 1\", { lang: \"js\", theme: cssVariables });\n\u002F\u002F \u003Cdiv … style=\"…;background:var(--shj-bg);color:var(--shj-fg);…\">\n\u002F\u002F   \u003Cspan style=\"color:var(--shj-kwd)\">const\u003C\u002Fspan> a …\n```\n\nDefine the properties wherever you like—on `:root`, on a container, or per code block—and every block on the page follows:\n\n```css\n:root {\n  --shj-bg: #0d1117;\n  --shj-fg: #e6edf3;\n  --shj-numbers: #8b949e; \u002F* falls back to --shj-cmnt *\u002F\n\n  --shj-kwd: #ff7b72;\n  --shj-oper: #ff7b72;\n  --shj-esc: #ff7b72;\n  --shj-deleted: #ffa198;\n  --shj-err: #ffa198;\n  --shj-class: #ffa657;\n  --shj-cmnt: #8b949e;\n  --shj-bracket: #8b949e;\n  --shj-num: #79c0ff;\n  --shj-bool: #79c0ff;\n  --shj-type: #79c0ff;\n  --shj-section: #79c0ff;\n  --shj-var: #79c0ff;\n  --shj-str: #a5d6ff;\n  --shj-func: #d2a8ff;\n  --shj-insert: #7ee787;\n}\n```\n\nA property you leave undefined is not an error: the declaration is simply dropped and the token inherits the block's `--shj-fg`. Because the values are resolved by the browser, this theme is the one way to switch palettes—media queries, a `data-theme` attribute, a class on `\u003Chtml>`—without re-running the highlighter. It is also the one theme that cannot color a terminal.\n\nFor markup with no `style` attribute at all, see [CSS classes](#css-classes) instead.\n\n[light-dark]: https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002Fcolor_value\u002Flight-dark\n\n## CSS classes\n\nPass `classes: true` to emit class names instead of inline styles. The output then carries **no `style` attribute anywhere**, which makes it the smallest markup rangi can produce and hands every decision to your stylesheet:\n\n```js\nimport { codeToHtml } from \"rangi\";\n\ncodeToHtml(\"const a = 1\", { lang: \"js\", classes: true });\n\u002F\u002F \u003Cdiv class=\"shj shj-lang-js shj-oneline\" data-lang=\"js\">\n\u002F\u002F   \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> a\n\u002F\u002F   \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1\u003C\u002Fspan>\n\u002F\u002F \u003C\u002Fdiv>\n```\n\nThe `theme` option is unused in this mode, and `rangi\u002Fcore` stops requiring one:\n\n```js\nimport { codeToHtml } from \"rangi\u002Fcore\";\nimport { js } from \"rangi\u002Flanguages\";\n\ncodeToHtml(code, { lang: \"js\", languages: { js }, classes: true });\n```\n\n**Nothing is styled until you supply the CSS**—including `white-space: pre`, without which the code collapses onto one line. These are all the class names emitted:\n\n| Class                                      | Element                                                          |\n| ------------------------------------------ | ---------------------------------------------------------------- |\n| `shj`                                      | Every block, inline or not                                       |\n| `shj-lang-\u003Clang>`                          | The language, escaped                                            |\n| `shj-inline` `shj-oneline` `shj-multiline` | The display mode                                                 |\n| `shj-scroll`                               | The scroll container of a multiline block                        |\n| `shj-numbers`                              | The line-number gutter, one `\u003Cdiv>` per line                     |\n| `shj-code`                                 | The code beside the gutter                                       |\n| `shj-\u003Ctype>`                               | One per token type, e.g. `shj-kwd` (see [Tokenizer](#tokenizer)) |\n\nThis stylesheet reproduces the default appearance. Drop the palette and keep the structure if you only want your own colors:\n\n```css\n.shj {\n  white-space: pre;\n  box-sizing: border-box;\n  max-width: min(100%, 100vw);\n  font:\n    normal 18px Consolas,\n    \"Courier New\",\n    Monaco,\n    \"Andale Mono\",\n    \"Ubuntu Mono\",\n    monospace;\n  line-height: 24px;\n  color-scheme: light dark;\n  background: light-dark(#fff, #1a1a1c);\n  color: light-dark(#112, #f8f8f2);\n  box-shadow: 0 0 5px #0001;\n  text-shadow: none;\n}\n.shj-inline {\n  display: inline-block;\n  margin: 0;\n  padding: 2px 5px;\n  border-radius: 5px;\n}\n.shj-oneline,\n.shj-multiline {\n  margin: 10px 0;\n  border-radius: 10px;\n}\n.shj-oneline {\n  padding: 12px 10px;\n}\n.shj-multiline {\n  padding: 30px 20px;\n}\n\n.shj-scroll {\n  display: flex;\n  overflow: auto;\n}\n.shj-numbers {\n  padding-left: 5px;\n  padding-right: 10px;\n  text-align: right;\n  opacity: 0.5;\n  user-select: none;\n  color: light-dark(#999, #7d828b);\n}\n.shj-code {\n  flex: 1;\n  outline: none;\n}\n\n.shj-deleted {\n  color: light-dark(#f44, #ff5261);\n}\n.shj-err {\n  color: light-dark(#e16, #ff5261);\n}\n.shj-var {\n  color: light-dark(#f44, #ff5261);\n}\n.shj-section {\n  color: light-dark(#84f, #ff7cc6);\n}\n.shj-kwd {\n  color: light-dark(#e16, #ff7cc6);\n}\n.shj-class {\n  color: light-dark(#f60, #eab07c);\n}\n.shj-insert {\n  color: light-dark(#7d8, #71d58a);\n}\n.shj-type {\n  color: light-dark(#5af, #71d58a);\n}\n.shj-func {\n  color: light-dark(#84f, #71d58a);\n}\n.shj-bool {\n  color: light-dark(#3bf, #71d58a);\n}\n.shj-num {\n  color: light-dark(#f60, #b581fd);\n}\n.shj-oper {\n  color: light-dark(#5af, #80c6ff);\n}\n.shj-str {\n  color: light-dark(#7d8, #4dacfa);\n}\n.shj-cmnt {\n  color: light-dark(#999, #7d828b);\n  font-style: italic;\n}\n.shj-bracket {\n  color: light-dark(#999, #7d828b);\n}\n\n\u002F* the badge the inline-style mode gives a one-line HTTP request *\u002F\n.shj-lang-http.shj-oneline .shj-kwd {\n  background: #25f;\n  color: #fff;\n  padding: 5px 7px;\n  border-radius: 5px;\n}\n```\n\nTwo things the inline-style mode does for you have to be written down here, because they are conventions of that output rather than theme data: the italic on `.shj-cmnt`, and the HTTP method badge. There is no `.shj-esc` rule above for the same reason the bundled themes have no `esc` color—it inherits—but the class is emitted, so you can style it.\n\nThe markup is the same in both modes, so the two are interchangeable per call: render `classes: true` where a stylesheet is already loaded, and inline the theme in the one email or RSS body that cannot have one.\n\n## Tokenizer\n\n`tokenize(code, opt)` is the foundation of the other APIs. It returns raw tokens, giving you full control over how to render them—as JSX, other markup, or any format you need:\n\n```js\nimport { tokenize } from \"rangi\";\n\ntokenize(\"let a = 1\", { lang: \"js\" });\n\u002F\u002F [\n\u002F\u002F   { text: \"let\", type: \"kwd\" },\n\u002F\u002F   { text: \" a \" },\n\u002F\u002F   { text: \"=\", type: \"oper\" },\n\u002F\u002F   { text: \" \" },\n\u002F\u002F   { text: \"1\", type: \"num\" }\n\u002F\u002F ]\n```\n\nIt accepts the same `lang` and `languages` options as the other entry points. Tokens are returned in source order, and individual tokens are never empty. Their `text` is raw and unescaped, so joining the token text recreates the original input. Unmatched text has no `type`. An unknown language or invalid grammar returns the entire input as one untyped token instead of throwing an error.\n\nThe `type` is one of `deleted`, `err`, `var`, `section`, `kwd`, `class`, `cmnt`, `insert`, `type`, `func`, `bool`, `num`, `oper`, `str`, `esc`, and `bracket`. These are the same keys that a [theme](#themes-) uses to assign colors. Themes do not define italics for `cmnt`; that styling is an HTML output convention.\n\n**A token may span multiple lines.** A block comment, template literal, or plain-text segment remains a single token regardless of how many lines it covers. To render line by line, tokenize the complete code **once**, then split the tokens. Tokenizing each line separately silently breaks constructs that cross line boundaries:\n\n```js\n\u002F\u002F ✗ Tokenize each line in isolation\n\"const a = 1; \u002F* multi\\nline *\u002F\".split(\"\\n\").map((line) => tokenize(line, { lang: \"js\" }));\n\n\u002F\u002F The second line becomes [{ text: \"line \" }, { text: \"*\u002F\", type: \"oper\" }].\n\u002F\u002F The comment is lost, and `*\u002F` is interpreted as an operator.\n\n\u002F\u002F ✓ Tokenize once, then split the tokens\nconst lines = [[]];\nfor (const { text, type } of tokenize(code, { lang: \"js\" })) {\n  text.split(\"\\n\").forEach((part, i) => {\n    if (i) lines.push([]);\n    if (part) lines.at(-1).push({ text: part, type });\n  });\n}\n```\n\n## License 📄\n\n[MIT](.\u002FLICENSE)\n\nThis project is a fork of [Speed Highlight JS](https:\u002F\u002Fgithub.com\u002Fspeed-highlight\u002Fcore) by [matubu](https:\u002F\u002Fmathias.ninja) and its contributors. The original project is dedicated to the public domain under [CC0 1.0](https:\u002F\u002Fcreativecommons.org\u002Fpublicdomain\u002Fzero\u002F1.0\u002F), while this fork is distributed under the MIT license. See [LICENSE](.\u002FLICENSE) for details.\n\nThanks to [@kamikazechaser](https:\u002F\u002Fgithub.com\u002Fkamikazechaser) for donating the `rangi` package name on npm.\n","rangi 是一个轻量级、同步执行的语法高亮库，专为服务端与客户端通用场景设计。核心功能包括：支持 46 种编程语言的高亮、25 种内置主题（含明暗双模式）、自动语言检测、行号控制、内联样式输出（无需外部 CSS 或 JS），以及零依赖、无全局状态的极简架构；其最小化体积仅约 1.5kB（core 版本），所有操作同步完成，适用于静态站点生成、Markdown 渲染器、文档构建工具及终端文本处理等对性能与部署简洁性要求较高的场景。",2,"2026-08-08 02:30:05","CREATED_QUERY"]