fix(plugins): share loader jiti cache overrides

This commit is contained in:
Vincent Koc
2026-04-14 17:07:28 +01:00
parent b4e4f96fd5
commit f366c38df8
3 changed files with 92 additions and 20 deletions

View File

@@ -84,4 +84,57 @@ describe("getCachedPluginJitiLoader", () => {
);
expect(cache.size).toBe(2);
});
it("lets callers override alias maps and tryNative while keeping cache keys stable", async () => {
const createJiti = vi.fn((filename: string, options: Record<string, unknown>) =>
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=overrides");
const cache = new Map();
const first = getCachedPluginJitiLoader({
cache,
modulePath: "/repo/extensions/demo/index.ts",
importerUrl: "file:///repo/src/plugins/loader.ts",
jitiFilename: "file:///repo/src/plugins/loader.ts",
aliasMap: {
alpha: "/repo/alpha.js",
zeta: "/repo/zeta.js",
},
tryNative: false,
});
const second = getCachedPluginJitiLoader({
cache,
modulePath: "/repo/extensions/demo/index.ts",
importerUrl: "file:///repo/src/plugins/loader.ts",
jitiFilename: "file:///repo/src/plugins/loader.ts",
aliasMap: {
zeta: "/repo/zeta.js",
alpha: "/repo/alpha.js",
},
tryNative: false,
});
expect(second).toBe(first);
expect(createJiti).toHaveBeenCalledTimes(1);
expect(createJiti).toHaveBeenCalledWith(
"file:///repo/src/plugins/loader.ts",
expect.objectContaining({
tryNative: false,
alias: {
alpha: "/repo/alpha.js",
zeta: "/repo/zeta.js",
},
}),
);
});
});

View File

@@ -1,5 +1,9 @@
import { createJiti } from "jiti";
import { buildPluginLoaderJitiOptions, resolvePluginLoaderJitiConfig } from "./sdk-alias.js";
import {
buildPluginLoaderJitiOptions,
createPluginLoaderJitiCacheKey,
resolvePluginLoaderJitiConfig,
} from "./sdk-alias.js";
export type PluginJitiLoader = ReturnType<typeof createJiti>;
export type PluginJitiLoaderFactory = typeof createJiti;
@@ -13,12 +17,33 @@ export function getCachedPluginJitiLoader(params: {
preferBuiltDist?: boolean;
jitiFilename?: string;
createLoader?: PluginJitiLoaderFactory;
aliasMap?: Record<string, string>;
tryNative?: boolean;
}): PluginJitiLoader {
const { tryNative, aliasMap, cacheKey } = resolvePluginLoaderJitiConfig({
modulePath: params.modulePath,
argv1: params.argvEntry ?? process.argv[1],
moduleUrl: params.importerUrl,
...(params.preferBuiltDist ? { preferBuiltDist: true } : {}),
const defaultConfig =
params.aliasMap || typeof params.tryNative === "boolean"
? resolvePluginLoaderJitiConfig({
modulePath: params.modulePath,
argv1: params.argvEntry ?? process.argv[1],
moduleUrl: params.importerUrl,
...(params.preferBuiltDist ? { preferBuiltDist: true } : {}),
})
: null;
const resolved = defaultConfig
? {
tryNative: params.tryNative ?? defaultConfig.tryNative,
aliasMap: params.aliasMap ?? defaultConfig.aliasMap,
}
: resolvePluginLoaderJitiConfig({
modulePath: params.modulePath,
argv1: params.argvEntry ?? process.argv[1],
moduleUrl: params.importerUrl,
...(params.preferBuiltDist ? { preferBuiltDist: true } : {}),
});
const { tryNative, aliasMap } = resolved;
const cacheKey = createPluginLoaderJitiCacheKey({
tryNative,
aliasMap,
});
const scopedCacheKey = `${params.jitiFilename ?? params.modulePath}::${cacheKey}`;
const cached = params.cache.get(scopedCacheKey);

View File

@@ -1,7 +1,6 @@
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { createJiti } from "jiti";
import {
clearAgentHarnesses,
listRegisteredAgentHarnesses,
@@ -46,6 +45,7 @@ import {
import { discoverOpenClawPlugins } from "./discovery.js";
import { initializeGlobalHookRunner } from "./hook-runner-global.js";
import { clearPluginInteractiveHandlers } from "./interactive-registry.js";
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
import { loadPluginManifestRegistry } from "./manifest-registry.js";
import type { PluginBundleFormat, PluginDiagnostic, PluginFormat } from "./manifest-types.js";
import type { PluginManifestContracts } from "./manifest.js";
@@ -277,7 +277,7 @@ function toSafeImportPath(specifier: string): string {
}
function createPluginJitiLoader(options: Pick<PluginLoadOptions, "pluginSdkResolution">) {
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
const jitiLoaders: PluginJitiLoaderCache = new Map();
return (modulePath: string) => {
const tryNative = shouldPreferNativeJiti(modulePath);
const aliasMap = buildPluginLoaderAliasMap(
@@ -286,24 +286,18 @@ function createPluginJitiLoader(options: Pick<PluginLoadOptions, "pluginSdkResol
import.meta.url,
options.pluginSdkResolution,
);
const cacheKey = JSON.stringify({
tryNative,
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
});
const cached = jitiLoaders.get(cacheKey);
if (cached) {
return cached;
}
const loader = createJiti(import.meta.url, {
...buildPluginLoaderJitiOptions(aliasMap),
return getCachedPluginJitiLoader({
cache: jitiLoaders,
modulePath,
importerUrl: import.meta.url,
jitiFilename: import.meta.url,
aliasMap,
// Source .ts runtime shims import sibling ".js" specifiers that only exist
// after build. Disable native loading for source entries so Jiti rewrites
// those imports against the source graph, while keeping native dist/*.js
// loading for the canonical built module graph.
tryNative,
});
jitiLoaders.set(cacheKey, loader);
return loader;
};
}