From 8fa63ac380193d38e2ff74e1023e63d3f29290b4 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 14 Apr 2026 17:20:44 +0100 Subject: [PATCH] fix(plugins): share bundled public surface jiti cache scope --- src/plugins/jiti-loader-cache.test.ts | 44 +++++++++++++++++++++++ src/plugins/jiti-loader-cache.ts | 3 +- src/plugins/public-surface-loader.test.ts | 32 +++++++++++++++++ src/plugins/public-surface-loader.ts | 17 ++++----- 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/plugins/jiti-loader-cache.test.ts b/src/plugins/jiti-loader-cache.test.ts index 4008a8ba218..1d3ee82b7da 100644 --- a/src/plugins/jiti-loader-cache.test.ts +++ b/src/plugins/jiti-loader-cache.test.ts @@ -137,4 +137,48 @@ describe("getCachedPluginJitiLoader", () => { }), ); }); + + it("lets callers intentionally share loaders behind a custom cache scope key", async () => { + const createJiti = vi.fn((filename: string, options: Record) => + Object.assign(vi.fn(), { + filename, + options, + }), + ); + vi.doMock("jiti", () => ({ + createJiti, + })); + + const { getCachedPluginJitiLoader } = await importFreshModule< + typeof import("./jiti-loader-cache.js") + >(import.meta.url, "./jiti-loader-cache.js?scope=cache-scope-key"); + + const cache = new Map(); + const first = getCachedPluginJitiLoader({ + cache, + modulePath: "/repo/dist/extensions/demo-a/api.js", + importerUrl: "file:///repo/src/plugins/public-surface-loader.ts", + jitiFilename: "file:///repo/src/plugins/public-surface-loader.ts", + aliasMap: { + demo: "/repo/demo-a.js", + }, + tryNative: true, + cacheScopeKey: "bundled:native", + }); + const second = getCachedPluginJitiLoader({ + cache, + modulePath: "/repo/dist/extensions/demo-b/api.js", + importerUrl: "file:///repo/src/plugins/public-surface-loader.ts", + jitiFilename: "file:///repo/src/plugins/public-surface-loader.ts", + aliasMap: { + demo: "/repo/demo-b.js", + }, + tryNative: true, + cacheScopeKey: "bundled:native", + }); + + expect(second).toBe(first); + expect(createJiti).toHaveBeenCalledTimes(1); + expect(cache.size).toBe(1); + }); }); diff --git a/src/plugins/jiti-loader-cache.ts b/src/plugins/jiti-loader-cache.ts index 40909665e7e..a7140cebb1b 100644 --- a/src/plugins/jiti-loader-cache.ts +++ b/src/plugins/jiti-loader-cache.ts @@ -19,6 +19,7 @@ export function getCachedPluginJitiLoader(params: { createLoader?: PluginJitiLoaderFactory; aliasMap?: Record; tryNative?: boolean; + cacheScopeKey?: string; }): PluginJitiLoader { const defaultConfig = params.aliasMap || typeof params.tryNative === "boolean" @@ -45,7 +46,7 @@ export function getCachedPluginJitiLoader(params: { tryNative, aliasMap, }); - const scopedCacheKey = `${params.jitiFilename ?? params.modulePath}::${cacheKey}`; + const scopedCacheKey = `${params.jitiFilename ?? params.modulePath}::${params.cacheScopeKey ?? cacheKey}`; const cached = params.cache.get(scopedCacheKey); if (cached) { return cached; diff --git a/src/plugins/public-surface-loader.test.ts b/src/plugins/public-surface-loader.test.ts index e06006c3841..3b5d1dc1463 100644 --- a/src/plugins/public-surface-loader.test.ts +++ b/src/plugins/public-surface-loader.test.ts @@ -105,4 +105,36 @@ describe("bundled plugin public surface loader", () => { expect(requireLoader).toHaveBeenCalledWith(pathModule.resolve(modulePath)); expect(createJiti).not.toHaveBeenCalled(); }); + + it("reuses one bundled dist jiti loader across public artifacts with the same native mode", async () => { + const createJiti = vi.fn(() => vi.fn((modulePath: string) => ({ modulePath }))); + vi.doMock("jiti", () => ({ + createJiti, + })); + + const publicSurfaceLoader = await importFreshModule< + typeof import("./public-surface-loader.js") + >(import.meta.url, "./public-surface-loader.js?scope=shared-bundled-jiti"); + const tempRoot = createTempDir(); + const bundledPluginsDir = path.join(tempRoot, "dist"); + process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir; + + const firstPath = path.join(bundledPluginsDir, "demo-a", "api.js"); + const secondPath = path.join(bundledPluginsDir, "demo-b", "api.js"); + fs.mkdirSync(path.dirname(firstPath), { recursive: true }); + fs.mkdirSync(path.dirname(secondPath), { recursive: true }); + fs.writeFileSync(firstPath, 'export const marker = "demo-a";\n', "utf8"); + fs.writeFileSync(secondPath, 'export const marker = "demo-b";\n', "utf8"); + + publicSurfaceLoader.loadBundledPluginPublicArtifactModuleSync<{ modulePath: string }>({ + dirName: "demo-a", + artifactBasename: "api.js", + }); + publicSurfaceLoader.loadBundledPluginPublicArtifactModuleSync<{ modulePath: string }>({ + dirName: "demo-b", + artifactBasename: "api.js", + }); + + expect(createJiti).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/plugins/public-surface-loader.ts b/src/plugins/public-surface-loader.ts index e84f3255494..71bde62e429 100644 --- a/src/plugins/public-surface-loader.ts +++ b/src/plugins/public-surface-loader.ts @@ -2,14 +2,12 @@ import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { createJiti } from "jiti"; import { openBoundaryFileSync } from "../infra/boundary-file-read.js"; import { resolveBundledPluginsDir } from "./bundled-dir.js"; import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js"; import { resolveBundledPluginPublicSurfacePath } from "./public-surface-runtime.js"; import { buildPluginLoaderAliasMap, - buildPluginLoaderJitiOptions, isBundledPluginExtensionPath, resolvePluginLoaderJitiConfig, resolveLoaderPackageRoot, @@ -141,17 +139,16 @@ function getSharedBundledPublicSurfaceJiti(modulePath: string, tryNative: boolea return null; } const cacheKey = tryNative ? "bundled:native" : "bundled:source"; - const cached = sharedBundledPublicSurfaceJitiLoaders.get(cacheKey); - if (cached) { - return cached; - } const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url); - const loader = createJiti(import.meta.url, { - ...buildPluginLoaderJitiOptions(aliasMap), + return getCachedPluginJitiLoader({ + cache: sharedBundledPublicSurfaceJitiLoaders, + modulePath, + importerUrl: import.meta.url, + jitiFilename: import.meta.url, + cacheScopeKey: cacheKey, + aliasMap, tryNative, }); - sharedBundledPublicSurfaceJitiLoaders.set(cacheKey, loader); - return loader; } export function loadBundledPluginPublicArtifactModuleSync(params: {