From a737bfa2e3cee9f2a00a9cbd4bf8cd8cc50a9417 Mon Sep 17 00:00:00 2001 From: akitaSummer Date: Mon, 9 Sep 2024 15:57:42 +0800 Subject: [PATCH] feat: add LifecyclePreStandaloneLoad --- core/lifecycle/src/LifycycleUtil.ts | 5 ++ core/lifecycle/src/decorator/index.ts | 13 ++++ core/types/lifecycle/EggObjectLifecycle.ts | 4 ++ standalone/standalone/src/Runner.ts | 20 ++++++ standalone/standalone/src/main.ts | 9 +++ .../standalone/test/fixtures/lifecycle/foo.ts | 70 +++++++++++++++++++ .../test/fixtures/lifecycle/package.json | 6 ++ standalone/standalone/test/index.test.ts | 26 ++++++- 8 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 standalone/standalone/test/fixtures/lifecycle/foo.ts create mode 100644 standalone/standalone/test/fixtures/lifecycle/package.json diff --git a/core/lifecycle/src/LifycycleUtil.ts b/core/lifecycle/src/LifycycleUtil.ts index ee859a0a..2388d81a 100644 --- a/core/lifecycle/src/LifycycleUtil.ts +++ b/core/lifecycle/src/LifycycleUtil.ts @@ -98,4 +98,9 @@ export class LifecycleUtil(LIFECYCLE_HOOK); } + + static getStaticLifecycleHook(hookName: LifecycleHookName, clazz: EggProtoImplClass) { + const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`); + return MetadataUtil.getMetaData(LIFECYCLE_HOOK, clazz); + } } diff --git a/core/lifecycle/src/decorator/index.ts b/core/lifecycle/src/decorator/index.ts index 3529287c..0e793128 100644 --- a/core/lifecycle/src/decorator/index.ts +++ b/core/lifecycle/src/decorator/index.ts @@ -10,9 +10,22 @@ function createLifecycle(hookName: LifecycleHookName) { }; } +function createStaticLifecycle(hookName: LifecycleHookName) { + return () => { + return function(target: EggProtoImplClass, methodName: string) { + if (typeof target !== 'function') { + throw new Error(`${hookName} must be a static function`); + } + LifecycleUtil.setLifecycleHook(methodName, hookName, target); + }; + }; +} + + export const LifecyclePostConstruct = createLifecycle('postConstruct'); export const LifecyclePreInject = createLifecycle('preInject'); export const LifecyclePostInject = createLifecycle('postInject'); export const LifecycleInit = createLifecycle('init'); export const LifecyclePreDestroy = createLifecycle('preDestroy'); export const LifecycleDestroy = createLifecycle('destroy'); +export const LifecyclePreStandaloneLoad = createStaticLifecycle('preStandaloneLoad'); diff --git a/core/types/lifecycle/EggObjectLifecycle.ts b/core/types/lifecycle/EggObjectLifecycle.ts index 7db36e47..00f88e25 100644 --- a/core/types/lifecycle/EggObjectLifecycle.ts +++ b/core/types/lifecycle/EggObjectLifecycle.ts @@ -4,6 +4,10 @@ import type { EggObject, EggObjectLifeCycleContext } from '@eggjs/tegg-runtime'; * lifecycle hook interface for egg object */ export interface EggObjectLifecycle { + /** + * call before standalone load + */ + preStandaloneLoad?(ctx: EggObjectLifeCycleContext, eggObj: EggObject): Promise; /** * call after construct */ diff --git a/standalone/standalone/src/Runner.ts b/standalone/standalone/src/Runner.ts index 22c14f91..1d155161 100644 --- a/standalone/standalone/src/Runner.ts +++ b/standalone/standalone/src/Runner.ts @@ -23,6 +23,7 @@ import { ModuleConfigs, ConfigSourceQualifierAttribute, Logger, + LifecycleUtil, } from '@eggjs/tegg'; import { StandaloneUtil, MainRunner } from '@eggjs/tegg/standalone'; import { CrosscutAdviceFactory } from '@eggjs/tegg/aop'; @@ -40,6 +41,7 @@ import { MysqlDataSourceManager } from '@eggjs/tegg-dal-plugin/lib/MysqlDataSour import { SqlMapManager } from '@eggjs/tegg-dal-plugin/lib/SqlMapManager'; import { TableModelManager } from '@eggjs/tegg-dal-plugin/lib/TableModelManager'; import { TransactionPrototypeHook } from '@eggjs/tegg-dal-plugin/lib/TransactionPrototypeHook'; +import { LoaderFactory } from '@eggjs/tegg-loader'; export interface ModuleDependency extends ReadModuleReferenceOptions { baseDir: string; @@ -189,6 +191,24 @@ export class Runner { return [ standaloneLoadUnit, ...loadUnits ]; } + static async preLoad(cwd: string, dependencies?: RunnerOptions['dependencies']) { + const moduleDirs = (dependencies || []).concat(cwd); + const moduleReferences = moduleDirs.reduce((list, baseDir) => { + const module = typeof baseDir === 'string' ? { baseDir } : baseDir; + return list.concat(...ModuleConfigUtil.readModuleReference(module.baseDir, module)); + }, [] as readonly ModuleReference[]); + for (const module of moduleReferences) { + const protoClassList = LoaderFactory.createLoader(module.path, 'MODULE').load(); + // lifecycle preStandaloneLoad hook + for (const protoClass of protoClassList) { + const fnName = LifecycleUtil.getStaticLifecycleHook('preStandaloneLoad', protoClass); + if (fnName) { + await protoClass[fnName](); + } + } + } + } + async init() { this.loadUnits = await this.load(); const instances: LoadUnitInstance[] = []; diff --git a/standalone/standalone/src/main.ts b/standalone/standalone/src/main.ts index 7db7e781..6b6a3404 100644 --- a/standalone/standalone/src/main.ts +++ b/standalone/standalone/src/main.ts @@ -1,5 +1,14 @@ import { Runner, RunnerOptions } from './Runner'; +export async function preLoad(cwd: string, dependencies?: RunnerOptions['dependencies']) { + try { + await Runner.preLoad(cwd, dependencies); + } catch (e) { + e.message = `[tegg/standalone] bootstrap standalone preLoad failed: ${e.message}`; + throw e; + } +} + export async function main(cwd: string, options?: RunnerOptions): Promise { const runner = new Runner(cwd, options); try { diff --git a/standalone/standalone/test/fixtures/lifecycle/foo.ts b/standalone/standalone/test/fixtures/lifecycle/foo.ts new file mode 100644 index 00000000..e50d491b --- /dev/null +++ b/standalone/standalone/test/fixtures/lifecycle/foo.ts @@ -0,0 +1,70 @@ +import { + SingletonProto, + LifecyclePreStandaloneLoad, + LifecyclePostConstruct, + LifecyclePreInject, + LifecyclePostInject, + LifecycleInit, + LifecyclePreDestroy, + LifecycleDestroy, +} from '@eggjs/tegg'; +import { Runner, MainRunner } from '@eggjs/tegg/standalone'; + +@Runner() +@SingletonProto() +export class Foo implements MainRunner { + private called: string[] = []; + + static staticCalled: string[] = []; + + getLifecycleCalled() { + return this.called; + } + + @LifecyclePreStandaloneLoad() + static async _preStandaloneLoad() { + Foo.staticCalled.push('preStandaloneLoad'); + } + + constructor() { + this.called.push('construct'); + } + + @LifecyclePostConstruct() + protected async _postConstruct() { + this.called.push('postConstruct'); + } + + @LifecyclePreInject() + protected async _preInject() { + this.called.push('preInject'); + } + + @LifecyclePostInject() + protected async _postInject() { + this.called.push('postInject'); + } + + protected async init() { + this.called.push('init should not called'); + } + + @LifecycleInit() + protected async _init() { + this.called.push('init'); + } + + @LifecyclePreDestroy() + protected async _preDestroy() { + this.called.push('preDestroy'); + } + + @LifecycleDestroy() + protected async _destroy() { + this.called.push('destroy'); + } + + async main(): Promise { + return this.called; + } +} diff --git a/standalone/standalone/test/fixtures/lifecycle/package.json b/standalone/standalone/test/fixtures/lifecycle/package.json new file mode 100644 index 00000000..75761a44 --- /dev/null +++ b/standalone/standalone/test/fixtures/lifecycle/package.json @@ -0,0 +1,6 @@ +{ + "name": "lifecycle", + "eggModule": { + "name": "lifecycle" + } +} diff --git a/standalone/standalone/test/index.test.ts b/standalone/standalone/test/index.test.ts index 1552693c..9cdf3c0c 100644 --- a/standalone/standalone/test/index.test.ts +++ b/standalone/standalone/test/index.test.ts @@ -1,8 +1,8 @@ -import { strict as assert } from 'node:assert'; +import { strict as assert, deepStrictEqual } from 'node:assert'; import path from 'node:path'; import fs from 'node:fs/promises'; import { ModuleConfig, ModuleConfigs } from '@eggjs/tegg/helper'; -import { main, StandaloneContext, Runner } from '..'; +import { main, StandaloneContext, Runner, preLoad } from '..'; import { crosscutAdviceParams, pointcutAdviceParams } from './fixtures/aop-module/Hello'; import { Foo } from './fixtures/dal-module/src/Foo'; @@ -276,4 +276,26 @@ describe('standalone/standalone/test/index.test.ts', () => { assert.equal(result, '{"body":{"fullname":"mock fullname","skipDependencies":true,"registryName":"ok"}}'); }); }); + + describe('lifecycle', () => { + const fixturePath = path.join(__dirname, './fixtures/lifecycle'); + + it('preLoad should work', async () => { + await preLoad(fixturePath); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { Foo } = require(path.join(fixturePath, './foo')); + deepStrictEqual(Foo.staticCalled, [ 'preStandaloneLoad' ]); + }); + + it('should work', async () => { + const result = await main(fixturePath); + assert.deepStrictEqual(result, [ + 'construct', + 'postConstruct', + 'preInject', + 'postInject', + 'init', + ]); + }); + }); });