diff --git a/CHANGELOG.md b/CHANGELOG.md index 86e4d2085525..2eb5e27ae320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Control UI: split large build-time runtime dependencies into stable chunks so Linux/Docker install and package builds stay below the app chunk warning threshold. - Scripts: run the optional Discord native opus installer through the shared pnpm launcher and Windows CI coverage so native Windows installs avoid shell-mode package-manager shims. - Agents/MCP: bound bundled MCP `tools/list` catalog discovery so hung MCP servers do not block session tool materialization. (#85063) Thanks @nxmxbbd. - Scripts: run generated-module formatting through the shared pnpm launcher and Windows CI coverage so native Windows generator checks avoid shell-mode package-manager shims. diff --git a/ui/build/chunking.ts b/ui/build/chunking.ts new file mode 100644 index 000000000000..2c6d5f4775ae --- /dev/null +++ b/ui/build/chunking.ts @@ -0,0 +1,49 @@ +export function normalizeModuleId(id: string): string { + return id.replace(/\\/g, "/"); +} + +export function moduleIdIncludesPackage(id: string, packageName: string): boolean { + const normalized = normalizeModuleId(id); + return ( + normalized.includes(`/node_modules/${packageName}/`) || + normalized.includes(`/openclaw-pnpm-node-modules/${packageName}/`) + ); +} + +export function controlUiManualChunk(id: string): string | undefined { + if ( + moduleIdIncludesPackage(id, "lit") || + moduleIdIncludesPackage(id, "lit-html") || + moduleIdIncludesPackage(id, "@lit/reactive-element") + ) { + return "lit-runtime"; + } + + if ( + moduleIdIncludesPackage(id, "highlight.js") || + moduleIdIncludesPackage(id, "markdown-it") || + moduleIdIncludesPackage(id, "markdown-it-task-lists") || + moduleIdIncludesPackage(id, "dompurify") || + moduleIdIncludesPackage(id, "entities") || + moduleIdIncludesPackage(id, "linkify-it") || + moduleIdIncludesPackage(id, "mdurl") || + moduleIdIncludesPackage(id, "punycode.js") || + moduleIdIncludesPackage(id, "uc.micro") + ) { + return "markdown-runtime"; + } + + if (moduleIdIncludesPackage(id, "zod") || moduleIdIncludesPackage(id, "json5")) { + return "config-runtime"; + } + + if ( + moduleIdIncludesPackage(id, "@noble/ed25519") || + moduleIdIncludesPackage(id, "@noble/hashes") || + moduleIdIncludesPackage(id, "ipaddr.js") + ) { + return "gateway-runtime"; + } + + return undefined; +} diff --git a/ui/src/ui/control-ui-chunking.test.ts b/ui/src/ui/control-ui-chunking.test.ts new file mode 100644 index 000000000000..e8423a2b9d03 --- /dev/null +++ b/ui/src/ui/control-ui-chunking.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; +import { controlUiManualChunk, normalizeModuleId } from "../../build/chunking.ts"; + +describe("Control UI build chunking", () => { + it("groups stable runtime dependencies into bounded chunks", () => { + expect(controlUiManualChunk("/repo/ui/node_modules/lit/index.js")).toBe("lit-runtime"); + expect(controlUiManualChunk("/repo/ui/node_modules/lit-html/directives/repeat.js")).toBe( + "lit-runtime", + ); + expect(controlUiManualChunk("/repo/ui/node_modules/highlight.js/lib/core.js")).toBe( + "markdown-runtime", + ); + expect( + controlUiManualChunk("/tmp/openclaw-pnpm-node-modules/dompurify/dist/purify.es.mjs"), + ).toBe("markdown-runtime"); + expect(controlUiManualChunk("/tmp/openclaw-pnpm-node-modules/zod/v4/core/schemas.js")).toBe( + "config-runtime", + ); + expect(controlUiManualChunk("/tmp/openclaw-pnpm-node-modules/json5/dist/index.js")).toBe( + "config-runtime", + ); + expect(controlUiManualChunk("/tmp/openclaw-pnpm-node-modules/@noble/ed25519/index.js")).toBe( + "gateway-runtime", + ); + expect(controlUiManualChunk("/repo/ui/src/ui/app-render.ts")).toBeUndefined(); + }); + + it("normalizes Windows module paths before package matching", () => { + expect(normalizeModuleId(String.raw`C:\repo\ui\node_modules\highlight.js\lib\core.js`)).toBe( + "C:/repo/ui/node_modules/highlight.js/lib/core.js", + ); + expect(controlUiManualChunk(String.raw`C:\repo\ui\node_modules\highlight.js\lib\core.js`)) + .toBe("markdown-runtime"); + }); +}); diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 351f7a1c52c6..3700de38cde4 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -3,6 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig, type Plugin } from "vite"; +import { controlUiManualChunk } from "./build/chunking.ts"; const here = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(here, ".."); @@ -98,7 +99,12 @@ export default defineConfig(() => { outDir, emptyOutDir: true, sourcemap: true, - // Keep CI/onboard logs clean; current control UI chunking is intentionally above 500 kB. + rollupOptions: { + output: { + manualChunks: controlUiManualChunk, + }, + }, + // Keep CI/onboard logs clean; the app chunk is split into stable runtime buckets above. chunkSizeWarningLimit: 1024, }, server: {