From 6e070151363d240900b5004716d69d111968e085 Mon Sep 17 00:00:00 2001 From: Arno V Date: Sun, 1 Feb 2026 10:58:30 -0500 Subject: [PATCH] fix(bundlecheck): Store and use actual_externals in cache Add an actual_externals column to the bundle_cache schema (with migration) and persist computed externals when caching results. getCachedResult now prefers actual_externals (falling back to legacy externals) and setCachedResult stores sorted actual_externals. A test was added to ensure cached results return the computed externals (e.g. auto-detected react/react-dom) when the cache key externals differ from the result externals. Backwards compatibility for older cache rows is preserved. --- .../bundlecheck/src/__tests__/cache.test.ts | 37 +++++++++++++++++++ packages/bundlecheck/src/cache.ts | 25 +++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/packages/bundlecheck/src/__tests__/cache.test.ts b/packages/bundlecheck/src/__tests__/cache.test.ts index 46c7325c..5a3d0021 100644 --- a/packages/bundlecheck/src/__tests__/cache.test.ts +++ b/packages/bundlecheck/src/__tests__/cache.test.ts @@ -411,6 +411,43 @@ describe("setCachedResult and getCachedResult", () => { expect(getCachedResult(keyBrowser)?.rawSize).toBe(1000); expect(getCachedResult(keyNode)?.rawSize).toBe(2000); }); + + it("should preserve actual externals when key externals differ from result externals", () => { + /** + * This tests the scenario where: + * - User doesn't provide --external flag (key.externals = []) + * - But the bundler auto-detects react/react-dom from package.json + * (result.externals = ["react", "react-dom"]) + * The cached result should return the actual computed externals, not the empty key externals. + */ + const key = normalizeCacheKey({ + packageName: "@mantine/core", + version: "8.0.0", + exports: ["Button"], + platform: "browser", + gzipLevel: 5, + externals: [], // User didn't pass --external flag + noExternal: false, + }); + + const resultWithComputedExternals: BundleResult = { + packageName: "@mantine/core", + packageVersion: "8.0.0", + exports: ["Button"], + rawSize: 50000, + gzipSize: 12000, + gzipLevel: 5, + externals: ["react", "react-dom"], // Auto-detected from package dependencies + dependencies: ["@mantine/hooks", "react", "react-dom"], + platform: "browser", + }; + + setCachedResult(key, resultWithComputedExternals); + const cached = getCachedResult(key); + + // The cached result should have the actual computed externals, not empty + expect(cached?.externals).toEqual(["react", "react-dom"]); + }); }); describe("clearCache", () => { diff --git a/packages/bundlecheck/src/cache.ts b/packages/bundlecheck/src/cache.ts index c5c67adb..1ab6e17a 100644 --- a/packages/bundlecheck/src/cache.ts +++ b/packages/bundlecheck/src/cache.ts @@ -49,6 +49,7 @@ type CacheRow = { dependencies: string; display_name: string; created_at: number; + actual_externals: string; }; let db: Database.Database | null = null; @@ -89,12 +90,22 @@ export function initCache(): Database.Database { dependencies TEXT NOT NULL DEFAULT '[]', display_name TEXT NOT NULL, created_at INTEGER NOT NULL, + actual_externals TEXT NOT NULL DEFAULT '', UNIQUE(package_name, version, exports, platform, gzip_level, externals, no_external) ); CREATE INDEX IF NOT EXISTS idx_created_at ON bundle_cache(created_at); `); + // Migration: Add actual_externals column if it doesn't exist (for existing databases). + try { + db.exec( + `ALTER TABLE bundle_cache ADD COLUMN actual_externals TEXT NOT NULL DEFAULT ''`, + ); + } catch { + // Column already exists, ignore error. + } + return db; } @@ -178,7 +189,12 @@ export function getCachedResult(key: CacheKey): CachedBundleResult | null { dependencies = []; } - // Convert row to BundleResult. + /** + * Convert row to BundleResult. + * Use actual_externals (computed externals including auto-detected react/react-dom) + * if available, otherwise fall back to externals (for old cache entries). + */ + const externalsStr = row.actual_externals || row.externals; return { packageName: row.display_name, packageVersion: row.version, @@ -186,7 +202,7 @@ export function getCachedResult(key: CacheKey): CachedBundleResult | null { rawSize: row.raw_size, gzipSize: row.gzip_size, gzipLevel: row.gzip_level, - externals: row.externals ? row.externals.split(",").filter(Boolean) : [], + externals: externalsStr ? externalsStr.split(",").filter(Boolean) : [], dependencies, platform: row.platform as "browser" | "node", }; @@ -214,8 +230,8 @@ export function setCachedResult(key: CacheKey, result: BundleResult): void { const stmt = database.prepare(` INSERT OR REPLACE INTO bundle_cache ( package_name, version, exports, platform, gzip_level, externals, no_external, - raw_size, gzip_size, dependencies, display_name, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + raw_size, gzip_size, dependencies, display_name, created_at, actual_externals + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run( @@ -231,6 +247,7 @@ export function setCachedResult(key: CacheKey, result: BundleResult): void { JSON.stringify(result.dependencies), result.packageName, Date.now(), + result.externals.slice().sort().join(","), ); // Enforce max entries (LRU-style eviction based on created_at).