@docfy/plugin-shiki 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +221 -0
- package/lib/index.d.ts +56 -0
- package/lib/index.js +200 -0
- package/package.json +59 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Josemar Luedke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.N
|
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# @docfy/plugin-shiki
|
|
2
|
+
|
|
3
|
+
An **opt-in** [Shiki](https://shiki.style) syntax-highlighting preset for
|
|
4
|
+
Docfy, with first-class support for Ember's `.gjs`/`.gts` file types.
|
|
5
|
+
|
|
6
|
+
This package is standalone: `shiki` is never a dependency of `@docfy/core` or
|
|
7
|
+
`@docfy/ember`. Consumers who want syntax highlighting install this package
|
|
8
|
+
and spread its return value into their own `rehypePlugins`.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Wiring up Shiki (or `rehype-highlight`) by hand for a Docfy site normally
|
|
13
|
+
means re-deriving the same handful of settings every time: which themes to
|
|
14
|
+
use, how to map `gjs`/`gts`/`hbs` fence languages onto real grammars, and how
|
|
15
|
+
to enable `{1,3-5}`-style line highlighting. This preset packages all of
|
|
16
|
+
that up so you don't have to.
|
|
17
|
+
|
|
18
|
+
**Glimmer support is the main reason this package exists.** Shiki bundles
|
|
19
|
+
first-class `glimmer-js` and `glimmer-ts` TextMate grammars (scope
|
|
20
|
+
`source.gts`), so a `.gts`/`.gjs` code fence gets real tokenisation —
|
|
21
|
+
including inside `<template>` tags — instead of silently falling back to
|
|
22
|
+
plain JavaScript (or plain text).
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @docfy/plugin-shiki
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import Docfy from '@docfy/core';
|
|
34
|
+
import docfyShiki from '@docfy/plugin-shiki';
|
|
35
|
+
|
|
36
|
+
const docfy = new Docfy({
|
|
37
|
+
rehypePlugins: [
|
|
38
|
+
// ...your other rehype plugins
|
|
39
|
+
...docfyShiki(),
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Why this preset builds its own highlighter
|
|
45
|
+
|
|
46
|
+
`@docfy/core` drives its rehype pipeline with `unified().runSync(...)` (see
|
|
47
|
+
`packages/core/src/-private/remark.ts`), never the async `.run()`/`.process()`.
|
|
48
|
+
Shiki's own `@shikijs/rehype` default export is async-only — it lazily boots a
|
|
49
|
+
singleton highlighter the first time the tree transformer runs — so wiring it
|
|
50
|
+
in directly makes `runSync` throw.
|
|
51
|
+
|
|
52
|
+
`@docfy/ember-cli` (the classic, non-Vite build) also loads a consumer's
|
|
53
|
+
`docfy.config.*` with a **synchronous** `require()`. A module that contains a
|
|
54
|
+
top-level `await` anywhere in its graph is an async ES module, and Node
|
|
55
|
+
throws `ERR_REQUIRE_ASYNC_MODULE` the moment such a config merely `import`s
|
|
56
|
+
it — so this package cannot use a top-level `await` either, even to build its
|
|
57
|
+
highlighter once at startup.
|
|
58
|
+
|
|
59
|
+
To stay synchronous end-to-end, this preset:
|
|
60
|
+
|
|
61
|
+
- Loads its grammars and themes via plain, **static** `import` statements
|
|
62
|
+
(see "Supported languages" and "Supported themes" below) — never a dynamic
|
|
63
|
+
`import()` or a top-level `await`. This applies just as much to any extra
|
|
64
|
+
grammar you pass via `langs` (see "Adding a language" below).
|
|
65
|
+
- Builds a Shiki `HighlighterCore` via `createHighlighterCoreSync`, using the
|
|
66
|
+
pure-JS regex engine (no WASM to load). Calling `docfyShiki()` with no
|
|
67
|
+
`langs`/`langAlias` reuses one memoized highlighter across calls, built the
|
|
68
|
+
first time it's needed rather than at `import` time — a consumer that
|
|
69
|
+
imports this package but never calls `docfyShiki()` doesn't pay for it at
|
|
70
|
+
all. Passing `langs` and/or `langAlias` builds a dedicated highlighter for
|
|
71
|
+
that call instead, since `langAlias` can only be set at construction time
|
|
72
|
+
(see "Language aliases" below).
|
|
73
|
+
- Uses `@shikijs/rehype/core`'s `rehypeShikiFromHighlighter`, which — given an
|
|
74
|
+
already-built highlighter and no lazy-loaded languages — returns a genuinely
|
|
75
|
+
synchronous unified transformer.
|
|
76
|
+
|
|
77
|
+
## Supported languages
|
|
78
|
+
|
|
79
|
+
Preloading **every** language Shiki bundles (~200 grammars, ~11.6MB of JSON)
|
|
80
|
+
would cost real parse time at `import`, whether or not a given site uses most
|
|
81
|
+
of them. Instead, this preset preloads a curated set covering the glimmer
|
|
82
|
+
grammars that are the point of the package, plus the languages Docfy's own
|
|
83
|
+
docs (and typical Ember app docs) actually fence:
|
|
84
|
+
|
|
85
|
+
`glimmer-ts`, `glimmer-js`, `handlebars`, `javascript`, `typescript`, `jsx`,
|
|
86
|
+
`tsx`, `json`, `css`, `scss`, `html`, `markdown`, `shellscript`, `diff`,
|
|
87
|
+
`yaml`.
|
|
88
|
+
|
|
89
|
+
It also registers these language aliases so fences written the way Ember
|
|
90
|
+
docs are actually written resolve to the grammars above:
|
|
91
|
+
|
|
92
|
+
| Fence language | Resolves to |
|
|
93
|
+
| -------------- | ------------ |
|
|
94
|
+
| `gts` | `glimmer-ts` |
|
|
95
|
+
| `gjs` | `glimmer-js` |
|
|
96
|
+
| `hbs` | `handlebars` |
|
|
97
|
+
|
|
98
|
+
You can add to (or override) this table with the `langAlias` option — see
|
|
99
|
+
"Adding a language" below.
|
|
100
|
+
|
|
101
|
+
### Adding a language
|
|
102
|
+
|
|
103
|
+
A fence whose language is not in the curated list above (for example
|
|
104
|
+
` ```rust `) is, by default, **not** an error: `@shikijs/rehype` leaves any
|
|
105
|
+
`<pre>` whose language isn't loaded completely untouched — no `.shiki`
|
|
106
|
+
wrapper, no theme classes, no Shiki-applied highlighting — it renders as
|
|
107
|
+
plain, unhighlighted fenced code, exactly as if no highlighter were
|
|
108
|
+
configured for it. A docs build never fails because someone wrote a fence in
|
|
109
|
+
a language this preset doesn't preload.
|
|
110
|
+
|
|
111
|
+
If you actually want that language highlighted, pass its grammar via the
|
|
112
|
+
`langs` option. Each entry is a statically-imported module from
|
|
113
|
+
`@shikijs/langs` (or any other Shiki-compatible grammar you import
|
|
114
|
+
yourself) — never a dynamic `import()`, since this package has to stay
|
|
115
|
+
synchronous end-to-end (see "Why this preset builds its own highlighter"
|
|
116
|
+
above):
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import rust from '@shikijs/langs/rust';
|
|
120
|
+
import shiki from '@docfy/plugin-shiki';
|
|
121
|
+
|
|
122
|
+
export default {
|
|
123
|
+
rehypePlugins: [autolinkHeadings, ...shiki({ langs: [rust] })],
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A ` ```rust ` fence now tokenizes for real. `langs` is additive — it doesn't
|
|
128
|
+
replace the curated set this preset already preloads.
|
|
129
|
+
|
|
130
|
+
If the language you need is only reachable under a different fence name (for
|
|
131
|
+
example your docs use ` ```rs ` rather than ` ```rust `), pair `langs` with
|
|
132
|
+
`langAlias`, which is merged over this preset's own `gjs`/`gts`/`hbs` table:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
shiki({ langs: [rust], langAlias: { rs: 'rust' } });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`langAlias` on its own (without a matching `langs` entry) cannot make a new
|
|
139
|
+
language highlight — see "Language aliases" below for why, and why the two
|
|
140
|
+
options work together the way they do.
|
|
141
|
+
|
|
142
|
+
## Supported themes
|
|
143
|
+
|
|
144
|
+
This preset statically preloads three themes: `github-light`, `github-dark`
|
|
145
|
+
(the defaults), and `nord`, and — unlike languages — there is currently no
|
|
146
|
+
option to add more; passing an unloaded theme name throws, because the
|
|
147
|
+
underlying highlighter is built synchronously and cannot fetch a theme
|
|
148
|
+
afterwards.
|
|
149
|
+
|
|
150
|
+
## Overriding themes
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
docfyShiki({
|
|
154
|
+
themes: { light: 'github-light', dark: 'nord' },
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Language aliases
|
|
159
|
+
|
|
160
|
+
An earlier version of this package removed a `langAlias` option, because at
|
|
161
|
+
the time the highlighter was built once, synchronously, at *module import*
|
|
162
|
+
time — before `docfyShiki(options)` was ever called. An alias supplied at
|
|
163
|
+
call time could only ever change the `data-language` attribute this preset
|
|
164
|
+
writes on the rendered `<pre>`; it could never register a new alias with
|
|
165
|
+
Shiki's grammar resolver, which was already fixed by then. A caller adding,
|
|
166
|
+
say, `{ svelte: 'html' }` would see their fence mislabelled rather than
|
|
167
|
+
actually highlighted as HTML — an option that appears to work and silently
|
|
168
|
+
does not, which is worse than no option.
|
|
169
|
+
|
|
170
|
+
`langAlias` is back because that constraint no longer holds: the highlighter
|
|
171
|
+
is now built inside `docfyShiki()` itself (see "Adding a language" above), so
|
|
172
|
+
an alias supplied there is baked into the same construction call as any
|
|
173
|
+
`langs` you pass alongside it, and genuinely participates in grammar
|
|
174
|
+
resolution. `langAlias` merges *over* the package's own `gjs`/`gts`/`hbs`
|
|
175
|
+
table — it can override one of those three, but not remove the other two.
|
|
176
|
+
|
|
177
|
+
Passing `langAlias` without a matching `langs` entry (or one of the
|
|
178
|
+
languages this preset already preloads) still can't highlight anything new —
|
|
179
|
+
an alias can only point at a grammar that's actually loaded. But it no
|
|
180
|
+
longer looks like it worked when it didn't: a fence whose alias points at an
|
|
181
|
+
unloaded grammar is left completely untouched (see "Adding a language"
|
|
182
|
+
above) — no `data-language` attribute either, exactly like any other
|
|
183
|
+
unsupported language — rather than being mislabelled with a `data-language`
|
|
184
|
+
that implies it highlighted.
|
|
185
|
+
|
|
186
|
+
## Extra transformers
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { transformerNotationDiff } from '@shikijs/transformers';
|
|
190
|
+
|
|
191
|
+
docfyShiki({
|
|
192
|
+
transformers: [transformerNotationDiff()],
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`transformers` is appended after this preset's own defaults
|
|
197
|
+
(`transformerMetaHighlight`, `transformerMetaWordHighlight`, and two small
|
|
198
|
+
internal transformers that add the `data-language="..."` and
|
|
199
|
+
`data-highlighted` attributes described above).
|
|
200
|
+
|
|
201
|
+
## Line highlighting
|
|
202
|
+
|
|
203
|
+
Marking specific lines in a fence, e.g.:
|
|
204
|
+
|
|
205
|
+
````md
|
|
206
|
+
```ts {2,4-5}
|
|
207
|
+
// ...
|
|
208
|
+
```
|
|
209
|
+
````
|
|
210
|
+
|
|
211
|
+
works because this preset wires up `transformerMetaHighlight` from
|
|
212
|
+
`@shikijs/transformers`. Line highlighting is **not** a feature of Shiki (or
|
|
213
|
+
of Docfy) on its own — it only works when this preset (or an equivalent
|
|
214
|
+
Shiki configuration that includes `transformerMetaHighlight`) is in use.
|
|
215
|
+
Highlighted lines get both a `highlighted` class and a `data-highlighted`
|
|
216
|
+
attribute, so you can style them with either `.highlighted` or
|
|
217
|
+
`[data-highlighted]` in your own CSS.
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
This project is licensed under the [MIT License](LICENSE.md).
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type LanguageRegistration } from 'shiki/core';
|
|
2
|
+
export interface DocfyShikiOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Light and dark themes, emitted as CSS variables so the page switches
|
|
5
|
+
* themes without re-highlighting and without any runtime JavaScript.
|
|
6
|
+
*
|
|
7
|
+
* Only themes preloaded by this package (currently `github-light`,
|
|
8
|
+
* `github-dark`, and `nord`) can be used — see "Supported themes" in the
|
|
9
|
+
* README.
|
|
10
|
+
*/
|
|
11
|
+
themes?: {
|
|
12
|
+
light: string;
|
|
13
|
+
dark: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Extra Shiki transformers, appended after the defaults.
|
|
17
|
+
*/
|
|
18
|
+
transformers?: unknown[];
|
|
19
|
+
/**
|
|
20
|
+
* Extra TextMate grammars to register alongside the curated set this
|
|
21
|
+
* preset preloads (see "Supported languages" below), so a fence in a
|
|
22
|
+
* language this preset doesn't ship by default can still be highlighted.
|
|
23
|
+
*
|
|
24
|
+
* Each entry is a statically-imported grammar module, e.g.:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import rust from '@shikijs/langs/rust';
|
|
28
|
+
* import shiki from '@docfy/plugin-shiki';
|
|
29
|
+
*
|
|
30
|
+
* shiki({ langs: [rust] });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Import must stay static (never `import()`) — see the module-level
|
|
34
|
+
* comment above about why a top-level `await` anywhere in this graph
|
|
35
|
+
* breaks `@docfy/ember-cli`'s synchronous `require()` of a consumer's
|
|
36
|
+
* `docfy.config.*`.
|
|
37
|
+
*/
|
|
38
|
+
langs?: LanguageRegistration[];
|
|
39
|
+
/**
|
|
40
|
+
* Extra fence-language aliases, merged OVER this preset's built-in
|
|
41
|
+
* `gjs`/`gts`/`hbs` map (so an entry here can override one of those three,
|
|
42
|
+
* but cannot remove the other two). Unlike the removed-and-reinstated
|
|
43
|
+
* option this replaces, this one is genuinely functional: passing
|
|
44
|
+
* `langs` and `langAlias` together builds a highlighter that knows about
|
|
45
|
+
* the new grammar from construction, so an alias can resolve to it.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* import svelte from '@shikijs/langs/svelte';
|
|
49
|
+
* import shiki from '@docfy/plugin-shiki';
|
|
50
|
+
*
|
|
51
|
+
* shiki({ langs: [svelte], langAlias: { html_svelte: 'svelte' } });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
langAlias?: Record<string, string>;
|
|
55
|
+
}
|
|
56
|
+
export default function docfyShiki(options?: DocfyShikiOptions): unknown[];
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createHighlighterCoreSync, } from 'shiki/core';
|
|
2
|
+
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
|
|
3
|
+
// Static imports only. `@docfy/ember-cli` loads a consumer's `docfy.config.*`
|
|
4
|
+
// with a synchronous `require()` (see `packages/ember-cli/src/get-config.ts`).
|
|
5
|
+
// A top-level `await` anywhere in this module's graph turns it into an async
|
|
6
|
+
// ES module, and Node throws `ERR_REQUIRE_ASYNC_MODULE` the moment such a
|
|
7
|
+
// config merely `import`s this package. Static imports do not make a module
|
|
8
|
+
// async, so they're the only way to preload grammars/themes and still be
|
|
9
|
+
// usable from the classic (non-Vite) build.
|
|
10
|
+
//
|
|
11
|
+
// Each of these modules is a plain, synchronously-evaluated ES module that
|
|
12
|
+
// exports the parsed grammar/theme object (or, for grammars that embed other
|
|
13
|
+
// languages, an array of them) directly — nothing here is a lazy loader.
|
|
14
|
+
import glimmerTs from '@shikijs/langs/glimmer-ts';
|
|
15
|
+
import glimmerJs from '@shikijs/langs/glimmer-js';
|
|
16
|
+
import handlebars from '@shikijs/langs/handlebars';
|
|
17
|
+
import javascript from '@shikijs/langs/javascript';
|
|
18
|
+
import typescript from '@shikijs/langs/typescript';
|
|
19
|
+
import jsx from '@shikijs/langs/jsx';
|
|
20
|
+
import tsx from '@shikijs/langs/tsx';
|
|
21
|
+
import json from '@shikijs/langs/json';
|
|
22
|
+
import css from '@shikijs/langs/css';
|
|
23
|
+
import scss from '@shikijs/langs/scss';
|
|
24
|
+
import html from '@shikijs/langs/html';
|
|
25
|
+
import markdown from '@shikijs/langs/markdown';
|
|
26
|
+
import shellscript from '@shikijs/langs/shellscript';
|
|
27
|
+
import diff from '@shikijs/langs/diff';
|
|
28
|
+
import yaml from '@shikijs/langs/yaml';
|
|
29
|
+
import githubLight from '@shikijs/themes/github-light';
|
|
30
|
+
import githubDark from '@shikijs/themes/github-dark';
|
|
31
|
+
import nord from '@shikijs/themes/nord';
|
|
32
|
+
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
|
|
33
|
+
import { transformerMetaHighlight, transformerMetaWordHighlight } from '@shikijs/transformers';
|
|
34
|
+
/**
|
|
35
|
+
* Shiki bundles first-class `glimmer-js` and `glimmer-ts` TextMate grammars
|
|
36
|
+
* (scope `source.gts`), so `.gjs`/`.gts` fences get real tokenisation rather
|
|
37
|
+
* than falling back to plain JavaScript. This is the preset's built-in alias
|
|
38
|
+
* table; a caller can add to it (or override individual entries) via the
|
|
39
|
+
* `langAlias` option on `docfyShiki(...)` — see `DocfyShikiOptions.langAlias`
|
|
40
|
+
* and the README's "Language aliases" section. It is merged with any
|
|
41
|
+
* caller-supplied aliases and re-baked into a fresh highlighter at
|
|
42
|
+
* `docfyShiki()` call time (see `getHighlighter` below), which is what makes
|
|
43
|
+
* a caller-supplied alias able to actually resolve to a caller-supplied
|
|
44
|
+
* `langs` grammar rather than only relabelling output.
|
|
45
|
+
*/
|
|
46
|
+
const DEFAULT_LANG_ALIAS = {
|
|
47
|
+
gjs: 'glimmer-js',
|
|
48
|
+
gts: 'glimmer-ts',
|
|
49
|
+
hbs: 'handlebars',
|
|
50
|
+
};
|
|
51
|
+
const DEFAULT_THEMES = { light: 'github-light', dark: 'github-dark' };
|
|
52
|
+
/**
|
|
53
|
+
* The curated set of languages this preset preloads by default. This is
|
|
54
|
+
* deliberately NOT "every language Shiki bundles" (~200 grammars, ~11.6MB of
|
|
55
|
+
* JSON) — preloading everything would cost real parse time for every
|
|
56
|
+
* consumer, whether or not they use most of those languages. This list
|
|
57
|
+
* covers the glimmer grammars that are the point of this package, plus the
|
|
58
|
+
* languages Docfy's own docs (and typical Ember app docs) actually fence:
|
|
59
|
+
* TypeScript/JavaScript and their JSX variants, Handlebars, JSON, CSS/SCSS,
|
|
60
|
+
* HTML, Markdown, shell, diff, and YAML.
|
|
61
|
+
*
|
|
62
|
+
* A fence in a language outside this set (and not covered by a `langs`
|
|
63
|
+
* option passed to `docfyShiki(...)`) is NOT an error: `rehypeShiki` (see
|
|
64
|
+
* below) leaves any `<pre>` whose language isn't loaded untouched — no
|
|
65
|
+
* `.shiki` wrapper, no theme, no crash — so a docs build never dies because
|
|
66
|
+
* someone wrote a ```rust fence. See the "Unsupported languages" section of
|
|
67
|
+
* the README, and `DocfyShikiOptions.langs` for how to add it instead.
|
|
68
|
+
*/
|
|
69
|
+
const DEFAULT_LANGS = [
|
|
70
|
+
glimmerTs,
|
|
71
|
+
glimmerJs,
|
|
72
|
+
handlebars,
|
|
73
|
+
javascript,
|
|
74
|
+
typescript,
|
|
75
|
+
jsx,
|
|
76
|
+
tsx,
|
|
77
|
+
json,
|
|
78
|
+
css,
|
|
79
|
+
scss,
|
|
80
|
+
html,
|
|
81
|
+
markdown,
|
|
82
|
+
shellscript,
|
|
83
|
+
diff,
|
|
84
|
+
yaml,
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* Builds a `HighlighterCore`. `langAlias` is construction-time-only in Shiki
|
|
88
|
+
* (it cannot be changed on an already-built highlighter), which is exactly
|
|
89
|
+
* why `docfyShiki()` builds a fresh highlighter per call rather than reusing
|
|
90
|
+
* a single module-level instance whenever a caller passes `langs` and/or
|
|
91
|
+
* `langAlias` — see `getHighlighter` below.
|
|
92
|
+
*/
|
|
93
|
+
function buildHighlighter(extraLangs, langAlias) {
|
|
94
|
+
return createHighlighterCoreSync({
|
|
95
|
+
langs: [...DEFAULT_LANGS, ...extraLangs],
|
|
96
|
+
themes: [githubLight, githubDark, nord],
|
|
97
|
+
// Shiki mutates the `langAlias` object it's given (accumulating its own
|
|
98
|
+
// built-in aliases into it), so pass a copy rather than the caller's map.
|
|
99
|
+
langAlias: Object.assign({}, langAlias),
|
|
100
|
+
// The pure-JS regex engine avoids loading a WASM binary. It cannot
|
|
101
|
+
// translate every Oniguruma pattern the way the WASM-backed `oniguruma`
|
|
102
|
+
// engine can, so `forgiving: true` is required — without it, an
|
|
103
|
+
// untranslatable pattern in any preloaded grammar throws at construction
|
|
104
|
+
// time, crashing every consumer's build rather than degrading the one
|
|
105
|
+
// language affected.
|
|
106
|
+
engine: createJavaScriptRegexEngine({ forgiving: true }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Memoized highlighter for the common case: a call to `docfyShiki()` with no
|
|
110
|
+
// `langs`/`langAlias`. Building the highlighter is moved from module load
|
|
111
|
+
// time into `docfyShiki()` (rather than the previous module-level
|
|
112
|
+
// `const highlighter = ...`) so that a consumer who imports this package but
|
|
113
|
+
// never calls `docfyShiki()` doesn't pay the parse cost of ~15 grammars for
|
|
114
|
+
// nothing; this cache keeps the *common* path — calling it once, with no
|
|
115
|
+
// extra options — down to a single construction, same as before.
|
|
116
|
+
let defaultHighlighter;
|
|
117
|
+
function getHighlighter(options) {
|
|
118
|
+
var _a;
|
|
119
|
+
const extraLangs = (_a = options.langs) !== null && _a !== void 0 ? _a : [];
|
|
120
|
+
const langAlias = Object.assign(Object.assign({}, DEFAULT_LANG_ALIAS), options.langAlias);
|
|
121
|
+
if (extraLangs.length === 0 && !options.langAlias) {
|
|
122
|
+
if (!defaultHighlighter) {
|
|
123
|
+
defaultHighlighter = buildHighlighter([], langAlias);
|
|
124
|
+
}
|
|
125
|
+
return { highlighter: defaultHighlighter, langAlias };
|
|
126
|
+
}
|
|
127
|
+
return { highlighter: buildHighlighter(extraLangs, langAlias), langAlias };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Records the language actually requested for a fence (after applying this
|
|
131
|
+
* preset's own alias table) as a `data-language` attribute on the `<pre>`.
|
|
132
|
+
* This makes the alias resolution independently observable in the rendered
|
|
133
|
+
* HTML: a `gts` fence that silently fell back to plain text would never
|
|
134
|
+
* reach this transformer with a real grammar match. It is intentionally a
|
|
135
|
+
* secondary signal in the test suite — the primary evidence that a language
|
|
136
|
+
* actually tokenised is Shiki's own per-token `<span style="...">` output,
|
|
137
|
+
* which this attribute cannot fake.
|
|
138
|
+
*/
|
|
139
|
+
function languageAttributeTransformer(langAlias) {
|
|
140
|
+
return {
|
|
141
|
+
name: 'docfy-shiki:language-attribute',
|
|
142
|
+
pre(node) {
|
|
143
|
+
var _a;
|
|
144
|
+
const lang = this.options.lang;
|
|
145
|
+
node.properties['data-language'] = (_a = langAlias[lang]) !== null && _a !== void 0 ? _a : lang;
|
|
146
|
+
return node;
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* `transformerMetaHighlight` (from `@shikijs/transformers`) marks lines named
|
|
152
|
+
* in a fence's `{1,3-5}` meta by adding a `highlighted` class to the line —
|
|
153
|
+
* it does not add a `data-highlighted` attribute. Consumers that prefer to
|
|
154
|
+
* style highlighted lines via `[data-highlighted]` (rather than matching a
|
|
155
|
+
* class name that could collide with their own CSS) need that attribute too,
|
|
156
|
+
* so this transformer mirrors the class onto a boolean data attribute. It
|
|
157
|
+
* must run after `transformerMetaHighlight` in the `transformers` array so
|
|
158
|
+
* the class has already been applied by the time this sees the line.
|
|
159
|
+
*/
|
|
160
|
+
function dataHighlightedAttributeTransformer() {
|
|
161
|
+
return {
|
|
162
|
+
name: 'docfy-shiki:data-highlighted',
|
|
163
|
+
line(node) {
|
|
164
|
+
const classes = node.properties.class;
|
|
165
|
+
const classList = Array.isArray(classes)
|
|
166
|
+
? classes
|
|
167
|
+
: typeof classes === 'string'
|
|
168
|
+
? classes.split(/\s+/)
|
|
169
|
+
: [];
|
|
170
|
+
if (classList.includes('highlighted')) {
|
|
171
|
+
node.properties['data-highlighted'] = '';
|
|
172
|
+
}
|
|
173
|
+
return node;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export default function docfyShiki(options = {}) {
|
|
178
|
+
const { highlighter, langAlias } = getHighlighter(options);
|
|
179
|
+
function shikiRehypePlugin() {
|
|
180
|
+
var _a, _b;
|
|
181
|
+
const transformers = [
|
|
182
|
+
transformerMetaHighlight(),
|
|
183
|
+
transformerMetaWordHighlight(),
|
|
184
|
+
languageAttributeTransformer(langAlias),
|
|
185
|
+
dataHighlightedAttributeTransformer(),
|
|
186
|
+
...((_a = options.transformers) !== null && _a !== void 0 ? _a : []),
|
|
187
|
+
];
|
|
188
|
+
return rehypeShikiFromHighlighter(highlighter, {
|
|
189
|
+
themes: (_b = options.themes) !== null && _b !== void 0 ? _b : DEFAULT_THEMES,
|
|
190
|
+
defaultColor: false,
|
|
191
|
+
transformers,
|
|
192
|
+
// A fence whose language was never preloaded (and isn't a "special"
|
|
193
|
+
// pseudo-language like `text`/`ansi`) is left as plain, unhighlighted
|
|
194
|
+
// code rather than throwing. `lazy` defaults to `false`, so there is no
|
|
195
|
+
// attempt to fetch a grammar on demand either — this preset is fully
|
|
196
|
+
// synchronous end to end.
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return [shikiRehypePlugin];
|
|
200
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@docfy/plugin-shiki",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "An opt-in Shiki syntax highlighting preset for Docfy",
|
|
6
|
+
"repository": "https://github.com/josemarluedke/docfy",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Josemar Luedke <josemarluedke@gmail.com>",
|
|
9
|
+
"main": "lib/index.js",
|
|
10
|
+
"types": "lib/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"lib"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"compile": "tsc",
|
|
16
|
+
"prepare": "tsc",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"lint": "eslint . --cache",
|
|
19
|
+
"lint:fix": "eslint . --fix",
|
|
20
|
+
"format": "prettier . --cache --write",
|
|
21
|
+
"format:check": "prettier . --cache --check"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@shikijs/langs": "^3.0.0",
|
|
25
|
+
"@shikijs/rehype": "^3.0.0",
|
|
26
|
+
"@shikijs/themes": "^3.0.0",
|
|
27
|
+
"@shikijs/transformers": "^3.0.0",
|
|
28
|
+
"shiki": "^3.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@docfy/core": "^0.13.1",
|
|
32
|
+
"@eslint/js": "^10.0.1",
|
|
33
|
+
"@types/mdast": "^4.0.4",
|
|
34
|
+
"@types/node": "^22.10.5",
|
|
35
|
+
"eslint": "^10.9.0",
|
|
36
|
+
"eslint-config-prettier": "^10.1.8",
|
|
37
|
+
"eslint-plugin-n": "^18.3.0",
|
|
38
|
+
"globals": "^17.11.0",
|
|
39
|
+
"prettier": "^3.9.6",
|
|
40
|
+
"ts-node": "^10.9.2",
|
|
41
|
+
"typescript": "^5.8.3",
|
|
42
|
+
"typescript-eslint": "^8.67.0",
|
|
43
|
+
"vitest": "^4.1.11"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22.22.2"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"type": "module",
|
|
52
|
+
"exports": {
|
|
53
|
+
".": {
|
|
54
|
+
"types": "./lib/index.d.ts",
|
|
55
|
+
"default": "./lib/index.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"gitHead": "d1cd6b90537801c49101a40d429a282aaad39aa7"
|
|
59
|
+
}
|