diff --git a/src/channels/plugins/bundled.shape-guard.test.ts b/src/channels/plugins/bundled.shape-guard.test.ts index fcba5bf999d..9201a476ec1 100644 --- a/src/channels/plugins/bundled.shape-guard.test.ts +++ b/src/channels/plugins/bundled.shape-guard.test.ts @@ -972,6 +972,14 @@ describe("bundled channel entry shape guards", () => { vi.doMock("../../plugins/channel-catalog-registry.js", () => ({ listChannelCatalogEntries: () => [], })); + // jiti-loader-cache prefers native require() for compiled .js before + // falling back to jiti. This test drives plugin loading via the jiti + // mock — disable the native-require fast path so the mocked jiti loader + // is exercised instead of loading the on-disk fixture directly. + vi.doMock("../../plugins/native-module-require.js", () => ({ + isJavaScriptModulePath: () => false, + tryNativeRequireJavaScriptModule: () => ({ ok: false }), + })); let reentered = false; vi.doMock("jiti", () => ({ diff --git a/src/plugins/jiti-loader-cache.test.ts b/src/plugins/jiti-loader-cache.test.ts index 9231571a622..b42e7f2c660 100644 --- a/src/plugins/jiti-loader-cache.test.ts +++ b/src/plugins/jiti-loader-cache.test.ts @@ -202,4 +202,60 @@ describe("getCachedPluginJitiLoader", () => { expect(firstAlias?.beta).toBe("/repo/alpha/sub"); expect((firstAlias as Record)[marker]).toBe(true); }); + + it("serves compiled .js targets from native require without invoking the jiti loader", async () => { + const jitiLoader = vi.fn(); + const createJiti = vi.fn(() => jitiLoader); + vi.doMock("jiti", () => ({ createJiti })); + vi.doMock("./native-module-require.js", () => ({ + isJavaScriptModulePath: (p: string) => + p.endsWith(".js") || p.endsWith(".mjs") || p.endsWith(".cjs"), + tryNativeRequireJavaScriptModule: (target: string) => ({ + ok: true, + moduleExport: { loadedFrom: target }, + }), + })); + const { getCachedPluginJitiLoader } = await importFreshModule< + typeof import("./jiti-loader-cache.js") + >(import.meta.url, "./jiti-loader-cache.js?scope=native-require-fastpath"); + + const cache = new Map(); + const loader = getCachedPluginJitiLoader({ + cache, + modulePath: "/repo/dist/extensions/demo/api.js", + importerUrl: "file:///repo/src/plugins/public-surface-loader.ts", + jitiFilename: "file:///repo/src/plugins/public-surface-loader.ts", + }); + + const result = loader("/repo/dist/extensions/demo/api.js") as { loadedFrom: string }; + expect(result.loadedFrom).toBe("/repo/dist/extensions/demo/api.js"); + // jiti is created eagerly, but its loader must NOT be invoked for .js + // targets that `tryNativeRequireJavaScriptModule` resolves. + expect(jitiLoader).not.toHaveBeenCalled(); + }); + + it("falls back to jiti when the native-require helper declines", async () => { + const jitiLoader = vi.fn(() => ({ fromJiti: true })); + const createJiti = vi.fn(() => jitiLoader); + vi.doMock("jiti", () => ({ createJiti })); + vi.doMock("./native-module-require.js", () => ({ + isJavaScriptModulePath: () => true, + tryNativeRequireJavaScriptModule: () => ({ ok: false }), + })); + const { getCachedPluginJitiLoader } = await importFreshModule< + typeof import("./jiti-loader-cache.js") + >(import.meta.url, "./jiti-loader-cache.js?scope=native-require-fallback"); + + const cache = new Map(); + const loader = getCachedPluginJitiLoader({ + cache, + modulePath: "/repo/dist/extensions/demo/api.js", + importerUrl: "file:///repo/src/plugins/public-surface-loader.ts", + jitiFilename: "file:///repo/src/plugins/public-surface-loader.ts", + }); + + const result = loader("/repo/dist/extensions/demo/api.js") as { fromJiti: boolean }; + expect(result.fromJiti).toBe(true); + expect(jitiLoader).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js"); + }); }); diff --git a/src/plugins/jiti-loader-cache.ts b/src/plugins/jiti-loader-cache.ts index 9d501a871b4..7e534d406e5 100644 --- a/src/plugins/jiti-loader-cache.ts +++ b/src/plugins/jiti-loader-cache.ts @@ -1,4 +1,5 @@ import { createJiti } from "jiti"; +import { tryNativeRequireJavaScriptModule } from "./native-module-require.js"; import { buildPluginLoaderJitiOptions, createPluginLoaderJitiCacheKey, @@ -74,10 +75,24 @@ export function getCachedPluginJitiLoader(params: { if (cached) { return cached; } - const loader = (params.createLoader ?? createJiti)(jitiFilename, { + const jitiLoader = (params.createLoader ?? createJiti)(jitiFilename, { ...buildPluginLoaderJitiOptions(aliasMap), tryNative, }); + // The returned loader prefers native require() for already-compiled JS + // artifacts (the bundled plugin public surfaces shipped in dist/) because + // jiti's transform pipeline provides no value for output that is already + // plain JS and adds several seconds of per-load overhead on slower hosts. + // Jiti stays on the hot path for TS / TSX and for the small set of + // require(esm)/async-module fallbacks `tryNativeRequireJavaScriptModule` + // declines to handle. + const loader = ((target: string) => { + const native = tryNativeRequireJavaScriptModule(target); + if (native.ok) { + return native.moduleExport; + } + return jitiLoader(target); + }) as PluginJitiLoader; params.cache.set(scopedCacheKey, loader); return loader; } diff --git a/src/plugins/setup-registry.test.ts b/src/plugins/setup-registry.test.ts index fe6a8e89ceb..4f7b4ae1fe0 100644 --- a/src/plugins/setup-registry.test.ts +++ b/src/plugins/setup-registry.test.ts @@ -8,6 +8,15 @@ import { resetRegistryJitiMocks, } from "./test-helpers/registry-jiti-mocks.js"; +// jiti-loader-cache prefers native require() for compiled .js before falling +// back to jiti. These tests scripts plugin-loading behaviour through the +// jiti mock — disable the native-require fast path so the mocked jiti loader +// stays authoritative for the test fixture files on disk. +vi.mock("./native-module-require.js", () => ({ + isJavaScriptModulePath: (_modulePath: string) => false, + tryNativeRequireJavaScriptModule: (_modulePath: string) => ({ ok: false }), +})); + const tempDirs: string[] = []; const mocks = getRegistryJitiMocks();