Skip to content

Commit 59f7935

Browse files
fix: Jest module resolution, reduce Shiki bundle 54%, outline button bg (#221)
1 parent 4d6de27 commit 59f7935

File tree

14 files changed

+231
-65
lines changed

14 files changed

+231
-65
lines changed

.changeset/fix-chunk-filenames.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@cloudflare/kumo": patch
3+
---
4+
5+
Fix Jest module resolution failures and reduce Shiki bundle size
6+
7+
**Jest Fix:**
8+
- Add `chunkFileNames` config to prevent double-dash filenames (e.g., `combobox--ec3iibR.js`) that Jest cannot resolve
9+
- Move chunks to `dist/chunks/` subdirectory for cleaner structure
10+
11+
**Bundle Size Reduction:**
12+
- Switch from full Shiki bundle to fine-grained imports via `shiki/core`
13+
- Reduce from ~300 language/theme chunks to ~16 bundled languages
14+
- Total JS files reduced from 358 to ~163 (54% reduction)
15+
16+
**Supported Languages:**
17+
`javascript`, `typescript`, `jsx`, `tsx`, `json`, `jsonc`, `html`, `css`, `python`, `yaml`, `markdown`, `graphql`, `sql`, `bash`, `shell`, `diff`
18+
19+
**Breaking Change:**
20+
- `BundledLanguage` type is now deprecated, use `SupportedLanguage` instead
21+
- Only the languages listed above are bundled; other Shiki languages are no longer available out of the box

packages/kumo-docs-astro/src/pages/components/code-highlighted.astro

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,14 @@ import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
212212
<ComponentSection>
213213
<Heading level={2}>Overview</Heading>
214214
<p class="text-kumo-strong">
215-
A Shiki-powered syntax highlighter. Supports 200+ languages with TextMate grammars,
215+
A Shiki-powered syntax highlighter with TextMate grammars,
216216
dual light/dark themes, and lazy loading. Exported from a separate entry point
217217
(<code class="rounded bg-kumo-control px-1 py-0.5 text-xs">@cloudflare/kumo/code</code>)
218218
to avoid bundling Shiki for apps that don't need it.
219219
</p>
220+
<p class="mt-2 text-sm text-kumo-subtle">
221+
<strong>Supported languages:</strong> javascript, typescript, jsx, tsx, json, jsonc, html, css, python, yaml, markdown, graphql, sql, bash, shell, diff
222+
</p>
220223
</ComponentSection>
221224

222225
<!-- Installation -->

packages/kumo/ai/component-registry.json

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,11 @@
318318
},
319319
"classes": {
320320
"primary": "bg-kumo-brand !text-white hover:bg-kumo-brand-hover focus:bg-kumo-brand-hover disabled:bg-kumo-brand/50",
321-
"secondary": "bg-kumo-control !text-kumo-default ring not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-control disabled:bg-kumo-control/50 disabled:!text-kumo-default/70 ring-kumo-line data-[state=open]:bg-kumo-control",
321+
"secondary": "bg-kumo-base !text-kumo-default ring not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-tint disabled:bg-kumo-base/50 disabled:!text-kumo-default/70 ring-kumo-ring data-[state=open]:bg-kumo-base",
322322
"ghost": "text-kumo-default hover:bg-kumo-tint shadow-none bg-inherit",
323323
"destructive": "bg-kumo-danger !text-white hover:bg-kumo-danger/70",
324-
"secondary-destructive": "bg-kumo-control !text-kumo-danger ring not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-control disabled:bg-kumo-control/50 disabled:!text-kumo-danger/70 ring-kumo-line data-[state=open]:bg-kumo-control",
325-
"outline": "bg-kumo-base text-kumo-default ring ring-kumo-line"
324+
"secondary-destructive": "bg-kumo-base !text-kumo-danger ring not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-base disabled:bg-kumo-base/50 disabled:!text-kumo-danger/70 ring-kumo-line data-[state=open]:bg-kumo-base",
325+
"outline": "text-kumo-default ring-2 ring-kumo-ring"
326326
},
327327
"stateClasses": {
328328
"primary": {
@@ -331,9 +331,9 @@
331331
"disabled": "disabled:bg-kumo-brand/50"
332332
},
333333
"secondary": {
334-
"not-disabled": "not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-control",
335-
"disabled": "disabled:bg-kumo-control/50 disabled:!text-kumo-default/70",
336-
"data-state": "data-[state=open]:bg-kumo-control"
334+
"not-disabled": "not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-tint",
335+
"disabled": "disabled:bg-kumo-base/50 disabled:!text-kumo-default/70",
336+
"data-state": "data-[state=open]:bg-kumo-base"
337337
},
338338
"ghost": {
339339
"hover": "hover:bg-kumo-tint"
@@ -342,9 +342,9 @@
342342
"hover": "hover:bg-kumo-danger/70"
343343
},
344344
"secondary-destructive": {
345-
"not-disabled": "not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-control",
346-
"disabled": "disabled:bg-kumo-control/50 disabled:!text-kumo-danger/70",
347-
"data-state": "data-[state=open]:bg-kumo-control"
345+
"not-disabled": "not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-base",
346+
"disabled": "disabled:bg-kumo-base/50 disabled:!text-kumo-danger/70",
347+
"data-state": "data-[state=open]:bg-kumo-base"
348348
}
349349
},
350350
"default": "secondary"
@@ -419,7 +419,6 @@
419419
"bg-kumo-base",
420420
"bg-kumo-brand",
421421
"bg-kumo-brand-hover",
422-
"bg-kumo-control",
423422
"bg-kumo-danger",
424423
"bg-kumo-tint",
425424
"ring-kumo-line",

packages/kumo/ai/component-registry.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,17 +291,17 @@ Primary action trigger. Supports multiple variants, sizes, shapes, icons, and lo
291291
- `focus`: `focus:bg-kumo-brand-hover`
292292
- `disabled`: `disabled:bg-kumo-brand/50`
293293
- `"secondary"`:
294-
- `not-disabled`: `not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-control`
295-
- `disabled`: `disabled:bg-kumo-control/50 disabled:!text-kumo-default/70`
296-
- `data-state`: `data-[state=open]:bg-kumo-control`
294+
- `not-disabled`: `not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-tint`
295+
- `disabled`: `disabled:bg-kumo-base/50 disabled:!text-kumo-default/70`
296+
- `data-state`: `data-[state=open]:bg-kumo-base`
297297
- `"ghost"`:
298298
- `hover`: `hover:bg-kumo-tint`
299299
- `"destructive"`:
300300
- `hover`: `hover:bg-kumo-danger/70`
301301
- `"secondary-destructive"`:
302-
- `not-disabled`: `not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-control`
303-
- `disabled`: `disabled:bg-kumo-control/50 disabled:!text-kumo-danger/70`
304-
- `data-state`: `data-[state=open]:bg-kumo-control`
302+
- `not-disabled`: `not-disabled:hover:border-secondary! not-disabled:hover:bg-kumo-base`
303+
- `disabled`: `disabled:bg-kumo-base/50 disabled:!text-kumo-danger/70`
304+
- `data-state`: `data-[state=open]:bg-kumo-base`
305305
- `children`: ReactNode
306306
- `className`: string
307307
- `icon`: ReactNode
@@ -318,7 +318,7 @@ Primary action trigger. Supports multiple variants, sizes, shapes, icons, and lo
318318

319319
**Colors (kumo tokens used):**
320320

321-
`bg-kumo-base`, `bg-kumo-brand`, `bg-kumo-brand-hover`, `bg-kumo-control`, `bg-kumo-danger`, `bg-kumo-tint`, `ring-kumo-line`, `ring-kumo-ring`, `text-kumo-danger`, `text-kumo-default`, `text-kumo-subtle`
321+
`bg-kumo-base`, `bg-kumo-brand`, `bg-kumo-brand-hover`, `bg-kumo-danger`, `bg-kumo-tint`, `ring-kumo-line`, `ring-kumo-ring`, `text-kumo-danger`, `text-kumo-default`, `text-kumo-subtle`
322322

323323
**Examples:**
324324

packages/kumo/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@
444444
},
445445
"dependencies": {
446446
"@base-ui/react": "^1.2.0",
447+
"@shikijs/langs": "^4.0.0",
448+
"@shikijs/themes": "^4.0.0",
447449
"clsx": "^2.1.1",
448450
"motion": "^12.34.1",
449451
"react-day-picker": "^9.13.2",

packages/kumo/src/code/context.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { createContext } from "react";
2-
import type { Highlighter } from "shiki";
3-
import type { BundledLanguage, CodeHighlightedLabels } from "./types";
2+
import type { HighlighterCore } from "shiki/core";
3+
import type { SupportedLanguage, CodeHighlightedLabels } from "./types";
44

55
export interface ShikiContextValue {
66
/** The initialized Shiki highlighter instance */
7-
highlighter: Highlighter | null;
7+
highlighter: HighlighterCore | null;
88

99
/** True while Shiki is loading */
1010
isLoading: boolean;
@@ -13,7 +13,7 @@ export interface ShikiContextValue {
1313
error: Error | null;
1414

1515
/** Configured languages */
16-
languages: BundledLanguage[];
16+
languages: SupportedLanguage[];
1717

1818
/** Localized labels for UI elements */
1919
labels: CodeHighlightedLabels;

packages/kumo/src/code/provider.tsx

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,33 @@
22

33
import { useState, useEffect, useMemo, type ReactNode } from "react";
44
import { ShikiContext, type ShikiContextValue } from "./context";
5-
import type { ShikiProviderProps, BundledLanguage } from "./types";
5+
import type { ShikiProviderProps, SupportedLanguage } from "./types";
6+
7+
/**
8+
* Pre-bundled languages - only these languages are included in the Kumo bundle.
9+
* Using fine-grained imports from @shikijs/langs to minimize bundle size.
10+
*/
11+
const BUNDLED_LANGS: Record<
12+
SupportedLanguage,
13+
() => Promise<{ default: unknown }>
14+
> = {
15+
javascript: () => import("@shikijs/langs/javascript"),
16+
typescript: () => import("@shikijs/langs/typescript"),
17+
jsx: () => import("@shikijs/langs/jsx"),
18+
tsx: () => import("@shikijs/langs/tsx"),
19+
json: () => import("@shikijs/langs/json"),
20+
jsonc: () => import("@shikijs/langs/jsonc"),
21+
html: () => import("@shikijs/langs/html"),
22+
css: () => import("@shikijs/langs/css"),
23+
python: () => import("@shikijs/langs/python"),
24+
yaml: () => import("@shikijs/langs/yaml"),
25+
markdown: () => import("@shikijs/langs/markdown"),
26+
graphql: () => import("@shikijs/langs/graphql"),
27+
sql: () => import("@shikijs/langs/sql"),
28+
bash: () => import("@shikijs/langs/bash"),
29+
shell: () => import("@shikijs/langs/shellscript"),
30+
diff: () => import("@shikijs/langs/diff"),
31+
};
632

733
/**
834
* Provider component that initializes and manages Shiki highlighting.
@@ -55,8 +81,8 @@ export function ShikiProvider({
5581

5682
async function initializeShiki() {
5783
try {
58-
// Dynamic import — Shiki is only loaded when this effect runs
59-
const { createHighlighter } = await import("shiki");
84+
// Dynamic import of shiki/core — only loads the core, not all languages
85+
const { createHighlighterCore } = await import("shiki/core");
6086

6187
// Load the appropriate engine
6288
const engineInstance =
@@ -68,9 +94,25 @@ export function ShikiProvider({
6894
m.createJavaScriptRegexEngine(),
6995
);
7096

71-
const highlighter = await createHighlighter({
72-
themes: ["github-light", "vesper"],
73-
langs: languages,
97+
// Load themes
98+
const [githubLight, vesper] = await Promise.all([
99+
import("@shikijs/themes/github-light"),
100+
import("@shikijs/themes/vesper"),
101+
]);
102+
103+
// Load only the requested languages from our bundled set
104+
const validLanguages = languages.filter(
105+
(lang): lang is SupportedLanguage => lang in BUNDLED_LANGS,
106+
);
107+
108+
const langModules = await Promise.all(
109+
validLanguages.map((lang) => BUNDLED_LANGS[lang]()),
110+
);
111+
112+
const highlighter = await createHighlighterCore({
113+
themes: [githubLight.default, vesper.default],
114+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
115+
langs: langModules.map((m) => m.default) as any,
74116
engine: engineInstance,
75117
});
76118

@@ -110,7 +152,7 @@ export function ShikiProvider({
110152
highlighter: state.highlighter,
111153
isLoading: state.isLoading,
112154
error: state.error,
113-
languages: languages as BundledLanguage[],
155+
languages: languages as SupportedLanguage[],
114156
labels: mergedLabels,
115157
}),
116158
[state.highlighter, state.isLoading, state.error, languages, mergedLabels],

packages/kumo/src/code/server.tsx

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,36 @@
1818
* ```
1919
*/
2020

21-
import type { Highlighter, BundledLanguage } from "shiki";
22-
import type { ShikiEngine } from "./types";
21+
import type { HighlighterCore } from "shiki/core";
22+
import type { ShikiEngine, SupportedLanguage } from "./types";
2323
import type { ReactNode } from "react";
2424

25+
/**
26+
* Pre-bundled languages - only these languages are included in the Kumo bundle.
27+
* Using fine-grained imports from @shikijs/langs to minimize bundle size.
28+
*/
29+
const BUNDLED_LANGS: Record<
30+
SupportedLanguage,
31+
() => Promise<{ default: unknown }>
32+
> = {
33+
javascript: () => import("@shikijs/langs/javascript"),
34+
typescript: () => import("@shikijs/langs/typescript"),
35+
jsx: () => import("@shikijs/langs/jsx"),
36+
tsx: () => import("@shikijs/langs/tsx"),
37+
json: () => import("@shikijs/langs/json"),
38+
jsonc: () => import("@shikijs/langs/jsonc"),
39+
html: () => import("@shikijs/langs/html"),
40+
css: () => import("@shikijs/langs/css"),
41+
python: () => import("@shikijs/langs/python"),
42+
yaml: () => import("@shikijs/langs/yaml"),
43+
markdown: () => import("@shikijs/langs/markdown"),
44+
graphql: () => import("@shikijs/langs/graphql"),
45+
sql: () => import("@shikijs/langs/sql"),
46+
bash: () => import("@shikijs/langs/bash"),
47+
shell: () => import("@shikijs/langs/shellscript"),
48+
diff: () => import("@shikijs/langs/diff"),
49+
};
50+
2551
export interface HighlightCodeOptions {
2652
/** Highlighting engine (default: "javascript") */
2753
engine?: ShikiEngine;
@@ -31,12 +57,12 @@ export interface CreateHighlighterOptions {
3157
/** Highlighting engine (default: "javascript") */
3258
engine?: ShikiEngine;
3359
/** Languages to support */
34-
languages: BundledLanguage[];
60+
languages: SupportedLanguage[];
3561
}
3662

3763
export interface ServerHighlighter {
3864
/** Highlight code and return HTML string */
39-
highlight: (code: string, lang: BundledLanguage) => string;
65+
highlight: (code: string, lang: SupportedLanguage) => string;
4066
/** Dispose the highlighter when done */
4167
dispose: () => void;
4268
}
@@ -56,10 +82,10 @@ export interface ServerHighlighter {
5682
*/
5783
export async function highlightCode(
5884
code: string,
59-
lang: BundledLanguage,
85+
lang: SupportedLanguage,
6086
options: HighlightCodeOptions = {},
6187
): Promise<string> {
62-
const { createHighlighter } = await import("shiki");
88+
const { createHighlighterCore } = await import("shiki/core");
6389

6490
const engine = options.engine ?? "javascript";
6591
const engineInstance =
@@ -71,9 +97,19 @@ export async function highlightCode(
7197
m.createJavaScriptRegexEngine(),
7298
);
7399

74-
const highlighter = await createHighlighter({
75-
themes: ["github-light", "vesper"],
76-
langs: [lang],
100+
// Load themes
101+
const [githubLight, vesper] = await Promise.all([
102+
import("@shikijs/themes/github-light"),
103+
import("@shikijs/themes/vesper"),
104+
]);
105+
106+
// Load only the requested language
107+
const langModule = await BUNDLED_LANGS[lang]();
108+
109+
const highlighter = await createHighlighterCore({
110+
themes: [githubLight.default, vesper.default],
111+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
112+
langs: [langModule.default] as any,
77113
engine: engineInstance,
78114
});
79115

@@ -113,7 +149,7 @@ export async function highlightCode(
113149
export async function createServerHighlighter(
114150
options: CreateHighlighterOptions,
115151
): Promise<ServerHighlighter> {
116-
const { createHighlighter } = await import("shiki");
152+
const { createHighlighterCore } = await import("shiki/core");
117153

118154
const engine = options.engine ?? "javascript";
119155
const engineInstance =
@@ -125,14 +161,30 @@ export async function createServerHighlighter(
125161
m.createJavaScriptRegexEngine(),
126162
);
127163

128-
const highlighter: Highlighter = await createHighlighter({
129-
themes: ["github-light", "vesper"],
130-
langs: options.languages,
164+
// Load themes
165+
const [githubLight, vesper] = await Promise.all([
166+
import("@shikijs/themes/github-light"),
167+
import("@shikijs/themes/vesper"),
168+
]);
169+
170+
// Load only the requested languages from our bundled set
171+
const validLanguages = options.languages.filter(
172+
(lang): lang is SupportedLanguage => lang in BUNDLED_LANGS,
173+
);
174+
175+
const langModules = await Promise.all(
176+
validLanguages.map((lang) => BUNDLED_LANGS[lang]()),
177+
);
178+
179+
const highlighter: HighlighterCore = await createHighlighterCore({
180+
themes: [githubLight.default, vesper.default],
181+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
182+
langs: langModules.map((m) => m.default) as any,
131183
engine: engineInstance,
132184
});
133185

134186
return {
135-
highlight: (code: string, lang: BundledLanguage): string => {
187+
highlight: (code: string, lang: SupportedLanguage): string => {
136188
return highlighter.codeToHtml(code, {
137189
lang,
138190
themes: {

0 commit comments

Comments
 (0)