Skip to content

Commit 2b72163

Browse files
authored
feat: add LifecyclePreLoad (#234)
<!-- 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 -->
1 parent 27fd9ff commit 2b72163

File tree

11 files changed

+173
-13
lines changed

11 files changed

+173
-13
lines changed

core/lifecycle/src/LifycycleUtil.ts

+5
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,9 @@ export class LifecycleUtil<T extends LifecycleContext, R extends LifecycleObject
9898
const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`);
9999
return proto.getMetaData<string>(LIFECYCLE_HOOK);
100100
}
101+
102+
static getStaticLifecycleHook(hookName: LifecycleHookName, clazz: EggProtoImplClass) {
103+
const LIFECYCLE_HOOK = Symbol.for(`EggPrototype#Lifecycle${hookName}`);
104+
return MetadataUtil.getMetaData<string>(LIFECYCLE_HOOK, clazz);
105+
}
101106
}

core/lifecycle/src/decorator/index.ts

+13
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,22 @@ function createLifecycle(hookName: LifecycleHookName) {
1010
};
1111
}
1212

13+
function createStaticLifecycle(hookName: LifecycleHookName) {
14+
return () => {
15+
return function(target: EggProtoImplClass, methodName: string) {
16+
if (typeof target !== 'function') {
17+
throw new Error(`${hookName} must be a static function`);
18+
}
19+
LifecycleUtil.setLifecycleHook(methodName, hookName, target);
20+
};
21+
};
22+
}
23+
24+
1325
export const LifecyclePostConstruct = createLifecycle('postConstruct');
1426
export const LifecyclePreInject = createLifecycle('preInject');
1527
export const LifecyclePostInject = createLifecycle('postInject');
1628
export const LifecycleInit = createLifecycle('init');
1729
export const LifecyclePreDestroy = createLifecycle('preDestroy');
1830
export const LifecycleDestroy = createLifecycle('destroy');
31+
export const LifecyclePreLoad = createStaticLifecycle('preLoad');

core/metadata/src/impl/ModuleLoadUnit.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type {
1919
import { Graph, GraphNode, MapUtil } from '@eggjs/tegg-common-util';
2020
import { PrototypeUtil, QualifierUtil } from '@eggjs/core-decorator';
2121
import { FrameworkErrorFormater } from 'egg-errors';
22-
import { IdenticalUtil } from '@eggjs/tegg-lifecycle';
22+
import { IdenticalUtil, LifecycleUtil } from '@eggjs/tegg-lifecycle';
2323
import { EggPrototypeFactory } from '../factory/EggPrototypeFactory';
2424
import { LoadUnitFactory } from '../factory/LoadUnitFactory';
2525
import { EggPrototypeCreatorFactory } from '../factory/EggPrototypeCreatorFactory';
@@ -182,6 +182,16 @@ export class ModuleLoadUnit implements LoadUnit {
182182
return clazzList;
183183
}
184184

185+
async preLoad() {
186+
const clazzList = this.loader.load();
187+
for (const protoClass of clazzList) {
188+
const fnName = LifecycleUtil.getStaticLifecycleHook('preLoad', protoClass);
189+
if (fnName) {
190+
await protoClass[fnName]?.();
191+
}
192+
}
193+
}
194+
185195
async init() {
186196
const clazzList = this.loadClazz();
187197
const protoGraph = new ModuleGraph(clazzList, this.unitPath, this.name);

core/types/lifecycle/EggObjectLifecycle.ts

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import type { EggObject, EggObjectLifeCycleContext } from '@eggjs/tegg-runtime';
44
* lifecycle hook interface for egg object
55
*/
66
export interface EggObjectLifecycle {
7+
/**
8+
* call before project load
9+
*/
10+
preLoad?(ctx: EggObjectLifeCycleContext): Promise<void>;
711
/**
812
* call after construct
913
*/

core/types/lifecycle/LifecycleHook.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface LifecycleContext {
44
}
55

66
export interface LifecycleObject<T extends LifecycleContext> extends IdenticalObject {
7+
preLoad?(): Promise<void>;
78
init?(ctx: T): Promise<void>;
89
destroy?(ctx: T): Promise<void>;
910
}

standalone/standalone/src/EggModuleLoader.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export class EggModuleLoader {
1010
this.moduleReferences = moduleReferences;
1111
}
1212

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

30-
async load(): Promise<LoadUnit[]> {
30+
private static async generateLoadUnits(moduleReferences: readonly ModuleReference[]) {
3131
const loadUnits: LoadUnit[] = [];
3232
const loaderCache = new Map<string, Loader>();
33-
const appGraph = this.buildAppGraph(loaderCache);
33+
const appGraph = EggModuleLoader.generateAppGraph(loaderCache, moduleReferences);
3434
appGraph.sort();
3535
const moduleConfigList = appGraph.moduleConfigList;
3636
for (const moduleConfig of moduleConfigList) {
@@ -40,6 +40,16 @@ export class EggModuleLoader {
4040
loadUnits.push(loadUnit);
4141
}
4242
return loadUnits;
43-
// return loadUnits;
43+
}
44+
45+
async load(): Promise<LoadUnit[]> {
46+
return await EggModuleLoader.generateLoadUnits(this.moduleReferences);
47+
}
48+
49+
static async preLoad(moduleReferences: readonly ModuleReference[]): Promise<void> {
50+
const loads = await EggModuleLoader.generateLoadUnits(moduleReferences);
51+
for (const load of loads) {
52+
await load.preLoad?.();
53+
}
4454
}
4555
}

standalone/standalone/src/Runner.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,7 @@ export class Runner {
8585
this.cwd = cwd;
8686
this.env = options?.env;
8787
this.name = options?.name;
88-
const moduleDirs = (options?.dependencies || []).concat(this.cwd);
89-
this.moduleReferences = moduleDirs.reduce((list, baseDir) => {
90-
const module = typeof baseDir === 'string' ? { baseDir } : baseDir;
91-
return list.concat(...ModuleConfigUtil.readModuleReference(module.baseDir, module));
92-
}, [] as readonly ModuleReference[]);
88+
this.moduleReferences = Runner.getModuleReferences(this.cwd, options?.dependencies);
9389
this.moduleConfigs = {};
9490
this.innerObjects = {
9591
moduleConfigs: [{
@@ -189,6 +185,19 @@ export class Runner {
189185
return [ standaloneLoadUnit, ...loadUnits ];
190186
}
191187

188+
static getModuleReferences(cwd: string, dependencies?: RunnerOptions['dependencies']) {
189+
const moduleDirs = (dependencies || []).concat(cwd);
190+
return moduleDirs.reduce((list, baseDir) => {
191+
const module = typeof baseDir === 'string' ? { baseDir } : baseDir;
192+
return list.concat(...ModuleConfigUtil.readModuleReference(module.baseDir, module));
193+
}, [] as readonly ModuleReference[]);
194+
}
195+
196+
static async preLoad(cwd: string, dependencies?: RunnerOptions['dependencies']) {
197+
const moduleReferences = Runner.getModuleReferences(cwd, dependencies);
198+
await EggModuleLoader.preLoad(moduleReferences);
199+
}
200+
192201
async init() {
193202
this.loadUnits = await this.load();
194203
const instances: LoadUnitInstance[] = [];

standalone/standalone/src/main.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import { Runner, RunnerOptions } from './Runner';
22

3+
export async function preLoad(cwd: string, dependencies?: RunnerOptions['dependencies']) {
4+
try {
5+
await Runner.preLoad(cwd, dependencies);
6+
} catch (e) {
7+
e.message = `[tegg/standalone] bootstrap standalone preLoad failed: ${e.message}`;
8+
throw e;
9+
}
10+
}
11+
312
export async function main<T = void>(cwd: string, options?: RunnerOptions): Promise<T> {
413
const runner = new Runner(cwd, options);
514
try {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
SingletonProto,
3+
LifecyclePreLoad,
4+
LifecyclePostConstruct,
5+
LifecyclePreInject,
6+
LifecyclePostInject,
7+
LifecycleInit,
8+
LifecyclePreDestroy,
9+
LifecycleDestroy,
10+
} from '@eggjs/tegg';
11+
import { Runner, MainRunner } from '@eggjs/tegg/standalone';
12+
13+
@Runner()
14+
@SingletonProto()
15+
export class Foo implements MainRunner<string[]> {
16+
17+
static staticCalled: string[] = [];
18+
19+
getLifecycleCalled() {
20+
return Foo.staticCalled;
21+
}
22+
23+
@LifecyclePreLoad()
24+
static async _preLoad() {
25+
Foo.staticCalled.push('preLoad');
26+
}
27+
28+
constructor() {
29+
Foo.staticCalled.push('construct');
30+
}
31+
32+
@LifecyclePostConstruct()
33+
protected async _postConstruct() {
34+
Foo.staticCalled.push('postConstruct');
35+
}
36+
37+
@LifecyclePreInject()
38+
protected async _preInject() {
39+
Foo.staticCalled.push('preInject');
40+
}
41+
42+
@LifecyclePostInject()
43+
protected async _postInject() {
44+
Foo.staticCalled.push('postInject');
45+
}
46+
47+
protected async init() {
48+
Foo.staticCalled.push('init should not called');
49+
}
50+
51+
@LifecycleInit()
52+
protected async _init() {
53+
Foo.staticCalled.push('init');
54+
}
55+
56+
@LifecyclePreDestroy()
57+
protected async _preDestroy() {
58+
Foo.staticCalled.push('preDestroy');
59+
}
60+
61+
@LifecycleDestroy()
62+
protected async _destroy() {
63+
Foo.staticCalled.push('destroy');
64+
}
65+
66+
async main(): Promise<string[]> {
67+
return Foo.staticCalled;
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "lifecycle",
3+
"eggModule": {
4+
"name": "lifecycle"
5+
}
6+
}

standalone/standalone/test/index.test.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { strict as assert } from 'node:assert';
1+
import { strict as assert, deepStrictEqual } from 'node:assert';
22
import path from 'node:path';
33
import fs from 'node:fs/promises';
44
import { ModuleConfig, ModuleConfigs } from '@eggjs/tegg/helper';
5-
import { main, StandaloneContext, Runner } from '..';
5+
import { main, StandaloneContext, Runner, preLoad } from '..';
66
import { crosscutAdviceParams, pointcutAdviceParams } from './fixtures/aop-module/Hello';
77
import { Foo } from './fixtures/dal-module/src/Foo';
88

@@ -276,4 +276,28 @@ describe('standalone/standalone/test/index.test.ts', () => {
276276
assert.equal(result, '{"body":{"fullname":"mock fullname","skipDependencies":true,"registryName":"ok"}}');
277277
});
278278
});
279+
280+
describe('lifecycle', () => {
281+
const fixturePath = path.join(__dirname, './fixtures/lifecycle');
282+
let Foo;
283+
284+
beforeEach(() => {
285+
delete require.cache[require.resolve(path.join(fixturePath, './foo'))];
286+
// eslint-disable-next-line @typescript-eslint/no-var-requires
287+
Foo = require(path.join(fixturePath, './foo')).Foo;
288+
});
289+
290+
it('should work', async () => {
291+
await preLoad(fixturePath);
292+
await main(fixturePath);
293+
deepStrictEqual(Foo.staticCalled, [
294+
'preLoad',
295+
'construct',
296+
'postConstruct',
297+
'preInject',
298+
'postInject',
299+
'init',
300+
]);
301+
});
302+
});
279303
});

0 commit comments

Comments
 (0)