Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/migrate-web-icons-to-unplugin-icons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

web: Compile icons at build time so the bundled web UI only carries the icons it renders.
8 changes: 3 additions & 5 deletions apps/kimi-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@
"build": "vite build",
"typecheck": "vue-tsc --noEmit",
"test": "vitest run",
"check:style": "node scripts/check-style.mjs",
"gen:icons": "node scripts/gen-icon-data.mjs"
"check:style": "node scripts/check-style.mjs"
},
"dependencies": {
"@fontsource-variable/inter": "^5.2.8",
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@iconify-json/ri": "^1.2.10",
"@iconify/utils": "^3.1.3",
"@iconify/vue": "^5.0.1",
"@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0",
"katex": "^0.17.0",
Expand All @@ -30,8 +26,10 @@
"vue-i18n": "^11.4.5"
},
"devDependencies": {
"@iconify-json/ri": "^1.2.10",
"@vitejs/plugin-vue": "^5.2.4",
"typescript": "6.0.2",
"unplugin-icons": "^23.0.0",
Comment thread
liruifengv marked this conversation as resolved.
"vite": "^6.3.3",
"vitest": "4.1.4",
"vue-tsc": "~3.2.0",
Expand Down
12 changes: 6 additions & 6 deletions apps/kimi-web/scripts/check-style.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ const DOMAIN_HEX_EXEMPT = new Set([
'Terminal.vue',
]);

// Files that legitimately render their own <svg>: the icon primitive itself,
// bespoke data-viz / colored illustrations, the spinner, and brand marks
// (the Kimi wordmark on the loading screen). Everything else should use
// lib/icons.ts via <Icon>/iconSvg(). The 32x22 Kimi eye logo is also exempted
// inline (matched by viewBox).
// Files that legitimately render their own <svg>: bespoke data-viz / colored
// illustrations, the spinner, and brand marks (the Kimi wordmark on the loading
// screen). Everything else should use lib/icons.ts via <Icon>/iconSvg(). The
// 32x22 Kimi eye logo is also exempted inline (matched by viewBox). The icon
// primitive (components/ui/Icon.vue) itself renders no hand-written <svg>, so it
// is not exempted here.
const ICON_EXEMPT = new Set([
'components/ui/Icon.vue',
'components/ui/Spinner.vue',
'components/ui/MoonSpinner.vue',
'components/ui/ContextRing.vue',
Expand Down
40 changes: 0 additions & 40 deletions apps/kimi-web/scripts/gen-icon-catalog.mjs

This file was deleted.

156 changes: 0 additions & 156 deletions apps/kimi-web/scripts/gen-icon-data.mjs

This file was deleted.

30 changes: 5 additions & 25 deletions apps/kimi-web/src/components/ui/Icon.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- apps/kimi-web/src/components/ui/Icon.vue -->
<!-- Design-system §02 icon primitive. Renders a registered line icon from
lib/icons.ts at a token size. Use everywhere instead of hand-writing <svg>. -->
lib/icons.ts at a token size. Use everywhere instead of hand-writing raw SVG. -->
<script setup lang="ts">
import { computed } from 'vue';
import { getIcon, SIZE_PX, type IconName, type IconSize } from '../../lib/icons';
Expand All @@ -15,38 +15,18 @@ const props = withDefaults(
{ size: 'md' },
);

const def = computed(() => getIcon(props.name));
const entry = computed(() => getIcon(props.name));
const px = computed(() => SIZE_PX[props.size]);
const viewBox = computed(() => def.value.viewBox ?? '0 0 16 16');
</script>

<template>
<svg
v-if="def.fill"
<component
v-if="entry"
:is="entry.component"
class="kw-icon"
:width="px"
:height="px"
:viewBox="viewBox"
fill="currentColor"
:aria-label="label"
:aria-hidden="label ? undefined : true"
xmlns="http://www.w3.org/2000/svg"
v-html="def.body"
/>
<svg
v-else
class="kw-icon"
:width="px"
:height="px"
:viewBox="viewBox"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
:aria-label="label"
:aria-hidden="label ? undefined : true"
xmlns="http://www.w3.org/2000/svg"
v-html="def.body"
/>
</template>
11 changes: 11 additions & 0 deletions apps/kimi-web/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,14 @@ declare module '*?worker&type=module' {
const WorkerFactory: new () => Worker;
export default WorkerFactory;
}

// unplugin-icons `?raw` imports — `unplugin-icons/types/vue` declares
// `~icons/*` as a Vue FunctionalComponent (for direct component imports). The
// `?raw` query re-exports the raw SVG source, which must type as `string`;
// this more-specific pattern overrides the component declaration for `?raw`
// imports only (e.g. `~icons/ri/add-line?raw`), leaving component imports
// (`~icons/ri/add-line`) typed as components.
declare module '~icons/*?raw' {
Comment thread
liruifengv marked this conversation as resolved.
const src: string;
export default src;
}
Loading
Loading