From 80a02851efcf1d03f9442e49e74bbaff041e1c2b Mon Sep 17 00:00:00 2001 From: killa Date: Wed, 6 May 2026 18:02:32 +0800 Subject: [PATCH 1/6] fix(bundler): normalize tegg manifest paths --- tegg/core/loader/src/LoaderUtil.ts | 28 +++++--- tegg/core/loader/test/Loader.test.ts | 29 +++++++- tools/egg-bundler/src/lib/ManifestLoader.ts | 71 ++++++++++++------- tools/egg-bundler/test/EntryGenerator.test.ts | 32 +++++++++ tools/egg-bundler/test/ManifestLoader.test.ts | 62 ++++++++++++++++ 5 files changed, 187 insertions(+), 35 deletions(-) diff --git a/tegg/core/loader/src/LoaderUtil.ts b/tegg/core/loader/src/LoaderUtil.ts index f8331d50ba..3dac05ddcd 100644 --- a/tegg/core/loader/src/LoaderUtil.ts +++ b/tegg/core/loader/src/LoaderUtil.ts @@ -8,6 +8,12 @@ import { isClass } from 'is-type-of'; // Guard against poorly mocked module constructors. const Module = globalThis.module?.constructor?.length > 1 ? globalThis.module.constructor : BuiltinModule; +type BundleModuleLoader = (filepath: string) => unknown; + +type BundleModuleGlobalThis = typeof globalThis & { + __EGG_BUNDLE_MODULE_LOADER__?: BundleModuleLoader; +}; + interface LoaderUtilConfig { extraFilePattern?: string[]; } @@ -64,20 +70,24 @@ export class LoaderUtil { static async loadFile(filePath: string): Promise { const originalFilePath = filePath; + let exports: any = (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__?.( + originalFilePath.split('\\').join('/'), + ); if (process.platform === 'win32') { // convert to file:// url // avoid windows path issue: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:' filePath = pathToFileURL(filePath).toString(); } - let exports; - try { - exports = await import(filePath); - } catch (e: any) { - console.trace('[tegg/loader] loadFile %s error:', filePath); - console.error(e); - throw new Error(`[tegg/loader] load ${filePath} failed: ${e.message}`, { - cause: e, - }); + if (exports === undefined) { + try { + exports = await import(filePath); + } catch (e: any) { + console.trace('[tegg/loader] loadFile %s error:', filePath); + console.error(e); + throw new Error(`[tegg/loader] load ${filePath} failed: ${e.message}`, { + cause: e, + }); + } } const clazzList: EggProtoImplClass[] = []; const exportNames = Object.keys(exports); diff --git a/tegg/core/loader/test/Loader.test.ts b/tegg/core/loader/test/Loader.test.ts index 980a526310..be9d54de95 100644 --- a/tegg/core/loader/test/Loader.test.ts +++ b/tegg/core/loader/test/Loader.test.ts @@ -1,12 +1,21 @@ import assert from 'node:assert/strict'; import path from 'node:path'; +import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator'; import { EggLoadUnitType } from '@eggjs/metadata'; -import { describe, it } from 'vitest'; +import { afterEach, describe, it } from 'vitest'; import { LoaderFactory, LoaderUtil } from '../src/index.ts'; +type BundleModuleGlobalThis = typeof globalThis & { + __EGG_BUNDLE_MODULE_LOADER__?: (filepath: string) => unknown; +}; + describe('core/loader/test/Loader.test.ts', () => { + afterEach(() => { + delete (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__; + }); + describe('module loader', () => { it('should load module', async () => { const repoModulePath = path.join(__dirname, './fixtures/modules/module-for-loader'); @@ -37,6 +46,24 @@ describe('core/loader/test/Loader.test.ts', () => { const prototypes = await loader.load(); assert.equal(prototypes.length, 1); }); + + it('should load pre-bundled files through the bundle module loader', async () => { + class BundledService {} + SingletonProto()(BundledService); + const bundledFile = '/bundle/app/port/manager/UserRoleManager.ts'; + (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { + assert.equal(filepath, bundledFile); + return { BundledService }; + }; + + const prototypes = await LoaderUtil.loadFile(bundledFile); + + assert.deepEqual( + prototypes.map((proto) => proto.name), + ['BundledService'], + ); + assert.equal(PrototypeUtil.getFilePath(BundledService), bundledFile); + }); }); describe('file has tsc error', () => { diff --git a/tools/egg-bundler/src/lib/ManifestLoader.ts b/tools/egg-bundler/src/lib/ManifestLoader.ts index cbb1972822..eb12f6a80c 100644 --- a/tools/egg-bundler/src/lib/ManifestLoader.ts +++ b/tools/egg-bundler/src/lib/ManifestLoader.ts @@ -28,7 +28,15 @@ interface TeggModuleDescriptor { decoratedFiles?: string[]; } +interface TeggModuleReference { + name: string; + path: string; + optional?: boolean; + loaderType?: string; +} + interface TeggManifestExtension { + moduleReferences?: TeggModuleReference[]; moduleDescriptors?: TeggModuleDescriptor[]; } @@ -401,35 +409,48 @@ export class ManifestLoader { ): Promise> { const result: Record = { ...extensions }; const tegg = extensions?.tegg as TeggManifestExtension | undefined; - if (tegg?.moduleDescriptors) { - result.tegg = { - ...tegg, - moduleDescriptors: await Promise.all( - tegg.moduleDescriptors.map(async (desc) => { - if (!path.isAbsolute(desc.unitPath)) return desc; - const real = await this.#realpath(desc.unitPath); - let best: ModuleMapEntry | undefined; - for (const entry of moduleMap) { - if (real === entry.realDir || real.startsWith(entry.realDir + path.sep)) { - best = entry; - break; - } - } - if (!best) { - // keep as relative-to-baseDir form so runtime can resolve via #resolveFromBase - const rel = path.relative(this.#baseDir, real).replaceAll(path.sep, '/'); - return { ...desc, unitPath: rel }; - } - const rest = real === best.realDir ? '' : real.slice(best.realDir.length + 1); - const unitPath = [best.normalizedDir, rest].filter(Boolean).join('/').replaceAll(path.sep, '/'); - return { ...desc, unitPath }; - }), - ), - }; + if (tegg?.moduleReferences || tegg?.moduleDescriptors) { + const normalizedTegg: TeggManifestExtension = { ...tegg }; + if (tegg.moduleReferences) { + normalizedTegg.moduleReferences = await Promise.all( + tegg.moduleReferences.map(async (ref) => ({ + ...ref, + path: await this.#normalizeTeggUnitPath(ref.path, moduleMap), + })), + ); + } + if (tegg.moduleDescriptors) { + normalizedTegg.moduleDescriptors = await Promise.all( + tegg.moduleDescriptors.map(async (desc) => ({ + ...desc, + unitPath: await this.#normalizeTeggUnitPath(desc.unitPath, moduleMap), + })), + ); + } + result.tegg = normalizedTegg; } return result; } + async #normalizeTeggUnitPath(unitPath: string, moduleMap: ModuleMapEntry[]): Promise { + if (!path.isAbsolute(unitPath)) return unitPath; + const real = await this.#realpath(unitPath); + let best: ModuleMapEntry | undefined; + for (const entry of moduleMap) { + if (real === entry.realDir || real.startsWith(entry.realDir + path.sep)) { + best = entry; + break; + } + } + if (!best) { + // Keep local app modules relative to baseDir so bundled runtime can + // resolve them under outputDir, and keep descriptor/reference keys equal. + return path.relative(this.#baseDir, real).replaceAll(path.sep, '/'); + } + const rest = real === best.realDir ? '' : real.slice(best.realDir.length + 1); + return [best.normalizedDir, rest].filter(Boolean).join('/').replaceAll(path.sep, '/'); + } + async #findPackageJsonFromNodeModules(name: string, startDir: string): Promise { let dir = startDir; const nameSegments = name.split('/'); diff --git a/tools/egg-bundler/test/EntryGenerator.test.ts b/tools/egg-bundler/test/EntryGenerator.test.ts index ae74cb106c..6d3560c1d2 100644 --- a/tools/egg-bundler/test/EntryGenerator.test.ts +++ b/tools/egg-bundler/test/EntryGenerator.test.ts @@ -152,6 +152,38 @@ describe('EntryGenerator', () => { ]); }); + it('includes app/port controller decorated files from tegg manifest descriptors', async () => { + const manifest = makeManifest({ + extensions: { + tegg: { + moduleReferences: [ + { + name: 'appPort', + path: 'app/port', + }, + ], + moduleDescriptors: [ + { + unitPath: 'app/port', + decoratedFiles: ['controller/HomeController.ts', 'manager/UserRoleManager.ts'], + }, + ], + }, + }, + }); + + const gen = new EntryGenerator({ baseDir: tmpDir, manifestLoader: createFakeLoader(manifest) }); + const result = await gen.generate(); + const worker = await fs.readFile(result.workerEntry, 'utf8'); + + expect(extractImports(worker).map((i) => i.specifier)).toEqual([ + '../../app/port/controller/HomeController.ts', + '../../app/port/manager/UserRoleManager.ts', + ]); + expect(worker).toContain('"moduleReferences"'); + expect(worker).toContain('"path": "app/port"'); + }); + it('skips resolveCache entries whose value is null', async () => { const manifest = makeManifest({ resolveCache: { diff --git a/tools/egg-bundler/test/ManifestLoader.test.ts b/tools/egg-bundler/test/ManifestLoader.test.ts index 69a7c0febf..1a506f165c 100644 --- a/tools/egg-bundler/test/ManifestLoader.test.ts +++ b/tools/egg-bundler/test/ManifestLoader.test.ts @@ -278,6 +278,68 @@ describe('ManifestLoader', () => { expect(loader.store.data).toBe(loaded); }); + it('normalizes tegg moduleReferences and moduleDescriptors to matching app-relative paths', async () => { + const baseDir = createTempApp(); + const portRoot = path.join(baseDir, 'app/port'); + const controllerFile = path.join(portRoot, 'controller/HomeController.ts'); + const managerFile = path.join(portRoot, 'manager/UserRoleManager.ts'); + fs.mkdirSync(path.dirname(controllerFile), { recursive: true }); + fs.mkdirSync(path.dirname(managerFile), { recursive: true }); + writeJson(path.join(baseDir, 'package.json'), {}); + writeJson(path.join(portRoot, 'package.json'), { + name: 'app-port', + eggModule: { + name: 'appPort', + }, + }); + fs.writeFileSync(controllerFile, 'export class HomeController {}\n'); + fs.writeFileSync(managerFile, 'export class UserRoleManager {}\n'); + + const manifestPath = path.join(baseDir, '.egg/manifest.json'); + writeJson( + manifestPath, + manifest({ + extensions: { + tegg: { + moduleReferences: [ + { + name: 'appPort', + path: portRoot, + }, + ], + moduleDescriptors: [ + { + name: 'appPort', + unitPath: portRoot, + decoratedFiles: ['controller/HomeController.ts', 'manager/UserRoleManager.ts'], + }, + ], + }, + }, + }), + ); + + const loader = new ManifestLoader({ baseDir, manifestPath, autoGenerate: false }); + const loaded = await loader.load(); + + expect(loaded.extensions.tegg).toEqual({ + moduleReferences: [ + { + name: 'appPort', + path: 'app/port', + }, + ], + moduleDescriptors: [ + { + name: 'appPort', + unitPath: 'app/port', + decoratedFiles: ['controller/HomeController.ts', 'manager/UserRoleManager.ts'], + }, + ], + }); + expect(loader.getTeggDecoratedFiles()).toEqual([controllerFile, managerFile]); + }); + it('merges file discovery entries that normalize to the same package path', async () => { const baseDir = createTempApp(); const directLib = path.join(baseDir, 'node_modules/direct/lib'); From 349a364ad310bf9ae3ebc454c1bc2291f453e66d Mon Sep 17 00:00:00 2001 From: killa Date: Wed, 6 May 2026 18:08:41 +0800 Subject: [PATCH 2/6] fix(loader): handle non-error import failures --- tegg/core/loader/src/LoaderUtil.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tegg/core/loader/src/LoaderUtil.ts b/tegg/core/loader/src/LoaderUtil.ts index 3dac05ddcd..9ec7d2c9fc 100644 --- a/tegg/core/loader/src/LoaderUtil.ts +++ b/tegg/core/loader/src/LoaderUtil.ts @@ -81,10 +81,11 @@ export class LoaderUtil { if (exports === undefined) { try { exports = await import(filePath); - } catch (e: any) { + } catch (e: unknown) { console.trace('[tegg/loader] loadFile %s error:', filePath); console.error(e); - throw new Error(`[tegg/loader] load ${filePath} failed: ${e.message}`, { + const message = e instanceof Error ? e.message : String(e); + throw new Error(`[tegg/loader] load ${filePath} failed: ${message}`, { cause: e, }); } From 14a239b66cacd74ca488f524b2cf0f3226bcd014 Mon Sep 17 00:00:00 2001 From: killa Date: Wed, 6 May 2026 18:11:58 +0800 Subject: [PATCH 3/6] fix(loader): fall back on null bundle hits --- tegg/core/loader/src/LoaderUtil.ts | 2 +- tegg/core/loader/test/Loader.test.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tegg/core/loader/src/LoaderUtil.ts b/tegg/core/loader/src/LoaderUtil.ts index 9ec7d2c9fc..f7c78e6e9d 100644 --- a/tegg/core/loader/src/LoaderUtil.ts +++ b/tegg/core/loader/src/LoaderUtil.ts @@ -78,7 +78,7 @@ export class LoaderUtil { // avoid windows path issue: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:' filePath = pathToFileURL(filePath).toString(); } - if (exports === undefined) { + if (exports == null) { try { exports = await import(filePath); } catch (e: unknown) { diff --git a/tegg/core/loader/test/Loader.test.ts b/tegg/core/loader/test/Loader.test.ts index be9d54de95..951ed49e21 100644 --- a/tegg/core/loader/test/Loader.test.ts +++ b/tegg/core/loader/test/Loader.test.ts @@ -64,6 +64,18 @@ describe('core/loader/test/Loader.test.ts', () => { ); assert.equal(PrototypeUtil.getFilePath(BundledService), bundledFile); }); + + it('should fall back to dynamic import when the bundle module loader returns null', async () => { + const appRepoFile = path.join(__dirname, './fixtures/modules/module-for-loader/AppRepo.ts'); + (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = () => null; + + const prototypes = await LoaderUtil.loadFile(appRepoFile); + + assert.deepEqual( + prototypes.map((proto) => proto.name), + ['AppRepo', 'AppRepo2'], + ); + }); }); describe('file has tsc error', () => { From c7396df58f55e74a8b8ce6c33b4f61aa5571e931 Mon Sep 17 00:00:00 2001 From: killa Date: Wed, 6 May 2026 18:39:47 +0800 Subject: [PATCH 4/6] fix(loader): wrap bundle loader failures --- tegg/core/loader/src/LoaderUtil.ts | 31 ++++++++++++++++++---------- tegg/core/loader/test/Loader.test.ts | 19 +++++++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/tegg/core/loader/src/LoaderUtil.ts b/tegg/core/loader/src/LoaderUtil.ts index f7c78e6e9d..e15bc29a3c 100644 --- a/tegg/core/loader/src/LoaderUtil.ts +++ b/tegg/core/loader/src/LoaderUtil.ts @@ -14,6 +14,13 @@ type BundleModuleGlobalThis = typeof globalThis & { __EGG_BUNDLE_MODULE_LOADER__?: BundleModuleLoader; }; +function createLoadError(filePath: string, e: unknown): Error { + const message = e instanceof Error ? e.message : String(e); + return new Error(`[tegg/loader] load ${filePath} failed: ${message}`, { + cause: e, + }); +} + interface LoaderUtilConfig { extraFilePattern?: string[]; } @@ -70,24 +77,26 @@ export class LoaderUtil { static async loadFile(filePath: string): Promise { const originalFilePath = filePath; - let exports: any = (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__?.( - originalFilePath.split('\\').join('/'), - ); - if (process.platform === 'win32') { - // convert to file:// url - // avoid windows path issue: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:' - filePath = pathToFileURL(filePath).toString(); + let exports: any; + try { + exports = (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__?.( + originalFilePath.split('\\').join('/'), + ); + } catch (e: unknown) { + throw createLoadError(originalFilePath, e); } if (exports == null) { + if (process.platform === 'win32') { + // convert to file:// url + // avoid windows path issue: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:' + filePath = pathToFileURL(filePath).toString(); + } try { exports = await import(filePath); } catch (e: unknown) { console.trace('[tegg/loader] loadFile %s error:', filePath); console.error(e); - const message = e instanceof Error ? e.message : String(e); - throw new Error(`[tegg/loader] load ${filePath} failed: ${message}`, { - cause: e, - }); + throw createLoadError(filePath, e); } } const clazzList: EggProtoImplClass[] = []; diff --git a/tegg/core/loader/test/Loader.test.ts b/tegg/core/loader/test/Loader.test.ts index 951ed49e21..14319a4aaf 100644 --- a/tegg/core/loader/test/Loader.test.ts +++ b/tegg/core/loader/test/Loader.test.ts @@ -14,6 +14,7 @@ type BundleModuleGlobalThis = typeof globalThis & { describe('core/loader/test/Loader.test.ts', () => { afterEach(() => { delete (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__; + LoaderUtil.setConfig({}); }); describe('module loader', () => { @@ -76,6 +77,24 @@ describe('core/loader/test/Loader.test.ts', () => { ['AppRepo', 'AppRepo2'], ); }); + + it('should wrap bundle module loader errors', async () => { + const bundledFile = '/bundle/app/service.ts'; + (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = () => { + throw 'bundle loader failed'; + }; + + await assert.rejects( + async () => { + await LoaderUtil.loadFile(bundledFile); + }, + (err: Error & { cause?: unknown }) => { + assert.equal(err.message, '[tegg/loader] load /bundle/app/service.ts failed: bundle loader failed'); + assert.equal(err.cause, 'bundle loader failed'); + return true; + }, + ); + }); }); describe('file has tsc error', () => { From 2c6da681efcdc5f04f10aad82502af5e1d21e465 Mon Sep 17 00:00:00 2001 From: killa Date: Wed, 6 May 2026 23:34:58 +0800 Subject: [PATCH 5/6] fix(loader): share bundle global typing --- packages/typings/src/index.ts | 4 ++++ plugins/mock/test/mock_service_cluster.test.ts | 2 +- tegg/core/loader/package.json | 1 + tegg/core/loader/src/LoaderUtil.ts | 9 +-------- tegg/core/loader/test/Loader.test.ts | 7 ++----- tools/egg-bundler/package.json | 1 + tools/egg-bundler/src/lib/EntryGenerator.ts | 5 ++--- .../__snapshots__/EntryGenerator.worker.canonical.snap | 5 ++--- 8 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/typings/src/index.ts b/packages/typings/src/index.ts index 3211d7e3da..cc1b9fadde 100644 --- a/packages/typings/src/index.ts +++ b/packages/typings/src/index.ts @@ -4,3 +4,7 @@ * standard import path. */ export type BundleModuleLoader = (filepath: string) => unknown; + +export type BundleModuleGlobalThis = typeof globalThis & { + __EGG_BUNDLE_MODULE_LOADER__?: BundleModuleLoader; +}; diff --git a/plugins/mock/test/mock_service_cluster.test.ts b/plugins/mock/test/mock_service_cluster.test.ts index ccced26e74..9d76c493f5 100644 --- a/plugins/mock/test/mock_service_cluster.test.ts +++ b/plugins/mock/test/mock_service_cluster.test.ts @@ -12,7 +12,7 @@ describe('test/mock_service_cluster.test.ts', () => { baseDir: getFixtures('demo_mock_service_cluster'), }); await app.ready(); - }); + }, 60000); afterAll(() => app.close()); afterEach(mm.restore); diff --git a/tegg/core/loader/package.json b/tegg/core/loader/package.json index a86832e5bc..8aa8cfe817 100644 --- a/tegg/core/loader/package.json +++ b/tegg/core/loader/package.json @@ -44,6 +44,7 @@ "@eggjs/core-decorator": "workspace:*", "@eggjs/metadata": "workspace:*", "@eggjs/tegg-types": "workspace:*", + "@eggjs/typings": "workspace:*", "globby": "catalog:", "is-type-of": "catalog:" }, diff --git a/tegg/core/loader/src/LoaderUtil.ts b/tegg/core/loader/src/LoaderUtil.ts index e15bc29a3c..cb92973032 100644 --- a/tegg/core/loader/src/LoaderUtil.ts +++ b/tegg/core/loader/src/LoaderUtil.ts @@ -3,17 +3,12 @@ import { pathToFileURL } from 'node:url'; import { PrototypeUtil } from '@eggjs/core-decorator'; import type { EggProtoImplClass } from '@eggjs/tegg-types'; +import type { BundleModuleGlobalThis } from '@eggjs/typings'; import { isClass } from 'is-type-of'; // Guard against poorly mocked module constructors. const Module = globalThis.module?.constructor?.length > 1 ? globalThis.module.constructor : BuiltinModule; -type BundleModuleLoader = (filepath: string) => unknown; - -type BundleModuleGlobalThis = typeof globalThis & { - __EGG_BUNDLE_MODULE_LOADER__?: BundleModuleLoader; -}; - function createLoadError(filePath: string, e: unknown): Error { const message = e instanceof Error ? e.message : String(e); return new Error(`[tegg/loader] load ${filePath} failed: ${message}`, { @@ -94,8 +89,6 @@ export class LoaderUtil { try { exports = await import(filePath); } catch (e: unknown) { - console.trace('[tegg/loader] loadFile %s error:', filePath); - console.error(e); throw createLoadError(filePath, e); } } diff --git a/tegg/core/loader/test/Loader.test.ts b/tegg/core/loader/test/Loader.test.ts index 14319a4aaf..f4acf1986a 100644 --- a/tegg/core/loader/test/Loader.test.ts +++ b/tegg/core/loader/test/Loader.test.ts @@ -3,14 +3,11 @@ import path from 'node:path'; import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator'; import { EggLoadUnitType } from '@eggjs/metadata'; +import type { BundleModuleGlobalThis } from '@eggjs/typings'; import { afterEach, describe, it } from 'vitest'; import { LoaderFactory, LoaderUtil } from '../src/index.ts'; -type BundleModuleGlobalThis = typeof globalThis & { - __EGG_BUNDLE_MODULE_LOADER__?: (filepath: string) => unknown; -}; - describe('core/loader/test/Loader.test.ts', () => { afterEach(() => { delete (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__; @@ -52,7 +49,7 @@ describe('core/loader/test/Loader.test.ts', () => { class BundledService {} SingletonProto()(BundledService); const bundledFile = '/bundle/app/port/manager/UserRoleManager.ts'; - (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { + (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = (filepath: string) => { assert.equal(filepath, bundledFile); return { BundledService }; }; diff --git a/tools/egg-bundler/package.json b/tools/egg-bundler/package.json index 91e30e1df1..d17d9f67a5 100644 --- a/tools/egg-bundler/package.json +++ b/tools/egg-bundler/package.json @@ -87,6 +87,7 @@ }, "dependencies": { "@eggjs/core": "workspace:*", + "@eggjs/typings": "workspace:*", "@utoo/pack": "catalog:", "execa": "catalog:", "js-yaml": "catalog:", diff --git a/tools/egg-bundler/src/lib/EntryGenerator.ts b/tools/egg-bundler/src/lib/EntryGenerator.ts index 5c7f3fe3d9..4f4075cb64 100644 --- a/tools/egg-bundler/src/lib/EntryGenerator.ts +++ b/tools/egg-bundler/src/lib/EntryGenerator.ts @@ -259,6 +259,7 @@ for (const [key, spec] of __EXTERNAL_SPECS) { import path from 'node:path'; import { ManifestStore } from '@eggjs/core'; +import type { BundleModuleGlobalThis } from '@eggjs/typings'; import { startEgg } from ${frameworkSpec}; import * as __frameworkModule from ${frameworkSpec}; @@ -314,9 +315,7 @@ for (const [appAbsRequest, targetRel] of __APP_RESOLVE_CACHE_ALIASES) { } } -const __bundleGlobalThis = globalThis as typeof globalThis & { - __EGG_BUNDLE_MODULE_LOADER__?: (filepath: string) => unknown; -}; +const __bundleGlobalThis = globalThis as BundleModuleGlobalThis; ManifestStore.setBundleStore(ManifestStore.fromBundle(MANIFEST_DATA as any, __outputDir)); __bundleGlobalThis.__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { return __getBundleMap(filepath); diff --git a/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap b/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap index d2092a39ee..45d50ed568 100644 --- a/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap +++ b/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap @@ -3,6 +3,7 @@ import path from 'node:path'; import { ManifestStore } from '@eggjs/core'; +import type { BundleModuleGlobalThis } from '@eggjs/typings'; import { startEgg } from "egg"; import * as __frameworkModule from "egg"; @@ -98,9 +99,7 @@ for (const [appAbsRequest, targetRel] of __APP_RESOLVE_CACHE_ALIASES) { } } -const __bundleGlobalThis = globalThis as typeof globalThis & { - __EGG_BUNDLE_MODULE_LOADER__?: (filepath: string) => unknown; -}; +const __bundleGlobalThis = globalThis as BundleModuleGlobalThis; ManifestStore.setBundleStore(ManifestStore.fromBundle(MANIFEST_DATA as any, __outputDir)); __bundleGlobalThis.__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { return __getBundleMap(filepath); From 49408cc222395dea6c16b96e4bae1817d060ecab Mon Sep 17 00:00:00 2001 From: killa Date: Thu, 7 May 2026 09:26:16 +0800 Subject: [PATCH 6/6] fix(loader): use shared global augmentation --- packages/typings/src/index.ts | 4 ---- packages/utils/src/import.ts | 11 +++-------- tegg/core/loader/src/LoaderUtil.ts | 6 ++---- tegg/core/loader/test/Loader.test.ts | 10 +++++----- tools/egg-bundler/src/lib/EntryGenerator.ts | 5 ++--- .../EntryGenerator.worker.canonical.snap | 5 ++--- 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/packages/typings/src/index.ts b/packages/typings/src/index.ts index cc1b9fadde..3211d7e3da 100644 --- a/packages/typings/src/index.ts +++ b/packages/typings/src/index.ts @@ -4,7 +4,3 @@ * standard import path. */ export type BundleModuleLoader = (filepath: string) => unknown; - -export type BundleModuleGlobalThis = typeof globalThis & { - __EGG_BUNDLE_MODULE_LOADER__?: BundleModuleLoader; -}; diff --git a/packages/utils/src/import.ts b/packages/utils/src/import.ts index 45cdeb199b..8313cfbf2c 100644 --- a/packages/utils/src/import.ts +++ b/packages/utils/src/import.ts @@ -5,6 +5,7 @@ import { pathToFileURL, fileURLToPath } from 'node:url'; import { debuglog } from 'node:util'; import type { BundleModuleLoader } from '@eggjs/typings'; +import type {} from '@eggjs/typings/global'; import { ImportResolveError } from './error/index.ts'; @@ -422,12 +423,6 @@ export function setSnapshotModuleLoader(loader: SnapshotModuleLoader): void { export type { BundleModuleLoader } from '@eggjs/typings'; -type BundleModuleGlobalThis = typeof globalThis & { - __EGG_BUNDLE_MODULE_LOADER__: BundleModuleLoader | undefined; -}; - -const bundleModuleGlobalThis = globalThis as BundleModuleGlobalThis; - function normalizeBundleModulePath(filepath: string): string { return filepath.split(path.win32.sep).join(path.posix.sep); } @@ -443,11 +438,11 @@ function normalizeBundleModulePath(filepath: string): string { * compatibility. */ export function setBundleModuleLoader(loader: BundleModuleLoader | undefined): void { - bundleModuleGlobalThis.__EGG_BUNDLE_MODULE_LOADER__ = loader; + globalThis.__EGG_BUNDLE_MODULE_LOADER__ = loader; } export async function importModule(filepath: string, options?: ImportModuleOptions): Promise { - const _bundleModuleLoader = bundleModuleGlobalThis.__EGG_BUNDLE_MODULE_LOADER__; + const _bundleModuleLoader = globalThis.__EGG_BUNDLE_MODULE_LOADER__; if (_bundleModuleLoader) { const hit = _bundleModuleLoader(normalizeBundleModulePath(filepath)); if (hit !== undefined) { diff --git a/tegg/core/loader/src/LoaderUtil.ts b/tegg/core/loader/src/LoaderUtil.ts index cb92973032..1158c2b962 100644 --- a/tegg/core/loader/src/LoaderUtil.ts +++ b/tegg/core/loader/src/LoaderUtil.ts @@ -3,7 +3,7 @@ import { pathToFileURL } from 'node:url'; import { PrototypeUtil } from '@eggjs/core-decorator'; import type { EggProtoImplClass } from '@eggjs/tegg-types'; -import type { BundleModuleGlobalThis } from '@eggjs/typings'; +import type {} from '@eggjs/typings/global'; import { isClass } from 'is-type-of'; // Guard against poorly mocked module constructors. @@ -74,9 +74,7 @@ export class LoaderUtil { const originalFilePath = filePath; let exports: any; try { - exports = (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__?.( - originalFilePath.split('\\').join('/'), - ); + exports = globalThis.__EGG_BUNDLE_MODULE_LOADER__?.(originalFilePath.split('\\').join('/')); } catch (e: unknown) { throw createLoadError(originalFilePath, e); } diff --git a/tegg/core/loader/test/Loader.test.ts b/tegg/core/loader/test/Loader.test.ts index f4acf1986a..a2dfb0ed8d 100644 --- a/tegg/core/loader/test/Loader.test.ts +++ b/tegg/core/loader/test/Loader.test.ts @@ -3,14 +3,14 @@ import path from 'node:path'; import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator'; import { EggLoadUnitType } from '@eggjs/metadata'; -import type { BundleModuleGlobalThis } from '@eggjs/typings'; +import type {} from '@eggjs/typings/global'; import { afterEach, describe, it } from 'vitest'; import { LoaderFactory, LoaderUtil } from '../src/index.ts'; describe('core/loader/test/Loader.test.ts', () => { afterEach(() => { - delete (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__; + globalThis.__EGG_BUNDLE_MODULE_LOADER__ = undefined; LoaderUtil.setConfig({}); }); @@ -49,7 +49,7 @@ describe('core/loader/test/Loader.test.ts', () => { class BundledService {} SingletonProto()(BundledService); const bundledFile = '/bundle/app/port/manager/UserRoleManager.ts'; - (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = (filepath: string) => { + globalThis.__EGG_BUNDLE_MODULE_LOADER__ = (filepath: string) => { assert.equal(filepath, bundledFile); return { BundledService }; }; @@ -65,7 +65,7 @@ describe('core/loader/test/Loader.test.ts', () => { it('should fall back to dynamic import when the bundle module loader returns null', async () => { const appRepoFile = path.join(__dirname, './fixtures/modules/module-for-loader/AppRepo.ts'); - (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = () => null; + globalThis.__EGG_BUNDLE_MODULE_LOADER__ = () => null; const prototypes = await LoaderUtil.loadFile(appRepoFile); @@ -77,7 +77,7 @@ describe('core/loader/test/Loader.test.ts', () => { it('should wrap bundle module loader errors', async () => { const bundledFile = '/bundle/app/service.ts'; - (globalThis as BundleModuleGlobalThis).__EGG_BUNDLE_MODULE_LOADER__ = () => { + globalThis.__EGG_BUNDLE_MODULE_LOADER__ = () => { throw 'bundle loader failed'; }; diff --git a/tools/egg-bundler/src/lib/EntryGenerator.ts b/tools/egg-bundler/src/lib/EntryGenerator.ts index 4f4075cb64..b533f100ef 100644 --- a/tools/egg-bundler/src/lib/EntryGenerator.ts +++ b/tools/egg-bundler/src/lib/EntryGenerator.ts @@ -259,7 +259,7 @@ for (const [key, spec] of __EXTERNAL_SPECS) { import path from 'node:path'; import { ManifestStore } from '@eggjs/core'; -import type { BundleModuleGlobalThis } from '@eggjs/typings'; +import type {} from '@eggjs/typings/global'; import { startEgg } from ${frameworkSpec}; import * as __frameworkModule from ${frameworkSpec}; @@ -315,9 +315,8 @@ for (const [appAbsRequest, targetRel] of __APP_RESOLVE_CACHE_ALIASES) { } } -const __bundleGlobalThis = globalThis as BundleModuleGlobalThis; ManifestStore.setBundleStore(ManifestStore.fromBundle(MANIFEST_DATA as any, __outputDir)); -__bundleGlobalThis.__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { +globalThis.__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { return __getBundleMap(filepath); }; diff --git a/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap b/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap index 45d50ed568..4b2f8e79e3 100644 --- a/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap +++ b/tools/egg-bundler/test/__snapshots__/EntryGenerator.worker.canonical.snap @@ -3,7 +3,7 @@ import path from 'node:path'; import { ManifestStore } from '@eggjs/core'; -import type { BundleModuleGlobalThis } from '@eggjs/typings'; +import type {} from '@eggjs/typings/global'; import { startEgg } from "egg"; import * as __frameworkModule from "egg"; @@ -99,9 +99,8 @@ for (const [appAbsRequest, targetRel] of __APP_RESOLVE_CACHE_ALIASES) { } } -const __bundleGlobalThis = globalThis as BundleModuleGlobalThis; ManifestStore.setBundleStore(ManifestStore.fromBundle(MANIFEST_DATA as any, __outputDir)); -__bundleGlobalThis.__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { +globalThis.__EGG_BUNDLE_MODULE_LOADER__ = (filepath) => { return __getBundleMap(filepath); };