From f76b426e2bac834270caa15631394eef22eee094 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 18:03:24 +0100 Subject: [PATCH] perf: reduce jiti loader alias work --- src/plugins/bundled-capability-runtime.ts | 31 ++++++--- src/plugins/jiti-loader-cache.ts | 16 +++-- src/plugins/loader.ts | 8 +-- src/plugins/public-surface-loader.ts | 3 - .../runtime/runtime-plugin-boundary.ts | 4 +- src/plugins/sdk-alias.test.ts | 69 ++++++++++++++++++- src/plugins/sdk-alias.ts | 44 ++++++++++-- 7 files changed, 143 insertions(+), 32 deletions(-) diff --git a/src/plugins/bundled-capability-runtime.ts b/src/plugins/bundled-capability-runtime.ts index 3e1f8d5b51b..5e72ead4d09 100644 --- a/src/plugins/bundled-capability-runtime.ts +++ b/src/plugins/bundled-capability-runtime.ts @@ -82,6 +82,13 @@ function applyVitestCapabilityAliasOverrides(params: { }; } +function shouldApplyVitestCapabilityAliasOverrides(params: { + pluginSdkResolution?: PluginSdkResolutionPreference; + env?: PluginLoadOptions["env"]; +}): boolean { + return Boolean(params.env?.VITEST && params.pluginSdkResolution === "dist"); +} + export function buildBundledCapabilityRuntimeConfig( pluginIds: readonly string[], env?: PluginLoadOptions["env"], @@ -193,22 +200,28 @@ export function loadBundledCapabilityRuntimeRegistry(params: { const getJiti = (modulePath: string) => { const tryNative = shouldPreferNativeJiti(modulePath) && !(env?.VITEST && params.pluginSdkResolution === "dist"); - const aliasMap = applyVitestCapabilityAliasOverrides({ - aliasMap: buildPluginLoaderAliasMap( - modulePath, - process.argv[1], - import.meta.url, - params.pluginSdkResolution, - ), + const aliasMap = shouldApplyVitestCapabilityAliasOverrides({ pluginSdkResolution: params.pluginSdkResolution, env, - }); + }) + ? applyVitestCapabilityAliasOverrides({ + aliasMap: buildPluginLoaderAliasMap( + modulePath, + process.argv[1], + import.meta.url, + params.pluginSdkResolution, + ), + pluginSdkResolution: params.pluginSdkResolution, + env, + }) + : undefined; return getCachedPluginJitiLoader({ cache: jitiLoaders, modulePath, importerUrl: import.meta.url, jitiFilename: import.meta.url, - aliasMap, + ...(aliasMap ? { aliasMap } : {}), + pluginSdkResolution: params.pluginSdkResolution, tryNative, }); }; diff --git a/src/plugins/jiti-loader-cache.ts b/src/plugins/jiti-loader-cache.ts index 7eff371bf69..9d501a871b4 100644 --- a/src/plugins/jiti-loader-cache.ts +++ b/src/plugins/jiti-loader-cache.ts @@ -3,6 +3,7 @@ import { buildPluginLoaderJitiOptions, createPluginLoaderJitiCacheKey, resolvePluginLoaderJitiConfig, + type PluginSdkResolutionPreference, } from "./sdk-alias.js"; export type PluginJitiLoader = ReturnType; @@ -19,6 +20,7 @@ export function getCachedPluginJitiLoader(params: { createLoader?: PluginJitiLoaderFactory; aliasMap?: Record; tryNative?: boolean; + pluginSdkResolution?: PluginSdkResolutionPreference; cacheScopeKey?: string; }): PluginJitiLoader { const jitiFilename = params.jitiFilename ?? params.modulePath; @@ -38,23 +40,27 @@ export function getCachedPluginJitiLoader(params: { argv1: params.argvEntry ?? process.argv[1], moduleUrl: params.importerUrl, ...(params.preferBuiltDist ? { preferBuiltDist: true } : {}), + ...(params.pluginSdkResolution + ? { pluginSdkResolution: params.pluginSdkResolution } + : {}), }) : null; + const canReuseDefaultCacheKey = + defaultConfig !== null && + (!hasAliasOverride || params.aliasMap === defaultConfig.aliasMap) && + (!hasTryNativeOverride || params.tryNative === defaultConfig.tryNative); const resolved = defaultConfig ? { tryNative: params.tryNative ?? defaultConfig.tryNative, aliasMap: params.aliasMap ?? defaultConfig.aliasMap, - cacheKey: - !hasAliasOverride && - (!hasTryNativeOverride || params.tryNative === defaultConfig.tryNative) - ? defaultConfig.cacheKey - : undefined, + cacheKey: canReuseDefaultCacheKey ? defaultConfig.cacheKey : undefined, } : resolvePluginLoaderJitiConfig({ modulePath: params.modulePath, argv1: params.argvEntry ?? process.argv[1], moduleUrl: params.importerUrl, ...(params.preferBuiltDist ? { preferBuiltDist: true } : {}), + ...(params.pluginSdkResolution ? { pluginSdkResolution: params.pluginSdkResolution } : {}), }); const { tryNative, aliasMap } = resolved; const cacheKey = diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 30b7a1f0451..2d6df8dd4f4 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -454,18 +454,12 @@ function createPluginJitiLoader(options: Pick { const tryNative = shouldPreferNativeJiti(modulePath); - const aliasMap = buildPluginLoaderAliasMap( - modulePath, - process.argv[1], - import.meta.url, - options.pluginSdkResolution, - ); return getCachedPluginJitiLoader({ cache: jitiLoaders, modulePath, importerUrl: import.meta.url, jitiFilename: import.meta.url, - aliasMap, + pluginSdkResolution: options.pluginSdkResolution, // 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 diff --git a/src/plugins/public-surface-loader.ts b/src/plugins/public-surface-loader.ts index 2e1029e71c3..5a3c74da4ed 100644 --- a/src/plugins/public-surface-loader.ts +++ b/src/plugins/public-surface-loader.ts @@ -7,7 +7,6 @@ import { resolveBundledPluginsDir } from "./bundled-dir.js"; import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js"; import { resolveBundledPluginPublicSurfacePath } from "./public-surface-runtime.js"; import { - buildPluginLoaderAliasMap, isBundledPluginExtensionPath, resolvePluginLoaderJitiTryNative, resolveLoaderPackageRoot, @@ -129,14 +128,12 @@ function getSharedBundledPublicSurfaceJiti(modulePath: string, tryNative: boolea return null; } const cacheKey = tryNative ? "bundled:native" : "bundled:source"; - const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url); return getCachedPluginJitiLoader({ cache: sharedBundledPublicSurfaceJitiLoaders, modulePath, importerUrl: import.meta.url, jitiFilename: import.meta.url, cacheScopeKey: cacheKey, - aliasMap, tryNative, }); } diff --git a/src/plugins/runtime/runtime-plugin-boundary.ts b/src/plugins/runtime/runtime-plugin-boundary.ts index cf6aa3c88e6..aa925ad1f89 100644 --- a/src/plugins/runtime/runtime-plugin-boundary.ts +++ b/src/plugins/runtime/runtime-plugin-boundary.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { loadConfig } from "../../config/config.js"; import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "../jiti-loader-cache.js"; import { loadPluginManifestRegistry } from "../manifest-registry.js"; -import { buildPluginLoaderAliasMap, shouldPreferNativeJiti } from "../sdk-alias.js"; +import { shouldPreferNativeJiti } from "../sdk-alias.js"; type PluginRuntimeRecord = { origin?: string; @@ -116,13 +116,11 @@ export function resolvePluginRuntimeModulePath( export function getPluginBoundaryJiti(modulePath: string, loaders: PluginJitiLoaderCache) { const tryNative = shouldPreferNativeJiti(modulePath); - const aliasMap = buildPluginLoaderAliasMap(modulePath); return getCachedPluginJitiLoader({ cache: loaders, modulePath, importerUrl: import.meta.url, jitiFilename: import.meta.url, - aliasMap, tryNative, }); } diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index 3e5e88f9621..59104524487 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -699,6 +699,35 @@ describe("plugin sdk alias helpers", () => { }); }); + it("falls back to source plugin-sdk subpath aliases when dist chunks are stale", () => { + const fixture = createPluginSdkAliasFixture({ + srcFile: "provider-entry.ts", + distFile: "provider-entry.js", + distBody: 'import { entry } from "../missing-provider-entry-chunk.js";\nexport { entry };\n', + packageExports: { + "./plugin-sdk/provider-entry": { default: "./dist/plugin-sdk/provider-entry.js" }, + }, + }); + const sourceProviderEntryPath = path.join( + fixture.root, + "src", + "plugin-sdk", + "provider-entry.ts", + ); + const sourcePluginEntry = writePluginEntry( + fixture.root, + bundledPluginFile("demo", "src/index.ts"), + ); + + const distAliases = withEnv({ NODE_ENV: undefined }, () => + buildPluginLoaderAliasMap(sourcePluginEntry, undefined, undefined, "dist"), + ); + + expect(fs.realpathSync(distAliases["openclaw/plugin-sdk/provider-entry"] ?? "")).toBe( + fs.realpathSync(sourceProviderEntryPath), + ); + }); + it("builds source plugin-sdk subpath aliases through the wider source extension family", () => { const { fixture, sourceRootAlias, sourceChannelRuntimePath } = createPluginSdkAliasTargetFixture({ @@ -939,7 +968,45 @@ describe("plugin sdk alias helpers", () => { preferBuiltDist: true, }); - expect(second).toEqual(first); + expect(second).toBe(first); + }); + + it("scopes plugin loader Jiti config by plugin-sdk resolution", () => { + const { fixture, sourceRootAlias, distRootAlias } = createPluginSdkAliasTargetFixture(); + const sourcePluginEntry = writePluginEntry( + fixture.root, + bundledPluginFile("demo", "src/index.ts"), + ); + + const { auto, dist, distAgain } = withEnv({ NODE_ENV: undefined }, () => ({ + auto: resolvePluginLoaderJitiConfig({ + modulePath: sourcePluginEntry, + argv1: path.join(fixture.root, "openclaw.mjs"), + moduleUrl: pathToFileURL(path.join(fixture.root, "src/plugins/loader.ts")).href, + pluginSdkResolution: "auto", + }), + dist: resolvePluginLoaderJitiConfig({ + modulePath: sourcePluginEntry, + argv1: path.join(fixture.root, "openclaw.mjs"), + moduleUrl: pathToFileURL(path.join(fixture.root, "src/plugins/loader.ts")).href, + pluginSdkResolution: "dist", + }), + distAgain: resolvePluginLoaderJitiConfig({ + modulePath: sourcePluginEntry, + argv1: path.join(fixture.root, "openclaw.mjs"), + moduleUrl: pathToFileURL(path.join(fixture.root, "src/plugins/loader.ts")).href, + pluginSdkResolution: "dist", + }), + })); + + expect(distAgain).toBe(dist); + expect(auto).not.toBe(dist); + expect(fs.realpathSync(auto.aliasMap["openclaw/plugin-sdk"] ?? "")).toBe( + fs.realpathSync(sourceRootAlias), + ); + expect(fs.realpathSync(dist.aliasMap["openclaw/plugin-sdk"] ?? "")).toBe( + fs.realpathSync(distRootAlias), + ); }); it("detects bundled plugin extension paths across source and dist roots", () => { diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index 19dcf08d97c..ba989a9573b 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -260,6 +260,35 @@ const PLUGIN_SDK_SOURCE_CANDIDATE_EXTENSIONS = [ ".cts", ".cjs", ] as const; +const JS_STATIC_RELATIVE_DEPENDENCY_PATTERN = + /(?:\bfrom\s*["']|\bimport\s*\(\s*["']|\brequire\s*\(\s*["'])(\.{1,2}\/[^"']+)["']/g; + +function isUsableDistPluginSdkArtifact(candidate: string): boolean { + if (!fs.existsSync(candidate)) { + return false; + } + switch (normalizeLowercaseStringOrEmpty(path.extname(candidate))) { + case ".js": + case ".mjs": + case ".cjs": + break; + default: + return true; + } + try { + const source = fs.readFileSync(candidate, "utf-8"); + for (const match of source.matchAll(JS_STATIC_RELATIVE_DEPENDENCY_PATTERN)) { + const specifier = match[1]; + if (!specifier || fs.existsSync(path.resolve(path.dirname(candidate), specifier))) { + continue; + } + return false; + } + } catch { + return false; + } + return true; +} function readPrivateLocalOnlyPluginSdkSubpaths(packageRoot: string): string[] { try { @@ -283,7 +312,7 @@ function shouldIncludePrivateLocalOnlyPluginSdkSubpaths() { function hasPluginSdkSubpathArtifact(packageRoot: string, subpath: string) { const distPath = path.join(packageRoot, "dist", "plugin-sdk", `${subpath}.js`); - if (fs.existsSync(distPath)) { + if (isUsableDistPluginSdkArtifact(distPath)) { return true; } return PLUGIN_SDK_SOURCE_CANDIDATE_EXTENSIONS.some((ext) => @@ -369,7 +398,7 @@ export function resolvePluginSdkScopedAliasMap( for (const kind of orderedKinds) { if (kind === "dist") { const candidate = path.join(packageRoot, "dist", "plugin-sdk", `${subpath}.js`); - if (fs.existsSync(candidate)) { + if (isUsableDistPluginSdkArtifact(candidate)) { for (const packageName of PLUGIN_SDK_PACKAGE_NAMES) { aliasMap[`${packageName}/${subpath}`] = candidate; } @@ -482,13 +511,14 @@ function buildPluginLoaderJitiConfigCacheKey(params: { argv1?: string; moduleUrl: string; preferBuiltDist?: boolean; + pluginSdkResolution?: PluginSdkResolutionPreference; }) { return [ buildPluginLoaderAliasMapCacheKey({ modulePath: params.modulePath, argv1: params.argv1, moduleUrl: params.moduleUrl, - pluginSdkResolution: "auto", + pluginSdkResolution: params.pluginSdkResolution ?? "auto", }), params.preferBuiltDist === true ? "prefer-built-dist" : "default-dist", ].join("\0"); @@ -647,6 +677,7 @@ export function resolvePluginLoaderJitiConfig(params: { argv1?: string; moduleUrl: string; preferBuiltDist?: boolean; + pluginSdkResolution?: PluginSdkResolutionPreference; }): { tryNative: boolean; aliasMap: Record; @@ -662,7 +693,12 @@ export function resolvePluginLoaderJitiConfig(params: { params.modulePath, params.preferBuiltDist ? { preferBuiltDist: true } : {}, ); - const aliasMap = buildPluginLoaderAliasMap(params.modulePath, params.argv1, params.moduleUrl); + const aliasMap = buildPluginLoaderAliasMap( + params.modulePath, + params.argv1, + params.moduleUrl, + params.pluginSdkResolution, + ); const result = { tryNative, aliasMap,