@svelte-dev/pretty-code

Beautiful Svelte code blocks for Markdown or MDsveX.

@svelte-dev/pretty-code is a MDsveX highlight plugin powered by rehype-pretty-code and shikiji. The syntax highlighter that provides beautiful code blocks for Markdown or MDsveX. It only works on Block Codes (not Inline codes).

Editor-Grade Highlighting

Enjoy the accuracy and granularity of VS Code’s syntax highlighting engine and the popularity of its themes ecosystem — use any VS Code theme you want!

demo.tsx
import { useFloating, offset } from '@floating-ui/react';
 
interface Props {
  open: boolean;
  onOpenChange(open: boolean): void;
}
 
export function App({ open, onOpenChange }: Props) {
  const { refs, floatingStyles } = useFloating({
    open,
    onOpenChange,
    placement: 'left',
    middleware: [offset(10)]
  });
 
  return (
    <Container>
      <div ref={refs.setReference} />
      {open && <div ref={refs.setFloating} style={floatingStyles} />}
    </Container>
  );
}

Line Numbers and Line Highlighting

Draw attention to a particular line of code.

import { useFloating } from '@floating-ui/react';
 
function MyComponent() {
  const { refs, floatingStyles } = useFloating();
 
  return (
    <>
      <div ref={refs.setReference} />
      <div ref={refs.setFloating} style={floatingStyles} />
    </>
  );
}
Caption

Word Highlighting

Draw attention to a particular word or series of characters.

import { useFloating } from '@floating-ui/react';
 
function MyComponent() {
  const { refs, floatingStyles } = useFloating();
 
  return (
    <>
      <div ref={refs.setReference} />
      <div ref={refs.setFloating} style={floatingStyles} />
    </>
  );
}

ANSI Highlighting

  vite v5.0.0 dev server running at:
 
  > Local: http://localhost:3000/
  > Network: use `--host` to expose
 
  ready in 125ms.
 
8:38:02 PM [vite] hmr update /src/App.jsx

Installation

Install via your terminal:

npm add @svelte-dev/pretty-code

This package is ESM-only and currently supports shikiji ^0.7.0 || ^0.8.0.

Usage

The following works both on the server and on the client.

unified@11 is used as a dependency.

import { defineMDSveXConfig as defineConfig } from 'mdsvex';
import { createHighlighter } from '@svelte-dev/pretty-code';
 
const config = defineConfig({
  extensions: ['.svelte.md', '.md', '.svx'],
 
  highlight: {
    highlighter: createHighlighter({
      // keepBackground: false,
      theme: {
        dark: 'solarized-dark',
        light: 'solarized-light'
      }
    })
  }
});
 
export default config;

Options

interface Options {
  grid?: boolean;
  theme?: Theme | Record<string, Theme>;
  keepBackground?: boolean;
  defaultLang?: string;
  tokensMap?: Record<string, string>;
  transformers?: ShikijiTransformer[];
  filterMetaString?(str: string): string;
  getHighlighter?(options: BundledHighlighterOptions): Promise<Highlighter>;
  onVisitLine?(element: LineElement): void;
  onVisitHighlightedLine?(element: LineElement): void;
  onVisitHighlightedChars?(element: CharsElement, id: string | undefined): void;
  onVisitTitle?(element: Element): void;
  onVisitCaption?(element: Element): void;
}

grid

A grid style is present by default which allows line highlighting to span the entire width of a horizontally-scrollable code block.

You can disable this setting if necessary:

const options = {
  grid: false
};

theme

The default theme is github-dark-dimmed. Shikiji has a bunch of pre-packaged themes, which can be specified as a plain string:

const options = {
  theme: 'one-dark-pro'
};

You can use your own theme as well by passing the theme JSON:

const options = {
  theme: JSON.parse(
    fs.readFileSync(new URL('./themes/moonlight-ii.json', import.meta.url), 'utf-8')
  )
};

keepBackground

To apply a custom background instead of inheriting the background from the theme:

const options = {
  keepBackground: false
};

defaultLang

In this case, you can specify a default language:

const options = {
  defaultLang: 'plaintext'
};

transformers

Transformers are a Shikiji-native way to manipulate the hAST tree of the code blocks and further extend the behavior of this plugin. The shikiji-transformers package provides some useful transformers.

import { transformerNotationDiff } from 'shikiji-transformers';
 
const options = {
  transformers: [transformerNotationDiff()]
};

Meta Strings

Code blocks are configured via the meta string on the top codeblock fence.

If your library also parses code blocks’ meta strings, it may cause conflicts with rehype-pretty-code. This option allows you to filter out some part of the meta string before the library starts parsing it.

const options = {
  filterMetaString: (string) => string.replace(/filename="[^"]*"/, '')
};

Highlight Lines

Place a numeric range inside {}.

\```js {1-3,4}
 
\```

The line <span> receives a data-highlighted-line attribute to style via CSS.

Group Highlighted Lines By Id

Place an id after # after the {}. This allows you to color or style lines differently based on their id.

\```js {1,2}#a {3,4}#b
 
\```

The line <span> receives a data-highlighted-line-id="<id>" attribute to style via CSS.

Highlight Chars

You can use either /:

\```js /carrot/
 
\```

Or " as a delimiter:

\```js "carrot"
\```

Different segments of chars can also be highlighted:

\```js /carrot/ /apple/
 
\```

The chars <span> receives a data-highlighted-chars attribute to style via CSS.

To highlight only the third to fifth instances of carrot, a numeric range can be placed after the last /.

\```js /carrot/3-5
 
\```

Highlight only the third to fifth instances of carrot and any instances of apple.

\```js /carrot/3-5 /apple/
 
\```

Group Highlighted Chars By Id

Place an id after # after the chars. This allows you to color chars differently based on their id.

\```js /age/#v /name/#v /setAge/#s /setName/#s /50/#i /'Taylor'/#i
const [age, setAge] = useState(50);
const [name, setName] = useState('Taylor');
\```
const [age, setAge] = useState(50);
const [name, setName] = useState('Taylor');

The chars <span> receives a data-chars-id="<id>" attribute to style via CSS.

Titles

Add a file title to your code block, with text inside double quotes (""):

\```js title="..."
 
\```

Captions

Add a caption underneath your code block, with text inside double quotes (""):

\```js caption="..."
 
\```

Line Numbers

CSS counters can be used to add line numbers.

code {
  counter-reset: line;
}
 
code > [data-line]::before {
  counter-increment: line;
  content: counter(line);
 
  /* Other styling */
  display: inline-block;
  width: 1rem;
  margin-right: 2rem;
  text-align: right;
  color: gray;
}
 
code[data-line-numbers-max-digits='2'] > [data-line]::before {
  width: 2rem;
}
 
code[data-line-numbers-max-digits='3'] > [data-line]::before {
  width: 3rem;
}

If you want to conditionally show them, use showLineNumbers:

\```js showLineNumbers
 
\```

<code> will have attributes data-line-numbers and data-line-numbers-max-digits="n".

If you want to start line numbers at a specific number, use showLineNumbers{number}:

\```js showLineNumbers{number}
 
\```

Multiple Themes (Dark and Light Mode)

Pass your themes to theme, where the keys represent the color mode:

const options = {
  theme: {
    dark: 'github-dark-dimmed',
    light: 'github-light'
  }
};

Now, use the following CSS to display the variable colors — if a space is found in the theme name, then CSS variable keys based on the object are available (more info):

code[data-theme*=' '],
code[data-theme*=' '] span {
  color: var(--shiki-light);
  background-color: var(--shiki-light-bg);
}
 
@media (prefers-color-scheme: dark) {
  code[data-theme*=' '],
  code[data-theme*=' '] span {
    color: var(--shiki-dark);
    background-color: var(--shiki-dark-bg);
  }
}

The <code> and <pre> elements will have the data attribute data-theme="...themes", listing each theme value space-separated:

<code data-theme="github-dark-dimmed github-light"></code>

Visitor Hooks

To customize the HTML output, you can use visitor callback hooks to manipulate the hAST elements directly:

const options = {
  onVisitLine(element) {
    console.log('Visited line');
  },
  onVisitHighlightedLine(element) {
    console.log('Visited highlighted line');
  },
  onVisitHighlightedChars(element) {
    console.log('Visited highlighted chars');
  },
  onVisitTitle(element) {
    console.log('Visited title');
  },
  onVisitCaption(element) {
    console.log('Visited caption');
  }
};

Custom Highlighter

To completely configure the highlighter, use the getHighlighter option. This is helpful if you’d like to configure other Shikiji options, such as langs.

import { getHighlighter } from 'shikiji';
 
const options = {
  getHighlighter: (options) =>
    getHighlighter({
      ...options,
      langs: ['plaintext', async () => JSON.parse(await readFile('my-grammar.json', 'utf-8'))]
    })
};

Owner: Willin Wang

Donation ways:

License

Apache-2.0