From d6ca271b46669c62e3bf07e7268b594dcda9ea1e Mon Sep 17 00:00:00 2001 From: "agentfront[bot]" Date: Wed, 21 Oct 2026 16:13:37 +0000 Subject: [PATCH] Cherry-pick: perf(sdk): load js-yaml, semver and the uipack renderer on first use under require() Cherry-picked from #935 (merged to release/1.10.x) Original commit: b7ee8319da5700049872f593074b2a541c444039 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> --- eslint.config.mjs | 2 +- jest.imports-mapper.js | 3 ++ libs/sdk/package.json | 15 +++++++++ libs/sdk/project.json | 11 +++++++ libs/sdk/src/auth/auth-ui/auth-ui.render.ts | 9 ++--- .../builtin/config/providers/config-loader.ts | 4 +-- libs/sdk/src/esm-loader/semver.utils.ts | 16 +++++---- .../src/skill/sep-2640/sep-2640.builders.ts | 2 +- libs/sdk/src/skill/skill-md-parser.ts | 4 +-- .../__tests__/lazy-js-yaml.spec.ts | 30 +++++++++++++++++ .../third-party/__tests__/lazy-semver.spec.ts | 33 +++++++++++++++++++ .../__tests__/lazy-uipack-render.spec.ts | 29 ++++++++++++++++ libs/sdk/src/third-party/js-yaml.ts | 2 ++ libs/sdk/src/third-party/lazy-js-yaml.ts | 5 +++ libs/sdk/src/third-party/lazy-semver.ts | 10 ++++++ .../sdk/src/third-party/lazy-uipack-render.ts | 5 +++ libs/sdk/src/third-party/semver.ts | 8 +++++ libs/sdk/src/third-party/uipack-render.ts | 2 ++ tsconfig.base.json | 5 ++- 19 files changed, 175 insertions(+), 20 deletions(-) create mode 100644 libs/sdk/src/third-party/__tests__/lazy-js-yaml.spec.ts create mode 100644 libs/sdk/src/third-party/__tests__/lazy-semver.spec.ts create mode 100644 libs/sdk/src/third-party/__tests__/lazy-uipack-render.spec.ts create mode 100644 libs/sdk/src/third-party/js-yaml.ts create mode 100644 libs/sdk/src/third-party/lazy-js-yaml.ts create mode 100644 libs/sdk/src/third-party/lazy-semver.ts create mode 100644 libs/sdk/src/third-party/lazy-uipack-render.ts create mode 100644 libs/sdk/src/third-party/semver.ts create mode 100644 libs/sdk/src/third-party/uipack-render.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index a09e17182..5ba36a604 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -65,7 +65,7 @@ export default [ '@frontmcp/protocol', '@frontmcp/testing', '@frontmcp/observability', - '^#(mcp-.+|crypto-.+|async-.+|event-.+|langchain-.+|sse-.+|express-.+|server-.+|stdio-.+|env|runtime-context|path|client|import-computed)$', + '^#(mcp-.+|crypto-.+|async-.+|event-.+|langchain-.+|sse-.+|express-.+|server-.+|stdio-.+|env|runtime-context|path|client|import-computed|js-yaml|semver|uipack-render)$', ], depConstraints: [ { diff --git a/jest.imports-mapper.js b/jest.imports-mapper.js index 9feb51b70..5b61f2e83 100644 --- a/jest.imports-mapper.js +++ b/jest.imports-mapper.js @@ -26,4 +26,7 @@ module.exports = { '^#sse-transport$': path.resolve(root, 'libs/sdk/src/transport/adapters/polyfills/node-sse-transport.ts'), '^#express-host$': path.resolve(root, 'libs/sdk/src/server/adapters/polyfills/node-express-host.ts'), '^#import-computed$': path.resolve(root, 'libs/sdk/src/esm-loader/import-computed.ts'), + '^#js-yaml$': path.resolve(root, 'libs/sdk/src/third-party/js-yaml.ts'), + '^#semver$': path.resolve(root, 'libs/sdk/src/third-party/semver.ts'), + '^#uipack-render$': path.resolve(root, 'libs/sdk/src/third-party/uipack-render.ts'), }; diff --git a/libs/sdk/package.json b/libs/sdk/package.json index c4f4c4192..ad82695bc 100644 --- a/libs/sdk/package.json +++ b/libs/sdk/package.json @@ -39,6 +39,21 @@ "#import-computed": { "import": "./src/esm-loader/import-computed.ts", "default": "./src/esm-loader/import-computed.ts" + }, + "#js-yaml": { + "types": "./src/third-party/js-yaml.ts", + "require": "./src/third-party/lazy-js-yaml.ts", + "default": "./src/third-party/js-yaml.ts" + }, + "#semver": { + "types": "./src/third-party/semver.ts", + "require": "./src/third-party/lazy-semver.ts", + "default": "./src/third-party/semver.ts" + }, + "#uipack-render": { + "types": "./src/third-party/uipack-render.ts", + "require": "./src/third-party/lazy-uipack-render.ts", + "default": "./src/third-party/uipack-render.ts" } }, "type": "commonjs", diff --git a/libs/sdk/project.json b/libs/sdk/project.json index c1fe0462d..f9f0db1f8 100644 --- a/libs/sdk/project.json +++ b/libs/sdk/project.json @@ -20,11 +20,22 @@ "bundle": true, "thirdParty": false, "platform": "node", + "additionalEntryPoints": [ + "libs/sdk/src/third-party/js-yaml.ts", + "libs/sdk/src/third-party/lazy-js-yaml.ts", + "libs/sdk/src/third-party/semver.ts", + "libs/sdk/src/third-party/lazy-semver.ts", + "libs/sdk/src/third-party/uipack-render.ts", + "libs/sdk/src/third-party/lazy-uipack-render.ts" + ], "assets": ["libs/sdk/README.md", "LICENSE", "libs/sdk/package.json"], "esbuildOptions": { "minifyWhitespace": true, "outExtension": { ".js": ".js" }, "external": [ + "#js-yaml", + "#semver", + "#uipack-render", "@frontmcp/sdk", "@frontmcp/utils", "@frontmcp/di", diff --git a/libs/sdk/src/auth/auth-ui/auth-ui.render.ts b/libs/sdk/src/auth/auth-ui/auth-ui.render.ts index 0694b94ae..09b0ea80d 100644 --- a/libs/sdk/src/auth/auth-ui/auth-ui.render.ts +++ b/libs/sdk/src/auth/auth-ui/auth-ui.render.ts @@ -38,12 +38,9 @@ * * @packageDocumentation */ -import { - createResolverWithOverrides, - renderComponent, - type ShellConfig, - type ShellMountDescriptor, -} from '@frontmcp/uipack'; +import type { ShellConfig, ShellMountDescriptor } from '@frontmcp/uipack'; + +import { createResolverWithOverrides, renderComponent } from '#uipack-render'; import { AUTH_FLOW_GLOBAL_KEY, AUTH_MOUNT_ID, type AuthFlowState, type AuthSlot } from './auth-ui.contract'; import { type AuthUiRegistry } from './auth-ui.registry'; diff --git a/libs/sdk/src/builtin/config/providers/config-loader.ts b/libs/sdk/src/builtin/config/providers/config-loader.ts index ba6a1fff3..1dddfd769 100644 --- a/libs/sdk/src/builtin/config/providers/config-loader.ts +++ b/libs/sdk/src/builtin/config/providers/config-loader.ts @@ -1,8 +1,8 @@ -import * as yaml from 'js-yaml'; - import { type z } from '@frontmcp/lazy-zod'; import { fileExists, getCwd, pathResolve, readFile } from '@frontmcp/utils'; +import * as yaml from '#js-yaml'; + import { ConfigValidationError } from './config.service'; import { canReadConfigFiles, diff --git a/libs/sdk/src/esm-loader/semver.utils.ts b/libs/sdk/src/esm-loader/semver.utils.ts index 35188f24f..7f6d7e4da 100644 --- a/libs/sdk/src/esm-loader/semver.utils.ts +++ b/libs/sdk/src/esm-loader/semver.utils.ts @@ -4,13 +4,15 @@ * Isolates the semver dependency to a single file for easier maintenance. */ -import semverCompare from 'semver/functions/compare.js'; -import semverGt from 'semver/functions/gt.js'; -import semverRcompare from 'semver/functions/rcompare.js'; -import semverSatisfies from 'semver/functions/satisfies.js'; -import semverValid from 'semver/functions/valid.js'; -import semverMaxSatisfying from 'semver/ranges/max-satisfying.js'; -import semverValidRange from 'semver/ranges/valid.js'; +import { + compare as semverCompare, + gt as semverGt, + maxSatisfying as semverMaxSatisfying, + rcompare as semverRcompare, + satisfies as semverSatisfies, + valid as semverValid, + validRange as semverValidRange, +} from '#semver'; /** * Check if a range string is a dist-tag (not a semver range). diff --git a/libs/sdk/src/skill/sep-2640/sep-2640.builders.ts b/libs/sdk/src/skill/sep-2640/sep-2640.builders.ts index b9faf3a78..f46866638 100644 --- a/libs/sdk/src/skill/sep-2640/sep-2640.builders.ts +++ b/libs/sdk/src/skill/sep-2640/sep-2640.builders.ts @@ -8,7 +8,7 @@ * guidance in the SEP. */ -import * as yaml from 'js-yaml'; +import * as yaml from '#js-yaml'; import type { SkillContent } from '../../common/interfaces'; import type { SkillMetadata } from '../../common/metadata'; diff --git a/libs/sdk/src/skill/skill-md-parser.ts b/libs/sdk/src/skill/skill-md-parser.ts index aab249317..b3f3cdac1 100644 --- a/libs/sdk/src/skill/skill-md-parser.ts +++ b/libs/sdk/src/skill/skill-md-parser.ts @@ -9,10 +9,10 @@ * @module skill/skill-md-parser */ -import * as yaml from 'js-yaml'; - import { readFile } from '@frontmcp/utils'; +import * as yaml from '#js-yaml'; + import type { SkillExample, SkillMetadata, diff --git a/libs/sdk/src/third-party/__tests__/lazy-js-yaml.spec.ts b/libs/sdk/src/third-party/__tests__/lazy-js-yaml.spec.ts new file mode 100644 index 000000000..9aa1608f4 --- /dev/null +++ b/libs/sdk/src/third-party/__tests__/lazy-js-yaml.spec.ts @@ -0,0 +1,30 @@ +describe('lazy-js-yaml', () => { + afterEach(() => { + jest.dontMock('js-yaml'); + jest.resetModules(); + }); + + it('loads js-yaml the first time one of its functions is read', () => { + const load = jest.fn(); + const loadJsYaml = jest.fn(() => ({ load, dump: jest.fn() })); + jest.doMock('js-yaml', loadJsYaml); + + jest.isolateModules(() => { + const lazyYaml = require('../lazy-js-yaml'); + expect(loadJsYaml).not.toHaveBeenCalled(); + + expect(lazyYaml.load).toBe(load); + expect(loadJsYaml).toHaveBeenCalledTimes(1); + }); + }); + + it('exports the functions the eager module exports', () => { + const lazyYaml = require('../lazy-js-yaml'); + const eagerYaml = require('../js-yaml'); + + expect(Object.keys(lazyYaml).sort()).toEqual(Object.keys(eagerYaml).sort()); + expect(lazyYaml.load).toBe(eagerYaml.load); + expect(lazyYaml.dump).toBe(eagerYaml.dump); + expect(lazyYaml.load('a: 1')).toEqual({ a: 1 }); + }); +}); diff --git a/libs/sdk/src/third-party/__tests__/lazy-semver.spec.ts b/libs/sdk/src/third-party/__tests__/lazy-semver.spec.ts new file mode 100644 index 000000000..92b6641bf --- /dev/null +++ b/libs/sdk/src/third-party/__tests__/lazy-semver.spec.ts @@ -0,0 +1,33 @@ +const SEMVER_FUNCTIONS = ['compare', 'gt', 'maxSatisfying', 'rcompare', 'satisfies', 'valid', 'validRange']; + +describe('lazy-semver', () => { + afterEach(() => { + jest.dontMock('semver/functions/satisfies.js'); + jest.resetModules(); + }); + + it('loads a semver function module the first time the function is read', () => { + const satisfies = jest.fn(); + const loadSatisfies = jest.fn(() => satisfies); + jest.doMock('semver/functions/satisfies.js', loadSatisfies); + + jest.isolateModules(() => { + const lazySemver = require('../lazy-semver'); + expect(loadSatisfies).not.toHaveBeenCalled(); + + expect(lazySemver.satisfies).toBe(satisfies); + expect(loadSatisfies).toHaveBeenCalledTimes(1); + }); + }); + + it('exports the functions the eager module exports', () => { + const lazySemver = require('../lazy-semver'); + const eagerSemver = require('../semver'); + + expect(Object.keys(lazySemver).sort()).toEqual(SEMVER_FUNCTIONS); + for (const name of SEMVER_FUNCTIONS) { + expect(lazySemver[name]).toBe(eagerSemver[name]); + } + expect(lazySemver.satisfies('1.2.3', '^1.0.0')).toBe(true); + }); +}); diff --git a/libs/sdk/src/third-party/__tests__/lazy-uipack-render.spec.ts b/libs/sdk/src/third-party/__tests__/lazy-uipack-render.spec.ts new file mode 100644 index 000000000..9a128c6a4 --- /dev/null +++ b/libs/sdk/src/third-party/__tests__/lazy-uipack-render.spec.ts @@ -0,0 +1,29 @@ +describe('lazy-uipack-render', () => { + afterEach(() => { + jest.dontMock('@frontmcp/uipack'); + jest.resetModules(); + }); + + it('loads @frontmcp/uipack the first time one of its functions is read', () => { + const renderComponent = jest.fn(); + const loadUipack = jest.fn(() => ({ renderComponent, createResolverWithOverrides: jest.fn() })); + jest.doMock('@frontmcp/uipack', loadUipack); + + jest.isolateModules(() => { + const lazyRender = require('../lazy-uipack-render'); + expect(loadUipack).not.toHaveBeenCalled(); + + expect(lazyRender.renderComponent).toBe(renderComponent); + expect(loadUipack).toHaveBeenCalledTimes(1); + }); + }); + + it('exports the functions the eager module exports', () => { + const lazyRender = require('../lazy-uipack-render'); + const eagerRender = require('../uipack-render'); + + expect(Object.keys(lazyRender).sort()).toEqual(Object.keys(eagerRender).sort()); + expect(lazyRender.renderComponent).toBe(eagerRender.renderComponent); + expect(lazyRender.createResolverWithOverrides).toBe(eagerRender.createResolverWithOverrides); + }); +}); diff --git a/libs/sdk/src/third-party/js-yaml.ts b/libs/sdk/src/third-party/js-yaml.ts new file mode 100644 index 000000000..8f4e81c7e --- /dev/null +++ b/libs/sdk/src/third-party/js-yaml.ts @@ -0,0 +1,2 @@ +// `#js-yaml` everywhere but a require(): the YAML functions the SDK uses, from js-yaml. +export { dump, load } from 'js-yaml'; diff --git a/libs/sdk/src/third-party/lazy-js-yaml.ts b/libs/sdk/src/third-party/lazy-js-yaml.ts new file mode 100644 index 000000000..11a214a1d --- /dev/null +++ b/libs/sdk/src/third-party/lazy-js-yaml.ts @@ -0,0 +1,5 @@ +// The `require` variant of `#js-yaml`: js-yaml loads the first time one of its functions is read. +Object.defineProperties(exports, { + dump: { enumerable: true, get: () => require('js-yaml').dump }, + load: { enumerable: true, get: () => require('js-yaml').load }, +}); diff --git a/libs/sdk/src/third-party/lazy-semver.ts b/libs/sdk/src/third-party/lazy-semver.ts new file mode 100644 index 000000000..630171912 --- /dev/null +++ b/libs/sdk/src/third-party/lazy-semver.ts @@ -0,0 +1,10 @@ +// The `require` variant of `#semver`: each semver function loads its module the first time it is read. +Object.defineProperties(exports, { + compare: { enumerable: true, get: () => require('semver/functions/compare.js') }, + gt: { enumerable: true, get: () => require('semver/functions/gt.js') }, + rcompare: { enumerable: true, get: () => require('semver/functions/rcompare.js') }, + satisfies: { enumerable: true, get: () => require('semver/functions/satisfies.js') }, + valid: { enumerable: true, get: () => require('semver/functions/valid.js') }, + maxSatisfying: { enumerable: true, get: () => require('semver/ranges/max-satisfying.js') }, + validRange: { enumerable: true, get: () => require('semver/ranges/valid.js') }, +}); diff --git a/libs/sdk/src/third-party/lazy-uipack-render.ts b/libs/sdk/src/third-party/lazy-uipack-render.ts new file mode 100644 index 000000000..76344f09a --- /dev/null +++ b/libs/sdk/src/third-party/lazy-uipack-render.ts @@ -0,0 +1,5 @@ +// The `require` variant of `#uipack-render`: @frontmcp/uipack loads the first time an auth-UI page is built. +Object.defineProperties(exports, { + createResolverWithOverrides: { enumerable: true, get: () => require('@frontmcp/uipack').createResolverWithOverrides }, + renderComponent: { enumerable: true, get: () => require('@frontmcp/uipack').renderComponent }, +}); diff --git a/libs/sdk/src/third-party/semver.ts b/libs/sdk/src/third-party/semver.ts new file mode 100644 index 000000000..92fc264a9 --- /dev/null +++ b/libs/sdk/src/third-party/semver.ts @@ -0,0 +1,8 @@ +// `#semver` everywhere but a require(): the semver functions the ESM loader uses, each from its own module. +export { default as compare } from 'semver/functions/compare.js'; +export { default as gt } from 'semver/functions/gt.js'; +export { default as rcompare } from 'semver/functions/rcompare.js'; +export { default as satisfies } from 'semver/functions/satisfies.js'; +export { default as valid } from 'semver/functions/valid.js'; +export { default as maxSatisfying } from 'semver/ranges/max-satisfying.js'; +export { default as validRange } from 'semver/ranges/valid.js'; diff --git a/libs/sdk/src/third-party/uipack-render.ts b/libs/sdk/src/third-party/uipack-render.ts new file mode 100644 index 000000000..e2e3dd9aa --- /dev/null +++ b/libs/sdk/src/third-party/uipack-render.ts @@ -0,0 +1,2 @@ +// `#uipack-render` everywhere but a require(): the uipack functions that build the auth-UI pages. +export { createResolverWithOverrides, renderComponent } from '@frontmcp/uipack'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 3dd1bbae2..b6eaaf5d7 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -118,7 +118,10 @@ "#langchain-anthropic": ["libs/sdk/src/agent/adapters/polyfills/node-langchain-anthropic.ts"], "#sse-transport": ["libs/sdk/src/transport/adapters/polyfills/node-sse-transport.ts"], "#express-host": ["libs/sdk/src/server/adapters/polyfills/node-express-host.ts"], - "#import-computed": ["libs/sdk/src/esm-loader/import-computed.ts"] + "#import-computed": ["libs/sdk/src/esm-loader/import-computed.ts"], + "#js-yaml": ["libs/sdk/src/third-party/js-yaml.ts"], + "#semver": ["libs/sdk/src/third-party/semver.ts"], + "#uipack-render": ["libs/sdk/src/third-party/uipack-render.ts"] } }, "exclude": ["node_modules", "tmp"]