diff --git a/package.json b/package.json index 778ef17f26..7063b24ae8 100644 --- a/package.json +++ b/package.json @@ -82,5 +82,8 @@ "engines": { "node": ">=22.18.0" }, + "optionalDependencies": { + "@utoo/utoo-mingw64_nt-10.0-26100-x64": "npm:@utoo/utoo-win32-x64@1.0.28" + }, "packageManager": "pnpm@10.28.0" } diff --git a/packages/core/src/lifecycle.ts b/packages/core/src/lifecycle.ts index 59ffea584f..0a3ef9123e 100644 --- a/packages/core/src/lifecycle.ts +++ b/packages/core/src/lifecycle.ts @@ -254,14 +254,20 @@ export class Lifecycle extends EventEmitter { } async close(): Promise { - // close in reverse order: first created, last closed - const closeFns = Array.from(this.#closeFunctionSet); - debug('%s start trigger %d beforeClose functions', this.app.type, closeFns.length); - for (const fn of closeFns.reverse()) { - debug('%s trigger beforeClose at %o', this.app.type, fn.fullPath); - await utils.callFn(fn); - this.#closeFunctionSet.delete(fn); + if (this.#metadataOnly || this.#snapshotBuilding) { + debug('%s skip beforeClose functions in early-exit lifecycle mode', this.app.type); + this.#closeFunctionSet.clear(); + } else { + // close in reverse order: first created, last closed + const closeFns = Array.from(this.#closeFunctionSet); + debug('%s start trigger %d beforeClose functions', this.app.type, closeFns.length); + for (const fn of closeFns.reverse()) { + debug('%s trigger beforeClose at %o', this.app.type, fn.fullPath); + await utils.callFn(fn); + this.#closeFunctionSet.delete(fn); + } } + // Be called after other close callbacks this.app.emit('close'); this.removeAllListeners(); diff --git a/packages/core/test/snapshot.test.ts b/packages/core/test/snapshot.test.ts index f4337e5a30..0a6236ffed 100644 --- a/packages/core/test/snapshot.test.ts +++ b/packages/core/test/snapshot.test.ts @@ -188,6 +188,35 @@ describe('test/snapshot.test.ts', () => { // beforeClose is registered during configDidLoad iteration, which is skipped assert.ok(!beforeCloseCalled, 'beforeClose should NOT be called since configDidLoad is skipped'); }); + + it('should skip app.beforeClose callbacks when closing during snapshot build', async () => { + let beforeCloseCalled = false; + app = new EggCore({ snapshot: true }); + + app.lifecycle.addBootHook( + class Boot { + app: EggCore; + + constructor(app: EggCore) { + this.app = app; + } + + configWillLoad(): void { + this.app.beforeClose(() => { + beforeCloseCalled = true; + }); + } + }, + ); + + app.lifecycle.init(); + app.lifecycle.triggerConfigWillLoad(); + await app.ready(); + await app.close(); + app = undefined; + + assert.ok(!beforeCloseCalled, 'app.beforeClose should NOT be called during snapshot build close'); + }); }); describe('snapshotWillSerialize / snapshotDidDeserialize lifecycle hooks', () => { diff --git a/packages/egg/test/fixtures/apps/metadata-only-app/app.js b/packages/egg/test/fixtures/apps/metadata-only-app/app.js index 48fb87d301..f06b70efc0 100644 --- a/packages/egg/test/fixtures/apps/metadata-only-app/app.js +++ b/packages/egg/test/fixtures/apps/metadata-only-app/app.js @@ -2,6 +2,9 @@ module.exports = class MetadataOnlyBoot { constructor(app) { this.app = app; app.bootLog = []; + app.beforeClose(() => { + app.bootLog.push('app.beforeClose'); + }); } configWillLoad() { diff --git a/packages/egg/test/start.test.ts b/packages/egg/test/start.test.ts index 8eeba3c5b5..9c83a77416 100644 --- a/packages/egg/test/start.test.ts +++ b/packages/egg/test/start.test.ts @@ -29,6 +29,12 @@ describe('test/start.test.ts', () => { }); }); + it('should skip beforeClose callbacks when closing a metadataOnly app', async () => { + const app = await singleProcessApp('apps/metadata-only-app', { metadataOnly: true }); + await app.close(); + assert.deepStrictEqual(app.bootLog, ['loadMetadata']); + }); + describe('normal mode (baseline)', () => { let app: SingleModeApplication; diff --git a/tools/egg-bundler/README.md b/tools/egg-bundler/README.md index cf6a8b6dfd..4d1710aa1a 100644 --- a/tools/egg-bundler/README.md +++ b/tools/egg-bundler/README.md @@ -23,28 +23,28 @@ path is `/.egg/manifest.json`. ## Options -| Option | Description | -| --- | --- | -| `baseDir` | Application root directory. Required. | -| `outputDir` | Output directory for the bundled artifact. Required. | -| `manifestPath` | Path to `manifest.json`. Defaults to `/.egg/manifest.json`. | -| `framework` | Framework name or absolute path. Defaults to `egg`. | -| `mode` | Build mode, `production` or `development`. Defaults to `production`. | -| `tegg` | Accepted by `BundlerConfig`, but not applied by the current implementation yet. | -| `externals.force` | Package names to always keep external. | -| `externals.inline` | Package names to force inline even if auto-detected as external. | -| `pack.buildFunc` | Test hook for replacing the real `@utoo/pack` build entry. | -| `pack.rootPath` | Override the monorepo workspace root used by `@utoo/pack`. | +| Option | Description | +| ------------------ | ------------------------------------------------------------------------------- | +| `baseDir` | Application root directory. Required. | +| `outputDir` | Output directory for the bundled artifact. Required. | +| `manifestPath` | Path to `manifest.json`. Defaults to `/.egg/manifest.json`. | +| `framework` | Framework name or absolute path. Defaults to `egg`. | +| `mode` | Build mode, `production` or `development`. Defaults to `production`. | +| `tegg` | Accepted by `BundlerConfig`, but not applied by the current implementation yet. | +| `externals.force` | Package names to always keep external. | +| `externals.inline` | Package names to force inline even if auto-detected as external. | +| `pack.buildFunc` | Test hook for replacing the real `@utoo/pack` build entry. | +| `pack.rootPath` | Override the monorepo workspace root used by `@utoo/pack`. | ## Result `bundle()` resolves with: -| Field | Description | -| --- | --- | -| `outputDir` | Absolute output directory. | -| `files` | Sorted absolute paths for files written into the artifact. | -| `manifestPath` | Absolute path to `bundle-manifest.json`. | +| Field | Description | +| -------------- | ---------------------------------------------------------- | +| `outputDir` | Absolute output directory. | +| `files` | Sorted absolute paths for files written into the artifact. | +| `manifestPath` | Absolute path to `bundle-manifest.json`. | ## Running The Bundle