From 0f8242d17b7470bfb6284e3d166516e33b3d9bd6 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 13 Jun 2026 05:22:19 +0000 Subject: [PATCH 1/2] Externalize library dependencies Avoid emitting dependency modules under dist/node_modules during library builds by deriving externals from package dependencies and peers. Co-authored-by: openhands --- vite.config.ts | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index d52f5d812..09cfa5d61 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -16,20 +16,45 @@ import { } from "./src/styles/agent-server-ui-style-scope"; const LIB_ENTRY = fileURLToPath(new URL("./src/index.ts", import.meta.url)); -const LIB_EXTERNALS = [ - "react", - "react-dom", - "react/jsx-runtime", - "react/jsx-dev-runtime", - "react-router", +const _require = createRequire(import.meta.url); +const PACKAGE_JSON = _require("./package.json") as { + dependencies?: Record; + optionalDependencies?: Record; + peerDependencies?: Record; +}; +const LIB_EXTERNAL_PACKAGE_NAMES = [ + ...new Set([ + ...Object.keys(PACKAGE_JSON.dependencies ?? {}), + ...Object.keys(PACKAGE_JSON.optionalDependencies ?? {}), + ...Object.keys(PACKAGE_JSON.peerDependencies ?? {}), + "react/jsx-runtime", + "react/jsx-dev-runtime", + ]), ]; +const POSTHOG_REACT_IMPORT_ID = "posthog-js/react"; +const POSTHOG_REACT_ESM_ENTRY = "posthog-js/react/dist/esm/index.js"; + +function isLibraryExternal(id: string) { + if ( + id.startsWith("\0") || + id.startsWith(".") || + id.startsWith("/") || + id.startsWith("#/") + ) { + return false; + } + + return LIB_EXTERNAL_PACKAGE_NAMES.some( + (packageName) => id === packageName || id.startsWith(`${packageName}/`), + ); +} + const APP_CHUNK_MAX_BYTES = 450 * 1024; // Absolute path to the bundled extensions skills directory in node_modules. // Injected as __EXTENSIONS_SKILLS_DIR__ so agent-server-adapter.ts can pass // real filesystem paths to the Python agent-server (which uses them to // resolve bundled skill resources like scripts/ and references/). -const _require = createRequire(import.meta.url); const EXTENSIONS_SKILLS_DIR = resolve( dirname(_require.resolve("@openhands/extensions/package.json")), "skills", @@ -177,7 +202,7 @@ export default defineConfig(({ mode }) => { formats: ["es"], }, rollupOptions: { - external: LIB_EXTERNALS, + external: isLibraryExternal, output: [ { format: "es", @@ -186,6 +211,8 @@ export default defineConfig(({ mode }) => { entryFileNames: "[name].js", chunkFileNames: "chunks/[name]-[hash].js", assetFileNames: "assets/[name]-[hash][extname]", + paths: (id) => + id === POSTHOG_REACT_IMPORT_ID ? POSTHOG_REACT_ESM_ENTRY : id, }, { format: "cjs", From 114d7679f302faaefca8f391bdb71a5d3731f916 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 13 Jun 2026 05:36:08 +0000 Subject: [PATCH 2/2] Update Vite config test for external matcher Assert the library build externalization function preserves package and subpath externals while keeping internal imports bundled. Co-authored-by: openhands --- __tests__/vite-config.test.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/__tests__/vite-config.test.ts b/__tests__/vite-config.test.ts index daa800abc..e710b44f4 100644 --- a/__tests__/vite-config.test.ts +++ b/__tests__/vite-config.test.ts @@ -54,7 +54,9 @@ describe("vite app build", () => { }; }; - expect(appBuild.build?.rolldownOptions?.output?.codeSplitting?.groups).toEqual( + expect( + appBuild.build?.rolldownOptions?.output?.codeSplitting?.groups, + ).toEqual( expect.arrayContaining([ expect.objectContaining({ name: "vendor", @@ -76,9 +78,20 @@ describe("vite library build", () => { expect(config.build?.lib).toMatchObject({ formats: ["es"], }); - expect(config.build?.rollupOptions?.external).toEqual( - expect.arrayContaining(["react", "react-dom", "react-router"]), + const external = config.build?.rollupOptions?.external; + expect(typeof external).toBe("function"); + if (typeof external !== "function") { + throw new Error("Expected library external matcher to be a function"); + } + expect(external("react", undefined, false)).toBe(true); + expect(external("react-dom", undefined, false)).toBe(true); + expect(external("react-router", undefined, false)).toBe(true); + expect(external("@openhands/extensions/skills", undefined, false)).toBe( + true, ); + expect(external("react-icons/fa6", undefined, false)).toBe(true); + expect(external("#/i18n/declaration", undefined, false)).toBe(false); + expect(external("./local-module", undefined, false)).toBe(false); expect(config.build?.rollupOptions?.output).toEqual( expect.arrayContaining([ expect.objectContaining({