Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add LifecyclePreLoad #234

Merged
merged 1 commit into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
]);
});
});
});
Loading