starry-night

repository·main·Indexed 22 days ago

https://github.com/wooorm/starry-night

An open-source syntax highlighter that replicates GitHub's 'PrettyLights' functionality using TextMate grammars and WASM. Unlike traditional highlighters, it produces a HAST (Hypertext Abstract Syntax Tree) instead of raw HTML, making it suitable for React/Preact virtual DOMs, CLI rendering, and unified/rehype workflows. It supports over 600 grammars and provides CSS-based theming for light and dark modes.

Tokens
21.4K
Snippets
63
Records
105
Agent score
83%

What's inside @wooorm/starry-night

  1. When to use starry-night

    main

    Use starry-night if:

    • You need high-quality syntax highlighting that matches GitHub's output.
    • You want to support a vast number of grammars (600+ via the all set).
    • You need an AST (Abstract Syntax Tree) output (like hast) instead of just serialized HTML. This is useful for React/Preact (virtual DOM), CLI rendering (ANSI), or rehype/unified workflows.
    • You want to use CSS-based theming (classes) rather than inline styles, making dark mode implementation easier.

    Avoid starry-night if:

    • You are concerned about bundle size in the browser. starry-night is relatively heavy due to its WASM dependency and TextMate grammars. For lighter alternatives, consider lowlight or refractor.
  2. Access available grammars via `all` and `common`

    main

    Starry Night provides pre-defined sets of grammars for syntax highlighting.

    • Use common to access a curated set of frequently used, checked grammars.
    • Use all to access the full collection of available grammars.

    You can extend these sets by adding your own grammars manually.

  3. Understand the starry-night hast syntax tree structure

    main
    The generated hast tree starts with a root node representing the fragment. It contains up to three levels of <span> elements, each with a single class. Because TextMate grammars work per line, all line endings are stored directly in the root node. This structure allows for efficient line-number gutter generation by iterating through the root node's children.
  4. Import grammars and styles from @wooorm/starry-night

    main

    The package allows importing specific grammars and CSS styles directly from the export map.

    Grammars: Import the default export from the grammar path. Do not use the lang/ folder or the .js extension.

    Styles: For CSS files, use the style/ directory and do not use the .css extension.

    import sourceMdx from '@wooorm/starry-night/source.mdx' // Grammar.
    import styleTritanopiaDark from '@wooorm/starry-night/style/tritanopia-dark' // CSS.
  5. Manage syntax highlighting CSS and themes

    main

    starry-night does not automatically inject CSS for syntax highlighted code, as it is designed to work in environments other than the browser. If you are using it in a browser, you must manually include one of the packaged themes.

    Theme Behavior

    • CSS Variables: All themes support CSS variables (custom properties).
    • core.css: Requires you to define your own CSS properties manually.
    • Other Themes: Define colors on :root.
    • Color Schemes:
      • Themes with a light or dark suffix are fixed to that scheme.
      • Themes with no suffix (or specific names like colorblind, dimmed, high-contrast, tritanopia) automatically switch between light and dark modes using @media (prefers-color-scheme: dark).

    All themes are under 1 kB in size.

  6. Define a JSDoc Grammar

    main

    A Grammar object defines how to parse and highlight JSDoc files. It consists of extensions, names, patterns, and an optional repository for reusable sub-patterns.

    Key components of a Grammar object:

    • patterns: An array of pattern objects used for matching text. Patterns can use match (for simple regex), begin/end (for block-level parsing), or captures to assign syntax highlighting names to specific groups.
    • repository: A collection of named patterns that can be reused within the main patterns array using the include key.
    • scopeName: The textmate scope name for the language (e.g., source.jsdoc).
    /** @type {Grammar} */
    const grammar = {
      extensions: [],
      names: [],
      patterns: [
        // ... pattern objects with match, begin, end, captures, etc.
      ],
      repository: {
        // ... reusable patterns
      },
      scopeName: 'source.jsdoc'
    }
    
    export default grammar;
  7. Grammar definition for Context files

    main

    The source.context grammar defines the syntax highlighting rules for .context files. It is a complex grammar that relies on the source.shell dependency for embedded shell commands. The grammar is structured around several core concepts:

    • Main Entrypoint: The main pattern orchestrates the parsing by including #line, #page, #init, #pageinfo, and #commands.
    • Device Initialization: The init pattern handles device initialization (e.g., X device_name(...)).
    • Page Management: The page pattern manages page boundaries (starting with P and ending with p), while pageinfo (starting with Y) defines paper specifications.
    • Line Context: The line pattern (starting with N and ending with n) defines a block of text where various commands and rules can be applied.
    • Command System: Most functionality is driven by specific command patterns (e.g., drawing, font, motion, text) that typically begin with a single-character operator.
    /**
     * @import {Grammar} from '@wooorm/starry-night'
     */
    
    /** @type {Grammar} */
    const grammar = {
      dependencies: ['source.shell'],
      // ...
      scopeName: 'source.context'
    }
    
    export default grammar
  8. Use starry-night in the browser

    main

    You can run starry-night directly in the browser (e.g., for client-side rendering of markdown or comments). This example demonstrates how to find <code> elements with a language-* class, resolve their scope using flagToScope, highlight the content, and replace the DOM nodes using hast-util-to-dom.

    import {
      common,
      createStarryNight
    } from 'https://esm.sh/@wooorm/starry-night@3?bundle'
    import {toDom} from 'https://esm.sh/hast-util-to-dom@4?bundle'
    
    const starryNight = await createStarryNight(common)
    const prefix = 'language-'
    
    const nodes = Array.from(document.body.querySelectorAll('code'))
    
    for (const node of nodes) {
      const className = Array.from(node.classList).find(function (d) {
        return d.startsWith(prefix)
      })
      if (!className) continue
      const scope = starryNight.flagToScope(className.slice(prefix.length))
      if (!scope) continue
      const tree = starryNight.highlight(node.textContent, scope)
      node.replaceChildren(toDom(tree, {fragment: true}))
    }
  9. Serialize hast trees as HTML

    main

    The hast trees returned by starry-night can be converted into HTML strings using hast-util-to-html. This is useful for server-side rendering or generating static HTML from highlighted code.

    import {common, createStarryNight} from '@wooorm/starry-night'
    import {toHtml} from 'hast-util-to-html'
    
    const starryNight = await createStarryNight(common)
    
    const tree = starryNight.highlight('"use strict";', 'source.js')
    
    console.log(toHtml(tree))
  10. Integrate with unified, remark, and rehype

    main

    You can use rehype-starry-night as a plugin within a unified processing pipeline to automatically highlight code blocks in Markdown files.

    import fs from 'node:fs/promises'
    import rehypeStarryNight from 'rehype-starry-night'
    import rehypeStringify from 'rehype-stringify'
    import remarkParse from 'remark-parse'
    import remarkRehype from 'remark-rehype'
    import {unified} from 'unified'
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkRehype)
      .use(rehypeStarryNight)
      .use(rehypeStringify)
      .process(await fs.readFile('example.md'))
    
    console.log(String(file))