mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:10:44 +00:00
fix(plugins): share cached plugin jiti loader config
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { createJiti } from "jiti";
|
||||
import { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||
import type { ChannelConfigRuntimeSchema } from "../channels/plugins/types.config.js";
|
||||
import {
|
||||
normalizeBundledPluginStringList,
|
||||
trimBundledPluginString,
|
||||
} from "./bundled-plugin-scan.js";
|
||||
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import type { PluginConfigUiHint } from "./manifest-types.js";
|
||||
import type {
|
||||
OpenClawPackageManifest,
|
||||
PluginManifest,
|
||||
PluginManifestChannelConfig,
|
||||
} from "./manifest.js";
|
||||
import { buildPluginLoaderJitiOptions, resolvePluginLoaderJitiConfig } from "./sdk-alias.js";
|
||||
|
||||
const PUBLIC_SURFACE_SOURCE_EXTENSIONS = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"] as const;
|
||||
const SOURCE_CONFIG_SCHEMA_CANDIDATES = [
|
||||
@@ -32,7 +31,7 @@ type ChannelConfigSurface = {
|
||||
runtime?: ChannelConfigRuntimeSchema;
|
||||
};
|
||||
|
||||
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
function isBuiltChannelConfigSchema(value: unknown): value is ChannelConfigSurface {
|
||||
if (!value || typeof value !== "object") {
|
||||
@@ -71,22 +70,13 @@ function resolveConfigSchemaExport(imported: Record<string, unknown>): ChannelCo
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const { tryNative, aliasMap, cacheKey } = resolvePluginLoaderJitiConfig({
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
argv1: process.argv[1],
|
||||
moduleUrl: import.meta.url,
|
||||
importerUrl: import.meta.url,
|
||||
preferBuiltDist: true,
|
||||
jitiFilename: import.meta.url,
|
||||
});
|
||||
const cached = jitiLoaders.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const loader = createJiti(import.meta.url, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
});
|
||||
jitiLoaders.set(cacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
function resolveChannelConfigSchemaModulePath(pluginDir: string): string | undefined {
|
||||
|
||||
87
src/plugins/jiti-loader-cache.test.ts
Normal file
87
src/plugins/jiti-loader-cache.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { importFreshModule } from "../../test/helpers/import-fresh.ts";
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetModules();
|
||||
vi.doUnmock("jiti");
|
||||
});
|
||||
|
||||
describe("getCachedPluginJitiLoader", () => {
|
||||
it("reuses cached loaders for the same module config and filename", async () => {
|
||||
const createJiti = vi.fn((filename: string) =>
|
||||
Object.assign(vi.fn(), {
|
||||
filename,
|
||||
}),
|
||||
);
|
||||
vi.doMock("jiti", () => ({
|
||||
createJiti,
|
||||
}));
|
||||
|
||||
const { getCachedPluginJitiLoader } = await importFreshModule<
|
||||
typeof import("./jiti-loader-cache.js")
|
||||
>(import.meta.url, "./jiti-loader-cache.js?scope=cached-loader");
|
||||
|
||||
const cache = new Map();
|
||||
const params = {
|
||||
cache,
|
||||
modulePath: "/repo/extensions/demo/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/setup-registry.ts",
|
||||
argvEntry: "/repo/openclaw.mjs",
|
||||
jitiFilename: "file:///repo/src/plugins/source-loader.ts",
|
||||
} as const;
|
||||
|
||||
const first = getCachedPluginJitiLoader(params);
|
||||
const second = getCachedPluginJitiLoader(params);
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect(createJiti).toHaveBeenCalledTimes(1);
|
||||
expect(cache.size).toBe(1);
|
||||
});
|
||||
|
||||
it("keeps loader caches scoped by jiti filename and dist preference", 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=filename-scope");
|
||||
|
||||
const cache = new Map();
|
||||
const first = getCachedPluginJitiLoader({
|
||||
cache,
|
||||
modulePath: "/repo/dist/extensions/demo/api.ts",
|
||||
importerUrl: "file:///repo/src/plugins/public-surface-loader.ts",
|
||||
argvEntry: "/repo/openclaw.mjs",
|
||||
preferBuiltDist: true,
|
||||
jitiFilename: "file:///repo/src/plugins/public-surface-loader.ts",
|
||||
});
|
||||
const second = getCachedPluginJitiLoader({
|
||||
cache,
|
||||
modulePath: "/repo/dist/extensions/demo/api.ts",
|
||||
importerUrl: "file:///repo/src/plugins/public-surface-loader.ts",
|
||||
argvEntry: "/repo/openclaw.mjs",
|
||||
preferBuiltDist: true,
|
||||
jitiFilename: "file:///repo/src/plugins/bundled-channel-config-metadata.ts",
|
||||
});
|
||||
|
||||
expect(second).not.toBe(first);
|
||||
expect(createJiti).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"file:///repo/src/plugins/public-surface-loader.ts",
|
||||
expect.objectContaining({ tryNative: true }),
|
||||
);
|
||||
expect(createJiti).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"file:///repo/src/plugins/bundled-channel-config-metadata.ts",
|
||||
expect.objectContaining({ tryNative: true }),
|
||||
);
|
||||
expect(cache.size).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,5 @@
|
||||
import { createJiti } from "jiti";
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
shouldPreferNativeJiti,
|
||||
} from "./sdk-alias.js";
|
||||
import { buildPluginLoaderJitiOptions, resolvePluginLoaderJitiConfig } from "./sdk-alias.js";
|
||||
|
||||
export type PluginJitiLoaderCache = Map<string, ReturnType<typeof createJiti>>;
|
||||
|
||||
@@ -12,25 +8,24 @@ export function getCachedPluginJitiLoader(params: {
|
||||
modulePath: string;
|
||||
importerUrl: string;
|
||||
argvEntry?: string;
|
||||
preferBuiltDist?: boolean;
|
||||
jitiFilename?: string;
|
||||
}): ReturnType<typeof createJiti> {
|
||||
const aliasMap = buildPluginLoaderAliasMap(
|
||||
params.modulePath,
|
||||
params.argvEntry ?? process.argv[1],
|
||||
params.importerUrl,
|
||||
);
|
||||
const tryNative = shouldPreferNativeJiti(params.modulePath);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
const { tryNative, aliasMap, cacheKey } = resolvePluginLoaderJitiConfig({
|
||||
modulePath: params.modulePath,
|
||||
argv1: params.argvEntry ?? process.argv[1],
|
||||
moduleUrl: params.importerUrl,
|
||||
...(params.preferBuiltDist ? { preferBuiltDist: true } : {}),
|
||||
});
|
||||
const cached = params.cache.get(cacheKey);
|
||||
const scopedCacheKey = `${params.jitiFilename ?? params.modulePath}::${cacheKey}`;
|
||||
const cached = params.cache.get(scopedCacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const loader = createJiti(params.modulePath, {
|
||||
const loader = createJiti(params.jitiFilename ?? params.modulePath, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
});
|
||||
params.cache.set(cacheKey, loader);
|
||||
params.cache.set(scopedCacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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,
|
||||
@@ -28,8 +29,8 @@ const publicSurfaceLocations = new Map<
|
||||
boundaryRoot: string;
|
||||
} | null
|
||||
>();
|
||||
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
const sharedBundledPublicSurfaceJitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
const sharedBundledPublicSurfaceJitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
function isSourceArtifactPath(modulePath: string): boolean {
|
||||
switch (path.extname(modulePath).toLowerCase()) {
|
||||
@@ -95,7 +96,7 @@ function resolvePublicSurfaceLocation(params: {
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const { tryNative, aliasMap, cacheKey } = resolvePluginLoaderJitiConfig({
|
||||
const { tryNative } = resolvePluginLoaderJitiConfig({
|
||||
modulePath,
|
||||
argv1: process.argv[1],
|
||||
moduleUrl: import.meta.url,
|
||||
@@ -105,15 +106,13 @@ function getJiti(modulePath: string) {
|
||||
if (sharedLoader) {
|
||||
return sharedLoader;
|
||||
}
|
||||
const cached = jitiLoaders.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const loader = createJiti(import.meta.url, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
const loader = getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
preferBuiltDist: true,
|
||||
jitiFilename: import.meta.url,
|
||||
});
|
||||
jitiLoaders.set(cacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@@ -130,10 +129,7 @@ function loadPublicSurfaceModule(modulePath: string): unknown {
|
||||
return getJiti(modulePath)(modulePath);
|
||||
}
|
||||
|
||||
function getSharedBundledPublicSurfaceJiti(
|
||||
modulePath: string,
|
||||
tryNative: boolean,
|
||||
): ReturnType<typeof createJiti> | null {
|
||||
function getSharedBundledPublicSurfaceJiti(modulePath: string, tryNative: boolean) {
|
||||
const bundledPluginsDir = resolveBundledPluginsDir();
|
||||
if (
|
||||
!isBundledPluginExtensionPath({
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { createJiti } from "jiti";
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
shouldPreferNativeJiti,
|
||||
} from "./sdk-alias.js";
|
||||
import type { PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import { getCachedPluginJitiLoader } from "./jiti-loader-cache.js";
|
||||
|
||||
export type PluginSourceLoader = (modulePath: string) => unknown;
|
||||
|
||||
@@ -12,22 +8,14 @@ function shouldProfilePluginSourceLoader(): boolean {
|
||||
}
|
||||
|
||||
export function createPluginSourceLoader(): PluginSourceLoader {
|
||||
const loaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
const loaders: PluginJitiLoaderCache = new Map();
|
||||
return (modulePath) => {
|
||||
const tryNative = shouldPreferNativeJiti(modulePath);
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
const jiti = getCachedPluginJitiLoader({
|
||||
cache: loaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
jitiFilename: import.meta.url,
|
||||
});
|
||||
let jiti = loaders.get(cacheKey);
|
||||
if (!jiti) {
|
||||
jiti = createJiti(import.meta.url, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
});
|
||||
loaders.set(cacheKey, jiti);
|
||||
}
|
||||
if (!shouldProfilePluginSourceLoader()) {
|
||||
return jiti(modulePath);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user