Skip to content

Commit

Permalink
feat: add LifecyclePreLoad (#234)
Browse files Browse the repository at this point in the history
<!--
Thank you for your pull request. Please review below requirements.
Bug fixes and new features should include tests and possibly benchmarks.
Contributors guide:
https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md

感谢您贡献代码。请确认下列 checklist 的完成情况。
Bug 修复和新功能必须包含测试,必要时请附上性能测试。
Contributors guide:
https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md
-->

##### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to
[x]. -->

- [ ] `npm test` passes
- [ ] tests and/or benchmarks are included
- [ ] documentation is changed or added
- [ ] commit message follows commit guidelines

##### Affected core subsystem(s)
<!-- Provide affected core subsystem(s). -->


##### Description of change
<!-- Provide a description of the change below this comment. -->

<!--
- any feature?
- close https://github.com/eggjs/egg/ISSUE_URL
-->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
	- Introduced static lifecycle hooks for improved lifecycle management.
	- Added a `preLoad` method to enhance egg object initialization.
	- Implemented a `preLoad` method for module pre-loading operations.
- Created a new class `Foo` to manage lifecycle events with various
hooks.

- **Bug Fixes**
- Enhanced error handling in the `preLoad` function to provide clearer
error messages.

- **Tests**
- Expanded test suite to validate the functionality of the new lifecycle
methods and pre-loading behavior.

- **Documentation**
	- Added metadata for the new lifecycle module in `package.json`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
akitaSummer committed Sep 10, 2024
1 parent 27fd9ff commit 2b72163
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 13 deletions.
5 changes: 5 additions & 0 deletions core/lifecycle/src/LifycycleUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,9 @@ export class LifecycleUtil<T extends LifecycleContext, R extends LifecycleObject
const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`);
return proto.getMetaData<string>(LIFECYCLE_HOOK);
}

static getStaticLifecycleHook(hookName: LifecycleHookName, clazz: EggProtoImplClass) {
const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`);
return MetadataUtil.getMetaData<string>(LIFECYCLE_HOOK, clazz);
}
}
13 changes: 13 additions & 0 deletions core/lifecycle/src/decorator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 LifecyclePreLoad = createStaticLifecycle('preLoad');
12 changes: 11 additions & 1 deletion core/metadata/src/impl/ModuleLoadUnit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type {
import { Graph, GraphNode, MapUtil } from '@eggjs/tegg-common-util';
import { PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator';
import { FrameworkErrorFormater } from 'egg-errors';
import { IdenticalUtil } from '@eggjs/tegg-lifecycle';
import { IdenticalUtil, LifecycleUtil } from '@eggjs/tegg-lifecycle';
import { EggPrototypeFactory } from '../factory/EggPrototypeFactory';
import { LoadUnitFactory } from '../factory/LoadUnitFactory';
import { EggPrototypeCreatorFactory } from '../factory/EggPrototypeCreatorFactory';
Expand Down Expand Up @@ -182,6 +182,16 @@ export class ModuleLoadUnit implements LoadUnit {
return clazzList;
}

async preLoad() {
const clazzList = this.loader.load();
for (const protoClass of clazzList) {
const fnName = LifecycleUtil.getStaticLifecycleHook('preLoad', protoClass);
if (fnName) {
await protoClass[fnName]?.();
}
}
}

async init() {
const clazzList = this.loadClazz();
const protoGraph = new ModuleGraph(clazzList, this.unitPath, this.name);
Expand Down
4 changes: 4 additions & 0 deletions core/types/lifecycle/EggObjectLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import type { EggObject, EggObjectLifeCycleContext } from '@eggjs/tegg-runtime';
* lifecycle hook interface for egg object
*/
export interface EggObjectLifecycle {
/**
* call before project load
*/
preLoad?(ctx: EggObjectLifeCycleContext): Promise<void>;
/**
* call after construct
*/
Expand Down
1 change: 1 addition & 0 deletions core/types/lifecycle/LifecycleHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface LifecycleContext {
}

export interface LifecycleObject<T extends LifecycleContext> extends IdenticalObject {
preLoad?(): Promise<void>;
init?(ctx: T): Promise<void>;
destroy?(ctx: T): Promise<void>;
}
Expand Down
20 changes: 15 additions & 5 deletions standalone/standalone/src/EggModuleLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export class EggModuleLoader {
this.moduleReferences = moduleReferences;
}

private buildAppGraph(loaderCache: Map<string, Loader>) {
private static generateAppGraph(loaderCache: Map<string, Loader>, moduleReferences: readonly ModuleReference[]) {
const appGraph = new AppGraph();
for (const moduleConfig of this.moduleReferences) {
for (const moduleConfig of moduleReferences) {
const modulePath = moduleConfig.path;
const moduleNode = new ModuleNode(moduleConfig);
const loader = LoaderFactory.createLoader(modulePath, EggLoadUnitType.MODULE);
Expand All @@ -27,10 +27,10 @@ export class EggModuleLoader {
return appGraph;
}

async load(): Promise<LoadUnit[]> {
private static async generateLoadUnits(moduleReferences: readonly ModuleReference[]) {
const loadUnits: LoadUnit[] = [];
const loaderCache = new Map<string, Loader>();
const appGraph = this.buildAppGraph(loaderCache);
const appGraph = EggModuleLoader.generateAppGraph(loaderCache, moduleReferences);
appGraph.sort();
const moduleConfigList = appGraph.moduleConfigList;
for (const moduleConfig of moduleConfigList) {
Expand All @@ -40,6 +40,16 @@ export class EggModuleLoader {
loadUnits.push(loadUnit);
}
return loadUnits;
// return loadUnits;
}

async load(): Promise<LoadUnit[]> {
return await EggModuleLoader.generateLoadUnits(this.moduleReferences);
}

static async preLoad(moduleReferences: readonly ModuleReference[]): Promise<void> {
const loads = await EggModuleLoader.generateLoadUnits(moduleReferences);
for (const load of loads) {
await load.preLoad?.();
}
}
}
19 changes: 14 additions & 5 deletions standalone/standalone/src/Runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,7 @@ export class Runner {
this.cwd = cwd;
this.env = options?.env;
this.name = options?.name;
const moduleDirs = (options?.dependencies || []).concat(this.cwd);
this.moduleReferences = moduleDirs.reduce((list, baseDir) => {
const module = typeof baseDir === 'string' ? { baseDir } : baseDir;
return list.concat(...ModuleConfigUtil.readModuleReference(module.baseDir, module));
}, [] as readonly ModuleReference[]);
this.moduleReferences = Runner.getModuleReferences(this.cwd, options?.dependencies);
this.moduleConfigs = {};
this.innerObjects = {
moduleConfigs: [{
Expand Down Expand Up @@ -189,6 +185,19 @@ export class Runner {
return [ standaloneLoadUnit, ...loadUnits ];
}

static getModuleReferences(cwd: string, dependencies?: RunnerOptions['dependencies']) {
const moduleDirs = (dependencies || []).concat(cwd);
return moduleDirs.reduce((list, baseDir) => {
const module = typeof baseDir === 'string' ? { baseDir } : baseDir;
return list.concat(...ModuleConfigUtil.readModuleReference(module.baseDir, module));
}, [] as readonly ModuleReference[]);
}

static async preLoad(cwd: string, dependencies?: RunnerOptions['dependencies']) {
const moduleReferences = Runner.getModuleReferences(cwd, dependencies);
await EggModuleLoader.preLoad(moduleReferences);
}

async init() {
this.loadUnits = await this.load();
const instances: LoadUnitInstance[] = [];
Expand Down
9 changes: 9 additions & 0 deletions standalone/standalone/src/main.ts
Original file line number Diff line number Diff line change
@@ -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<T = void>(cwd: string, options?: RunnerOptions): Promise<T> {
const runner = new Runner(cwd, options);
try {
Expand Down
69 changes: 69 additions & 0 deletions standalone/standalone/test/fixtures/lifecycle/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
SingletonProto,
LifecyclePreLoad,
LifecyclePostConstruct,
LifecyclePreInject,
LifecyclePostInject,
LifecycleInit,
LifecyclePreDestroy,
LifecycleDestroy,
} from '@eggjs/tegg';
import { Runner, MainRunner } from '@eggjs/tegg/standalone';

@Runner()
@SingletonProto()
export class Foo implements MainRunner<string[]> {

static staticCalled: string[] = [];

getLifecycleCalled() {
return Foo.staticCalled;
}

@LifecyclePreLoad()
static async _preLoad() {
Foo.staticCalled.push('preLoad');
}

constructor() {
Foo.staticCalled.push('construct');
}

@LifecyclePostConstruct()
protected async _postConstruct() {
Foo.staticCalled.push('postConstruct');
}

@LifecyclePreInject()
protected async _preInject() {
Foo.staticCalled.push('preInject');
}

@LifecyclePostInject()
protected async _postInject() {
Foo.staticCalled.push('postInject');
}

protected async init() {
Foo.staticCalled.push('init should not called');
}

@LifecycleInit()
protected async _init() {
Foo.staticCalled.push('init');
}

@LifecyclePreDestroy()
protected async _preDestroy() {
Foo.staticCalled.push('preDestroy');
}

@LifecycleDestroy()
protected async _destroy() {
Foo.staticCalled.push('destroy');
}

async main(): Promise<string[]> {
return Foo.staticCalled;
}
}
6 changes: 6 additions & 0 deletions standalone/standalone/test/fixtures/lifecycle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "lifecycle",
"eggModule": {
"name": "lifecycle"
}
}
28 changes: 26 additions & 2 deletions standalone/standalone/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -276,4 +276,28 @@ 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');
let Foo;

beforeEach(() => {
delete require.cache[require.resolve(path.join(fixturePath, './foo'))];
// eslint-disable-next-line @typescript-eslint/no-var-requires
Foo = require(path.join(fixturePath, './foo')).Foo;
});

it('should work', async () => {
await preLoad(fixturePath);
await main(fixturePath);
deepStrictEqual(Foo.staticCalled, [
'preLoad',
'construct',
'postConstruct',
'preInject',
'postInject',
'init',
]);
});
});
});

0 comments on commit 2b72163

Please sign in to comment.