Close up of The Starry Night by Vincent van Gogh (1889)
with examples of starry-night
over it
starry-night
[![Build][build-badge]][build] [![Coverage][coverage-badge]][coverage] [![Downloads][downloads-badge]][downloads] [![Size][size-badge]][size]
Syntax highlighting, like what GitHub uses to highlight code, but free and open source and JavaScript!
Contents
- What is this?
- When should I use this?
- What is
PrettyLights
? - Install
- Use
- API
- Examples
- Syntax tree
- CSS
- Languages
- Types
- Compatibility
- Security
- Related
- Contribute
- License
What is this?
This package is an open source version of GitHub’s closed-source PrettyLights
project (more on that later).
It supports 600+ grammars and its extremely high quality.
It uses TextMate grammars which are also used in popular editors (SublimeText,
Atom, VS Code, &c).
They’re heavy but high quality.
When should I use this?
starry-night
is a high quality highlighter
(when your readers or authors are programmers, you want this!)
that can support tons of grammars
(from new things like MDX to much more!)
which approaches how GitHub renders code.
It has a WASM dependency, and rather big grammars, which means that
starry-night
might be too heavy particularly in browsers, in which case
[lowlight
][lowlight] or [refractor
][refractor] might be more suitable.
This project is similar to the excellent [shiki
][shiki], and it uses the same
underlying dependencies, but starry-night
is meant to match GitHub in that it
produces classes and works with the CSS it ships, making it easier to add dark
mode and other themes with CSS compared to inline styles.
Finally, this package produces objects (an AST), which makes it useful when you
want to perform syntax highlighting in a place where serialized HTML wouldn’t
work or wouldn’t work well.
For example, when you want to show code in a CLI by rendering to ANSI sequences,
when you’re using virtual DOM frameworks (such as React or Preact) so that
diffing can be performant, or when you’re working with [hast
][hast] or
[rehype
][rehype].
Bundled, minified, and gzipped, starry-night
and the WASM binary are 185 kB.
There are two lists of grammars you can use: [common
][api-common] (±35
languages, good for your own site) adds 250 kB and [all
][api-all] (~600
languages, useful if you are making a site like GitHub) is 1.6 MB.
You can also manually choose which grammars to include (or add to common
): a
language is typically between 3 and 5 kB.
To illustrate, Astro costs 2.1 kB and TSX costs 25.4 kB.
What is PrettyLights
?
PrettyLights
is the syntax highlighter that GitHub uses to turn this:
```markdown
# Hello, world!
```
…into this:
<span class="pl-mh"><span class="pl-mh">#</span><span class="pl-mh"> </span>Hello, world!</span>
…which is what starry-night
does too (some small differences in markup, but
essentially the same)!
PrettyLights
is responsible for taking the flag markdown
, looking it up in
[languages.yml
][languages-yml] from github-linguist
to figure out that that
means markdown, taking a corresponding grammar (in this case
[wooorm/markdown-tm-language
][markdown-tm-language]),
doing some GPL magic in C,
and turning it into spans with classes.
GitHub is using PrettyLights
since December 2014, when it
[replaced Pygments
][ref-1].
They wanted to open source it, but [were unable][ref-2] due to licensing issues.
Recently ([Feb 2019][ref-3]?), GitHub has slowly started to move towards
TreeLights
, which is based on TreeSitter, and also closed source.
If TreeLights
includes a language (currently: C, C#, CSS, CodeQL, EJS, Elixir,
ERB, Gleam, Go, HTML, Java, JS, Nix, PHP, Python, RegEx, Ruby, Rust, TLA, TS),
that’ll be used, for everything else PrettyLights
is used.
starry-night
does what PrettyLights
does, not what TreeLights
does.
I’m hopeful that that will be open sourced in the future and we can mimic both.
[][ref]
Install
This package is [ESM only][esm]. In Node.js (version 16+), install with [npm][]:
npm install @wooorm/starry-night
In Deno with [esm.sh
][esmsh]:
import {common, createStarryNight} from 'https://esm.sh/@wooorm/starry-night@3'
In browsers with [esm.sh
][esmsh]:
<script type="module">
import {common, createStarryNight} from 'https://esm.sh/@wooorm/starry-night@3?bundle'
</script>
To get the CSS in browsers, do (see [CSS][] for more info):
<!-- This supports light and dark mode automatically. -->
<link rel="stylesheet" href="https://esm.sh/@wooorm/starry-night@3/style/both">
Use
import {common, createStarryNight} from '@wooorm/starry-night'
const starryNight = await createStarryNight(common)
const scope = starryNight.flagToScope('markdown')
const tree = starryNight.highlight('# hi', scope)
console.log(tree)
Yields:
{
type: 'root',
children: [
{
type: 'element',
tagName: 'span',
properties: {className: ['pl-mh']},
children: [
{type: 'text', value: '# '},
{
type: 'element',
tagName: 'span',
properties: {className: ['pl-en']},
children: [{type: 'text', value: 'hi'}]
}
]
}
]
}
API
This package exports the identifiers [all
][api-all],
[common
][api-common], and [createStarryNight
][api-create-starry-night] from
the main module.
There is no default export.
It also includes grammars directly in its export map.
Do not use the lang/
folder or the .js
extension.
For CSS files, do use style/
but don’t use .css
:
import mdx from '@wooorm/starry-night/source.mdx' // Grammar.
import tritanopiaDark from '@wooorm/starry-night/style/tritanopia-dark' // CSS.
all
List of all grammars ([Array<Grammar>
][api-grammar])
common
List of ±35 common grammars ([Array<Grammar>
][api-grammar])
createStarryNight(grammars[, options])
Create a StarryNight
that can highlight things with the given grammars
.
This is async to allow async loading and registering, which is currently
only used for WASM.
Parameters
grammars
([Array<Grammar>
][api-grammar]) — grammars to supportoptions
([Options
][api-options], optional) — configuration
Returns
Promise that resolves to an instance which highlights with the bound
grammars (Promise<StarryNight>
).
starryNight.flagToScope(flag)
Get the grammar scope (such as text.md
) associated with a grammar name
(such as markdown
) or grammar extension (such as .mdwn
).
This function uses the first word (when splitting on spaces and tabs) that is used after the opening of a fenced code block:
```js
console.log(1)
```
To match GitHub, this also accepts entire paths:
```path/to/example.js
console.log(1)
```
👉 Note: languages can use the same extensions. For example,
.h
is reused by many languages. In those cases, you will get one scope back, but it might not be the most popular language associated with an extension.
Parameters
flag
(string
) — grammar name (such as'markdown'
), grammar extension (such as'.mdwn'
), or entire file path ending in extension
Returns
Grammar scope, such as 'text.md'
(string
or undefined
).
Example
import {common, createStarryNight} from '@wooorm/starry-night'
const starryNight = await createStarryNight(common)
console.log(starryNight.flagToScope('pandoc')) // `'text.md'`
console.log(starryNight.flagToScope('workbook')) // `'text.md'`
console.log(starryNight.flagToScope('.workbook')) // `'text.md'`
console.log(starryNight.flagToScope('path/to/example.js')) // `'source.js'`
console.log(starryNight.flagToScope('whatever')) // `undefined`
starryNight.highlight(value, scope)
Highlight programming code.
Parameters
value
(string
) — code to highlightscope
(string
) — registered grammar scope to highlight as (such as'text.md'
)
Returns
Node representing highlighted code ([Root
][hast-root]).
Example
import {createStarryNight} from '@wooorm/starry-night'
import sourceCss from '@wooorm/starry-night/source.css'
const starryNight = await createStarryNight([sourceCss])
console.log(starryNight.highlight('em { color: red }', 'source.css'))
Yields:
{
type: 'root',
children: [
{type: 'element', tagName: 'span', properties: [Object], children: [Array]},
{type: 'text', value: ' { '},
{type: 'element', tagName: 'span', properties: [Object], children: [Array]},
{type: 'text', value: ': '},
{type: 'element', tagName: 'span', properties: [Object], children: [Array]},
{type: 'text', value: ' }'}
]
}
starryNight.missingScopes()
List scopes that are needed by the registered grammars but that are missing.
To illustrate, the text.xml.svg
grammar needs the text.xml
grammar.
When you register text.xml.svg
without text.xml
, it will be listed here.
Returns
List of grammar scopes, such as 'text.md'
(Array<string>
).
Example
import {createStarryNight} from '@wooorm/starry-night'
import textXml from '@wooorm/starry-night/text.xml'
import textXmlSvg from '@wooorm/starry-night/text.xml.svg'
const svg = await createStarryNight([textXmlSvg])
console.log(svg.missingScopes()) //=> ['text.xml']
const svgAndXml = await createStarryNight([textXmlSvg, textXml])
console.log(svgAndXml.missingScopes()) //=> []
starryNight.register(grammars)
Add more grammars.
Parameters
grammars
([Array<Grammar>
][api-grammar]) — grammars to support
Returns
Promise resolving to nothing (Promise<undefined>
).
Example
import {createStarryNight} from '@wooorm/starry-night'
import sourceCss from '@wooorm/starry-night/source.css'
import textMd from '@wooorm/starry-night/text.md'
import {toHtml} from 'hast-util-to-html'
const markdown = '```css\nem { color: red }\n```'
const starryNight = await createStarryNight([textMd])
console.log(toHtml(starryNight.highlight(markdown, 'text.md')))
await starryNight.register([sourceCss])
console.log(toHtml(starryNight.highlight(markdown, 'text.md')))
Yields:
<span class="pl-s">```</span><span class="pl-en">css</span>
<span class="pl-c1">em { color: red }</span>
<span class="pl-s">```</span>
<span class="pl-s">```</span><span class="pl-en">css</span>
<span class="pl-ent">em</span> { <span class="pl-c1">color</span>: <span class="pl-c1">red</span> }
<span class="pl-s">```</span>
starryNight.scopes()
List all registered scopes.
Returns
List of grammar scopes, such as 'text.md'
(Array<string>
).
Example
import {common, createStarryNight} from '@wooorm/starry-night'
const starryNight = await createStarryNight(common)
console.log(starryNight.scopes())
Yields:
[
'source.c',
'source.c++',
// …
'text.xml',
'text.xml.svg'
]
GetOnigurumaUrl
Function to get a URL to the oniguruma WASM (TypeScript type).
👉 Note: this must currently result in a version 2 URL of
onig.wasm
from [vscode-oniguruma
][vscode-oniguruma].
⚠️ Danger: when you use this functionality, your project might break at any time (when reinstalling dependencies), except when you make sure that the WASM binary you load manually is what our internally used
vscode-oniguruma
dependency expects. To solve this, you could for example use an npm script called [dependencies
][npm-script-dependencies] (which runs everytimenode_modules
is changed) which copiesvscode-oniguruma/release/onig.wasm
to the place you want to host it.
Returns
URL object to a WASM binary (Promise<URL>
or URL
).
Example
import {common, createStarryNight} from '@wooorm/starry-night'
const starryNight = await createStarryNight(common, {
getOnigurumaUrlFetch() {
return new URL('/onig.wasm', window.location.href);
}
})
Grammar
TextMate grammar with some extra info (TypeScript type).
Fields
dependencies
(Array<string>
, optional, example:['source.tsx']
) — list of scopes that are needed for this grammar to workextensions
(Array<string>
, example:['.mdx']
) — list of extensionsextensionsWithDot
(Array<string>
, optional, example:['.php']
) — list of extensions that only match if used w/ a dotinjections
(Record<string, Rule>
, optional) — TextMate injectionsnames
(Array<string>
, example:['mdx']
) — list of namespatterns
(Array<Rule>
) — TextMate patternsrepository
(Record<string, Rule>
, optional) — TextMate repositoryscopeName
(string
, example:'source.mdx'
) — scope
Options
Configuration (TypeScript type).