@vmz/plugin-shiki 0.3.13 → 0.3.15
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/README.md +11 -7
- package/components/Shiki.vmz +9 -2
- package/dist/runtime.d.ts +25 -4
- package/dist/runtime.js +68 -8
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -24,16 +24,19 @@ It is especially useful for:
|
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
27
|
-
`@vmz/plugin-shiki` resolves Shiki from the application. `shiki` (`>=3`)
|
|
28
|
-
bundled
|
|
27
|
+
`@vmz/plugin-shiki` resolves Shiki from the application. `shiki`, `@shikijs/langs` and `@shikijs/themes` (`>=3`) are
|
|
28
|
+
required peer dependencies, not bundled dependencies, so install them alongside the plugin:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
pnpm add @vmz/plugin-shiki shiki
|
|
31
|
+
pnpm add @vmz/plugin-shiki shiki @shikijs/langs @shikijs/themes
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The runtime loads
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
The runtime loads `shiki/core` plus explicitly imported grammar and theme modules, so a client bundle only carries the
|
|
35
|
+
grammars this plugin can actually resolve instead of every `bundle-full` language. Supported grammars:
|
|
36
|
+
`javascript`, `typescript`, `tsx`, `jsx`, `json`, `html`, `css`, `markdown`, `bash` (plus `text`/`plaintext`, which
|
|
37
|
+
Shiki serves without a grammar module). Supported themes: `vitesse-dark` (default) and `vitesse-light`. A theme outside
|
|
38
|
+
that set is rejected with an explicit error instead of being silently swapped. A TextMate adapter adds custom grammars
|
|
39
|
+
on top; it does not replace the application-level `shiki` install.
|
|
37
40
|
|
|
38
41
|
## Third-party TextMate grammar (VMZ-2)
|
|
39
42
|
|
|
@@ -49,7 +52,8 @@ export default defineConfig({
|
|
|
49
52
|
```
|
|
50
53
|
|
|
51
54
|
The adapter specifier is host-resolved (serve-host resolve hooks on Node, an import map or vendor copy in the
|
|
52
|
-
browser) and never vendored by the packer; when it fails to load, the runtime falls back to
|
|
55
|
+
browser) and never vendored by the packer; when it fails to load, the runtime falls back to the built-in grammar set
|
|
56
|
+
above.
|
|
53
57
|
|
|
54
58
|
The plugin writes `dist/_vmz/plugin-shiki.config.json`; the runtime reads it in SSR (`VMZ_DIST`) and in the browser (`fetch`).
|
|
55
59
|
|
package/components/Shiki.vmz
CHANGED
|
@@ -9,11 +9,18 @@ export default class Shiki {
|
|
|
9
9
|
public lang: string = "text";
|
|
10
10
|
public theme: string = "vitesse-dark";
|
|
11
11
|
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* `null` means "no client payload": the sync path only fills it on the
|
|
14
|
+
* server (or after prewarm). Keeping it null lets the resumed host keep the
|
|
15
|
+
* SSR payload instead of replacing tokenized markup with a fallback shell.
|
|
16
|
+
*/
|
|
13
17
|
html: string | null = null;
|
|
14
18
|
|
|
15
19
|
async onMount() {
|
|
16
|
-
|
|
20
|
+
const next = await highlight(this.code, this.lang, this.theme);
|
|
21
|
+
// A degraded payload must not replace the server's tokenized markup:
|
|
22
|
+
// only an actual highlight is an improvement over what SSR wrote.
|
|
23
|
+
if (!next.includes('shiki-fallback')) this.html = next;
|
|
17
24
|
}
|
|
18
25
|
}
|
|
19
26
|
</script>
|
package/dist/runtime.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shiki highlight helper — async with optional sync cache after prewarm.
|
|
3
3
|
* TextMate grammar via configurable peer (default `vmz-textmate/shiki`).
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
5
|
+
import type { ShikiTransformer } from 'shiki';
|
|
6
6
|
export type ShikiRuntimeConfig = {
|
|
7
7
|
/**
|
|
8
8
|
* TextMate Shiki adapter module id (e.g. `vmz-textmate/shiki`). Explicit
|
|
@@ -14,7 +14,19 @@ export type ShikiRuntimeConfig = {
|
|
|
14
14
|
/** Default themes passed to the highlighter factory. */
|
|
15
15
|
themes?: string[];
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The highlight surface every adapter must provide. Kept structural instead of
|
|
19
|
+
* `Pick<Highlighter, 'codeToHtml'>`: the fine-grained entry returns a
|
|
20
|
+
* `HighlighterCore`, and both satisfy this shape.
|
|
21
|
+
*/
|
|
22
|
+
type CodeHighlighter = {
|
|
23
|
+
codeToHtml(code: string, options: {
|
|
24
|
+
lang: string;
|
|
25
|
+
theme: string;
|
|
26
|
+
transformers?: ShikiTransformer[];
|
|
27
|
+
structure?: 'classic' | 'inline';
|
|
28
|
+
}): string;
|
|
29
|
+
};
|
|
18
30
|
/** @internal test hook */
|
|
19
31
|
export declare function getShikiRuntimeConfig(): Readonly<ShikiRuntimeConfig>;
|
|
20
32
|
/** Reset module state (tests). */
|
|
@@ -27,8 +39,17 @@ export declare function prewarmShiki(opts?: {
|
|
|
27
39
|
themes?: string[];
|
|
28
40
|
}): Promise<CodeHighlighter>;
|
|
29
41
|
export declare function highlight(code: string, lang?: string, theme?: string | null): Promise<string>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Sync highlight when the runtime is prewarmed.
|
|
44
|
+
*
|
|
45
|
+
* `null` means "this runtime has no payload for this language yet". The server
|
|
46
|
+
* never has a better source than one it cannot resolve, so it degrades to the
|
|
47
|
+
* escaped shell. A browser with a cold cache (or a grammar the shipped generic
|
|
48
|
+
* path cannot resolve) must not invent one: the host may already hold the
|
|
49
|
+
* server's tokenized payload, and replacing it with an un-tokenized shell
|
|
50
|
+
* throws that payload away.
|
|
51
|
+
*/
|
|
52
|
+
export declare function highlightSync(code: string, lang?: string, theme?: string | null): string | null;
|
|
32
53
|
/**
|
|
33
54
|
* Inline-structure sync highlight for embedders that own the `<pre>` chrome
|
|
34
55
|
* (markdown-it fence callback in the document pipeline). Returns the token
|
package/dist/runtime.js
CHANGED
|
@@ -123,15 +123,62 @@ async function loadTextmateHighlighter(themes) {
|
|
|
123
123
|
}
|
|
124
124
|
return null;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Grammars the generic (non-TextMate) path can resolve. `shiki`'s root entry
|
|
128
|
+
* reaches `bundle-full`, which registers every bundled grammar and theme as its
|
|
129
|
+
* own dynamic import — 272 async chunks of unused `@shikijs/langs` grammars in
|
|
130
|
+
* the delivered pack. The fine-grained entry below keeps the reachable grammar
|
|
131
|
+
* set equal to what the runtime can actually resolve.
|
|
132
|
+
*
|
|
133
|
+
* `text` / `plaintext` / `txt` / `plain` need no module: Shiki treats them as
|
|
134
|
+
* plain text (`isPlainLang`).
|
|
135
|
+
*/
|
|
136
|
+
const LANG_MODULES = {
|
|
137
|
+
javascript: () => import('@shikijs/langs/javascript'),
|
|
138
|
+
typescript: () => import('@shikijs/langs/typescript'),
|
|
139
|
+
tsx: () => import('@shikijs/langs/tsx'),
|
|
140
|
+
jsx: () => import('@shikijs/langs/jsx'),
|
|
141
|
+
json: () => import('@shikijs/langs/json'),
|
|
142
|
+
html: () => import('@shikijs/langs/html'),
|
|
143
|
+
css: () => import('@shikijs/langs/css'),
|
|
144
|
+
markdown: () => import('@shikijs/langs/markdown'),
|
|
145
|
+
bash: () => import('@shikijs/langs/bash'),
|
|
146
|
+
};
|
|
147
|
+
/** Themes the generic path can resolve (`vitesse-dark` is the runtime default). */
|
|
148
|
+
const THEME_MODULES = {
|
|
149
|
+
'vitesse-dark': () => import('@shikijs/themes/vitesse-dark'),
|
|
150
|
+
'vitesse-light': () => import('@shikijs/themes/vitesse-light'),
|
|
151
|
+
};
|
|
126
152
|
async function loadGenericHighlighter(themes) {
|
|
127
|
-
// Literal
|
|
153
|
+
// Literal specifiers only: a computed dynamic import (variable argument) is
|
|
128
154
|
// invisible to the packer, so the browser chunk would keep a bare specifier
|
|
129
155
|
// and fail with "Failed to resolve module specifier 'shiki'". Literal
|
|
130
156
|
// imports let vmz-pack vendor the highlighter into an async client chunk.
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
157
|
+
for (const theme of themes) {
|
|
158
|
+
if (!THEME_MODULES[theme]) {
|
|
159
|
+
// Explicit rejection, no silent substitution: a theme outside the
|
|
160
|
+
// shipped set cannot be highlighted, and pretending otherwise would
|
|
161
|
+
// emit the wrong colours.
|
|
162
|
+
throw new Error(`@vmz/plugin-shiki: unsupported theme ${JSON.stringify(theme)} ` +
|
|
163
|
+
`(supported: ${Object.keys(THEME_MODULES).join(', ')})`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Every supported theme is registered at once: the highlighter is created
|
|
167
|
+
// once per process and per-call `theme` switches must not hit an
|
|
168
|
+
// unregistered theme. The requested list only decides what must be
|
|
169
|
+
// resolvable (validation above), not what is registered.
|
|
170
|
+
const [{ createHighlighterCore }, { createJavaScriptRegexEngine }, themeModules, langModules] = await Promise.all([
|
|
171
|
+
import('shiki/core'),
|
|
172
|
+
import('shiki/engine/javascript'),
|
|
173
|
+
Promise.all(Object.values(THEME_MODULES).map((load) => load())),
|
|
174
|
+
Promise.all(Object.values(LANG_MODULES).map((load) => load())),
|
|
175
|
+
]);
|
|
176
|
+
return createHighlighterCore({
|
|
177
|
+
themes: themeModules.map((module) => module.default),
|
|
178
|
+
langs: langModules.map((module) => module.default),
|
|
179
|
+
// Grammar fidelity comes from the same JS RegExp engine `vmz-textmate`
|
|
180
|
+
// ships: it needs no 608 KiB inlined Oniguruma wasm beside the chunk.
|
|
181
|
+
engine: createJavaScriptRegexEngine(),
|
|
135
182
|
});
|
|
136
183
|
}
|
|
137
184
|
export async function prewarmShiki(opts = {}) {
|
|
@@ -171,10 +218,19 @@ export async function highlight(code, lang = 'text', theme = DEFAULT_THEME) {
|
|
|
171
218
|
return fallbackPre(code);
|
|
172
219
|
}
|
|
173
220
|
}
|
|
174
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Sync highlight when the runtime is prewarmed.
|
|
223
|
+
*
|
|
224
|
+
* `null` means "this runtime has no payload for this language yet". The server
|
|
225
|
+
* never has a better source than one it cannot resolve, so it degrades to the
|
|
226
|
+
* escaped shell. A browser with a cold cache (or a grammar the shipped generic
|
|
227
|
+
* path cannot resolve) must not invent one: the host may already hold the
|
|
228
|
+
* server's tokenized payload, and replacing it with an un-tokenized shell
|
|
229
|
+
* throws that payload away.
|
|
230
|
+
*/
|
|
175
231
|
export function highlightSync(code, lang = 'text', theme = DEFAULT_THEME) {
|
|
176
232
|
if (!cached)
|
|
177
|
-
return fallbackPre(code);
|
|
233
|
+
return isServerRuntime() ? fallbackPre(code) : null;
|
|
178
234
|
try {
|
|
179
235
|
return cached.codeToHtml(code ?? '', {
|
|
180
236
|
lang: normalizeLang(lang),
|
|
@@ -183,9 +239,13 @@ export function highlightSync(code, lang = 'text', theme = DEFAULT_THEME) {
|
|
|
183
239
|
});
|
|
184
240
|
}
|
|
185
241
|
catch {
|
|
186
|
-
return fallbackPre(code);
|
|
242
|
+
return isServerRuntime() ? fallbackPre(code) : null;
|
|
187
243
|
}
|
|
188
244
|
}
|
|
245
|
+
/** No `document` means the serialize path (SSR) — see {@link highlightSync}. */
|
|
246
|
+
function isServerRuntime() {
|
|
247
|
+
return typeof document === 'undefined';
|
|
248
|
+
}
|
|
189
249
|
/**
|
|
190
250
|
* Inline-structure sync highlight for embedders that own the `<pre>` chrome
|
|
191
251
|
* (markdown-it fence callback in the document pipeline). Returns the token
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@vmz/plugin": "0.3.
|
|
3
|
+
"@vmz/plugin": "0.3.15"
|
|
4
4
|
},
|
|
5
5
|
"description": "VMZ Shiki adapter - <Shiki> + code engine registration",
|
|
6
6
|
"devDependencies": {
|
|
7
|
+
"@shikijs/langs": "^3.23.0",
|
|
8
|
+
"@shikijs/themes": "^3.23.0",
|
|
7
9
|
"@types/node": "^22.15.0",
|
|
8
10
|
"shiki": "^3.12.2",
|
|
9
11
|
"typescript": "^5.8.3",
|
|
10
|
-
"vmz-textmate": "0.3.
|
|
12
|
+
"vmz-textmate": "0.3.15"
|
|
11
13
|
},
|
|
12
14
|
"engines": {
|
|
13
15
|
"node": ">=24.12.0"
|
|
@@ -37,6 +39,8 @@
|
|
|
37
39
|
"main": "./dist/vmz.plugin.js",
|
|
38
40
|
"name": "@vmz/plugin-shiki",
|
|
39
41
|
"peerDependencies": {
|
|
42
|
+
"@shikijs/langs": ">=3",
|
|
43
|
+
"@shikijs/themes": ">=3",
|
|
40
44
|
"shiki": ">=3"
|
|
41
45
|
},
|
|
42
46
|
"publishConfig": {
|
|
@@ -52,5 +56,5 @@
|
|
|
52
56
|
},
|
|
53
57
|
"type": "module",
|
|
54
58
|
"types": "./dist/vmz.plugin.d.ts",
|
|
55
|
-
"version": "0.3.
|
|
59
|
+
"version": "0.3.15"
|
|
56
60
|
}
|