Repository: artusjs/core
Pull request #183 (2022-08-25 12:26:59)

fix(loader): lifecycle loader support named export
related #163 

codecov-commenter: # [Codecov](https://codecov.io/gh/artusjs/core/pull/183?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#183](https://codecov.io/gh/artusjs/core/pull/183?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (75ce365) into [master](https://codecov.io/gh/artusjs/core/commit/fed9fbac9aa0c76c130de23c84e051343251f2bf?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (fed9fba) will **increase** coverage by `0.05%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master     #183      +/-   ##
==========================================
+ Coverage   89.91%   89.97%   +0.05%     
==========================================
  Files          51       51              
  Lines        1121     1127       +6     
  Branches      195      195              
==========================================
+ Hits         1008     1014       +6     
  Misses        113      113              
```


| [Impacted Files](https://codecov.io/gh/artusjs/core/pull/183?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [src/loader/impl/lifecycle.ts](https://codecov.io/gh/artusjs/core/pull/183/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2xvYWRlci9pbXBsL2xpZmVjeWNsZS50cw==) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)


Patch:
diff --git a/src/lifecycle/index.ts b/src/lifecycle/index.ts
index c83f216..fd7dcd3 100644
--- a/src/lifecycle/index.ts
+++ b/src/lifecycle/index.ts
@@ -1,5 +1,5 @@
 import { Constructable, Container } from '@artus/injection';
-import { Application } from '../types';
+import { Application, ApplicationLifecycle } from '../types';
 import {
   HOOK_NAME_META_PREFIX,
 } from '../constant';
@@ -22,6 +22,7 @@ export class LifecycleManager {
   hookFnMap: Map<string, HookFunction[]> = new Map();
   private app: Application;
   private container: Container;
+  private hookUnitSet: Set<Constructable<ApplicationLifecycle>> = new Set();
 
   constructor(app: Application, container: Container) {
     this.app = app;
@@ -48,6 +49,10 @@ export class LifecycleManager {
   }
 
   registerHookUnit(extClazz: Constructable<any>) {
+    if (this.hookUnitSet.has(extClazz)) {
+      return;
+    }
+    this.hookUnitSet.add(extClazz);
     const fnMetaKeys = Reflect.getMetadataKeys(extClazz);
     const extClazzInstance = this.container.get(extClazz);
     for (const fnMetaKey of fnMetaKeys) {
diff --git a/src/loader/factory.ts b/src/loader/factory.ts
index ccb744e..2963164 100644
--- a/src/loader/factory.ts
+++ b/src/loader/factory.ts
@@ -88,7 +88,7 @@ export class LoaderFactory {
   loadItem(item: ManifestItem): Promise<any> {
     const loaderName = item.loader || DEFAULT_LOADER;
     const loader = this.getLoader(loaderName);
-    loader.state = item._loaderState;
+    loader.state = item.loaderState;
     return loader.load(item);
   }
 
diff --git a/src/loader/impl/lifecycle.ts b/src/loader/impl/lifecycle.ts
index c1e672d..59e88cc 100644
--- a/src/loader/impl/lifecycle.ts
+++ b/src/loader/impl/lifecycle.ts
@@ -14,12 +14,24 @@ class LifecycleLoader implements Loader {
     this.container = container;
   }
 
+  get lifecycleManager(): LifecycleManager {
+    return this.container.get(ArtusInjectEnum.LifecycleManager);
+  }
+
   async load(item: ManifestItem) {
-    const extClazz: Constructable<ApplicationLifecycle> = await compatibleRequire(item.path);
-    const lifecycleManager: LifecycleManager = this.container.get(ArtusInjectEnum.LifecycleManager);
-    this.container.set({ type: extClazz });
-    lifecycleManager.registerHookUnit(extClazz);
-    return extClazz;
+    const origin: Constructable<ApplicationLifecycle>[] = await compatibleRequire(item.path, true);
+    item.loaderState = Object.assign({ exportNames: ['default'] }, item.loaderState);
+    const { loaderState: state } = item as { loaderState: { exportNames: string[] } };
+
+    const lifecycleClazzList = [];
+
+    for (const name of state.exportNames) {
+      const clazz = origin[name];
+      this.container.set({ type: clazz });
+      this.lifecycleManager.registerHookUnit(clazz);
+    }
+
+    return lifecycleClazzList;
   }
 }
 
diff --git a/src/loader/impl/module.ts b/src/loader/impl/module.ts
index ba2e989..80e9a26 100644
--- a/src/loader/impl/module.ts
+++ b/src/loader/impl/module.ts
@@ -14,8 +14,8 @@ class ModuleLoader implements Loader {
 
   async load(item: ManifestItem): Promise<Constructable[]> {
     const origin = await compatibleRequire(item.path, true);
-    item._loaderState = Object.assign({ exportNames: ['default'] }, item._loaderState);
-    const { _loaderState: state } = item as { _loaderState: { exportNames: string[] } };
+    item.loaderState = Object.assign({ exportNames: ['default'] }, item.loaderState);
+    const { loaderState: state } = item as { loaderState: { exportNames: string[] } };
 
     const modules: Constructable[] = [];
 
diff --git a/src/loader/impl/plugin_config.ts b/src/loader/impl/plugin_config.ts
index 365fb55..aa16eb5 100644
--- a/src/loader/impl/plugin_config.ts
+++ b/src/loader/impl/plugin_config.ts
@@ -28,7 +28,7 @@ class PluginConfigLoader extends ConfigLoader implements Loader {
             `Plugin ${pluginName} config can't have both package and path at ${item.path}`,
           );
         }
-        const loaderState = item._loaderState as { baseDir: string };
+        const loaderState = item.loaderState as { baseDir: string };
         pluginConfigItem.path = ArtusPlugin.getPath(pluginConfigItem.package, [loaderState?.baseDir]);
         delete pluginConfigItem.package;
         configObj[pluginName] = pluginConfigItem;
diff --git a/src/loader/types.ts b/src/loader/types.ts
index 5e1ddc2..a3d82e4 100644
--- a/src/loader/types.ts
+++ b/src/loader/types.ts
@@ -13,7 +13,7 @@ interface ManifestItem<LoaderState = unknown> extends Record<string, any> {
   loader?: string;
   source?: string;
   unitName?: string;
-  _loaderState?: LoaderState;
+  loaderState?: LoaderState;
 }
 
 interface LoaderFindOptions {
diff --git a/src/scanner/scan.ts b/src/scanner/scan.ts
index a6af857..72c6c00 100644
--- a/src/scanner/scan.ts
+++ b/src/scanner/scan.ts
@@ -188,7 +188,7 @@ export class Scanner {
         filename,
         loader,
         source: 'config',
-        _loaderState: {
+        loaderState: {
           baseDir,
         },
       };
diff --git a/src/scanner/utils.ts b/src/scanner/utils.ts
index bd97463..a2b3d87 100644
--- a/src/scanner/utils.ts
+++ b/src/scanner/utils.ts
@@ -71,7 +71,7 @@ export class ScanUtils {
           source,
         };
         if (loaderState) {
-          item._loaderState = loaderState;
+          item.loaderState = loaderState;
         }
         unitName && (item.unitName = unitName);
         const itemList = options.itemMap.get(item.loader ?? DEFAULT_LOADER);


Test patch:
diff --git a/test/fixtures/app_with_lifecycle/bootstrap_duplicated.ts b/test/fixtures/app_with_lifecycle/bootstrap_duplicated.ts
new file mode 100644
index 0000000..166a7ae
--- /dev/null
+++ b/test/fixtures/app_with_lifecycle/bootstrap_duplicated.ts
@@ -0,0 +1,1 @@
+export * from './bootstrap_ready';
diff --git a/test/fixtures/app_with_lifecycle/bootstrap.ts b/test/fixtures/app_with_lifecycle/bootstrap_load.ts
similarity index 65%
rename from test/fixtures/app_with_lifecycle/bootstrap.ts
rename to test/fixtures/app_with_lifecycle/bootstrap_load.ts
index 97aad97..7590f3f 100644
--- a/test/fixtures/app_with_lifecycle/bootstrap.ts
+++ b/test/fixtures/app_with_lifecycle/bootstrap_load.ts
@@ -8,7 +8,7 @@ import LifecycleList from './lifecyclelist';
 export const TEST_LIFE_CYCLE_LIST = 'TEST_LIFE_CYCLE_LIST';
 
 @LifecycleHookUnit()
-export default class AppLifecycle implements ApplicationLifecycle {
+export default class AppLoadLifecycle implements ApplicationLifecycle {
   @Inject(ArtusInjectEnum.Application)
   app: ArtusApplication;
 
@@ -36,22 +36,4 @@ export default class AppLifecycle implements ApplicationLifecycle {
     await new Promise(resolve => setTimeout(resolve, 100));
     this.lifecycleList.add('app_didLoad');
   }
-
-  @LifecycleHook('willReady')
-  async willReady() {
-    await new Promise(resolve => setTimeout(resolve, 100));
-    this.lifecycleList.add('app_willReady');
-  }
-
-  @LifecycleHook('didReady')
-  async didReady() {
-    await new Promise(resolve => setTimeout(resolve, 100));
-    this.lifecycleList.add('app_didReady');
-  }
-
-  @LifecycleHook()
-  async beforeClose() {
-    await new Promise(resolve => setTimeout(resolve, 100));
-    this.lifecycleList.add('app_beforeClose');
-  }
 }
diff --git a/test/fixtures/app_with_lifecycle/bootstrap_ready.ts b/test/fixtures/app_with_lifecycle/bootstrap_ready.ts
new file mode 100644
index 0000000..9f0d7b4
--- /dev/null
+++ b/test/fixtures/app_with_lifecycle/bootstrap_ready.ts
@@ -0,0 +1,37 @@
+import {
+  ArtusApplication,
+  ApplicationLifecycle, LifecycleHookUnit, LifecycleHook, ArtusInjectEnum,
+} from '../../../src/index';
+
+import { Container, Inject } from '@artus/injection';
+import LifecycleList from './lifecyclelist';
+
+@LifecycleHookUnit()
+export class AppReadyLifecycle implements ApplicationLifecycle {
+  @Inject(ArtusInjectEnum.Application)
+  app: ArtusApplication;
+
+  @Inject()
+  container: Container;
+
+  @Inject()
+  lifecycleList: LifecycleList;
+
+  @LifecycleHook('willReady')
+  async willReady() {
+    await new Promise(resolve => setTimeout(resolve, 100));
+    this.lifecycleList.add('app_willReady');
+  }
+
+  @LifecycleHook('didReady')
+  async didReady() {
+    await new Promise(resolve => setTimeout(resolve, 100));
+    this.lifecycleList.add('app_didReady');
+  }
+
+  @LifecycleHook()
+  async beforeClose() {
+    await new Promise(resolve => setTimeout(resolve, 100));
+    this.lifecycleList.add('app_beforeClose');
+  }
+}
diff --git a/test/fixtures/module_with_custom_loader/src/index.ts b/test/fixtures/module_with_custom_loader/src/index.ts
index af0d2c7..64354e8 100644
--- a/test/fixtures/module_with_custom_loader/src/index.ts
+++ b/test/fixtures/module_with_custom_loader/src/index.ts
@@ -11,7 +11,7 @@ export default ({
       extname: '.ts',
       filename: 'test_clazz.ts',
       loader: 'test-custom-loader',
-      _loaderState: {
+      loaderState: {
         hello: 'loaderState',
       },
     },