Repository: artusjs/core
Pull request #68 (2022-05-13 13:19:38)

refactor(loader&scanner): 重构 DefineLoader 装饰器实现及注册逻辑


Patch:
diff --git a/src/constraints.ts b/src/constraints.ts
index af5b893..4a321e5 100644
--- a/src/constraints.ts
+++ b/src/constraints.ts
@@ -1,4 +1,5 @@
 export const DEFAULT_LOADER = 'module';
+export const LOADER_NAME_META = 'loader:name';
 
 export const ArtusInjectPrefix = 'artus#';
 
diff --git a/src/loader/base.ts b/src/loader/base.ts
new file mode 100644
index 0000000..f9bb30a
--- /dev/null
+++ b/src/loader/base.ts
@@ -0,0 +1,7 @@
+import { Loader, ManifestItem } from './types';
+
+export default class BaseLoader implements Loader {
+  async load(_item: ManifestItem) {
+    throw new Error('Not implemented');
+  }
+}
diff --git a/src/loader/decorator.ts b/src/loader/decorator.ts
index 300d8bb..57635e8 100644
--- a/src/loader/decorator.ts
+++ b/src/loader/decorator.ts
@@ -1,8 +1,7 @@
-import { LoaderFactory } from './factory';
-import { LoaderConstructor } from './types';
+import { LOADER_NAME_META } from '../constraints';
 
 export const DefineLoader = (loaderName: string): ClassDecorator => 
   (target: Function) => {
-    LoaderFactory.registerLoader(loaderName, target as unknown as LoaderConstructor);
+    Reflect.defineMetadata(LOADER_NAME_META, loaderName, target);
   }
 ;
diff --git a/src/loader/factory.ts b/src/loader/factory.ts
index 797b7a3..4fd95dc 100644
--- a/src/loader/factory.ts
+++ b/src/loader/factory.ts
@@ -1,6 +1,6 @@
 import * as path from 'path';
 import { Container } from '@artus/injection';
-import { ArtusInjectEnum, DEFAULT_LOADER, HOOK_FILE_LOADER } from '../constraints';
+import { ArtusInjectEnum, DEFAULT_LOADER, HOOK_FILE_LOADER, LOADER_NAME_META } from '../constraints';
 import { Manifest, ManifestItem, LoaderConstructor, LoaderHookUnit, LoaderCheckOptions } from './types';
 import ConfigurationHandler from '../configuration';
 import { LifecycleManager } from '../lifecycle';
@@ -10,7 +10,8 @@ export class LoaderFactory {
   private container: Container;
   private static loaderClazzMap: Map<string, LoaderConstructor> = new Map();
 
-  static registerLoader(loaderName: string, clazz: LoaderConstructor) {
+  static register(clazz: LoaderConstructor) {
+    const loaderName = Reflect.getMetadata(LOADER_NAME_META, clazz);
     this.loaderClazzMap.set(loaderName, clazz);
   }
 
@@ -22,31 +23,36 @@ export class LoaderFactory {
     return new LoaderFactory(container);
   }
 
-  async loadManifest(manifest: Manifest): Promise<void> {
-    const lifecycleManager: LifecycleManager = this.container.get(ArtusInjectEnum.LifecycleManager);
-    const configurationHandler: ConfigurationHandler = this.container.get(ConfigurationHandler);
+  get lifecycleManager(): LifecycleManager {
+    return this.container.get(ArtusInjectEnum.LifecycleManager);
+  }
 
+  get configurationHandler(): ConfigurationHandler {
+    return this.container.get(ConfigurationHandler);
+  }
+
+  async loadManifest(manifest: Manifest): Promise<void> {
     await this.loadItemList(manifest.items, {
       config: {
-        before: () => lifecycleManager.emitHook('configWillLoad'),
+        before: () => this.lifecycleManager.emitHook('configWillLoad'),
         after: () => {
           this.container.set({
             id: ArtusInjectEnum.Config,
-            value: configurationHandler.getMergedConfig()
+            value: this.configurationHandler.getMergedConfig()
           });
-          lifecycleManager.emitHook('configDidLoad');
+          this.lifecycleManager.emitHook('configDidLoad');
         }
       },
       'framework-config': {
         after: () => this.container.set({
           id: ArtusInjectEnum.Frameworks,
-          value: configurationHandler.getFrameworkConfig()
+          value: this.configurationHandler.getFrameworkConfig()
         })
       },
       'package-json': {
         after: () => this.container.set({
           id: ArtusInjectEnum.Packages,
-          value: configurationHandler.getPackages()
+          value: this.configurationHandler.getPackages()
         })
       }
     });
diff --git a/src/loader/impl/frameworkConfig.ts b/src/loader/impl/frameworkConfig.ts
index 498ccad..86e112e 100644
--- a/src/loader/impl/frameworkConfig.ts
+++ b/src/loader/impl/frameworkConfig.ts
@@ -7,7 +7,7 @@ import ConfigLoader from './config';
 import { isMatch } from '../../utils';
 
 @DefineLoader('framework-config')
-class FrameworkLoader extends ConfigLoader implements Loader {
+class FrameworkConfigLoader extends ConfigLoader implements Loader {
   constructor(container) {
     super(container);
   }
@@ -43,4 +43,4 @@ class FrameworkLoader extends ConfigLoader implements Loader {
   }
 }
 
-export default FrameworkLoader;
+export default FrameworkConfigLoader;
diff --git a/src/loader/impl/index.ts b/src/loader/impl/index.ts
index 68ad9c1..0d05df1 100644
--- a/src/loader/impl/index.ts
+++ b/src/loader/impl/index.ts
@@ -1,8 +1,19 @@
-export * from './module';
-export * from './config';
-export * from './exception';
-export * from './lifecycle';
-export * from './pluginMeta';
-export * from './pluginConfig';
-export * from './frameworkConfig';
-export * from './package';
+import ModuleLoader from './module';
+import ConfigLoader from './config';
+import ExceptionLoader from './exception';
+import LifecycleLoader from './lifecycle';
+import PluginMetaLoader from './pluginMeta';
+import PluginConfigLoader from './pluginConfig';
+import FrameworkConfigLoader from './frameworkConfig';
+import PackageLoader from './package';
+
+export {
+  ModuleLoader,
+  ConfigLoader,
+  ExceptionLoader,
+  LifecycleLoader,
+  PluginMetaLoader,
+  PluginConfigLoader,
+  FrameworkConfigLoader,
+  PackageLoader,
+};
diff --git a/src/loader/index.ts b/src/loader/index.ts
index 85564ce..2b4820f 100644
--- a/src/loader/index.ts
+++ b/src/loader/index.ts
@@ -1,9 +1,15 @@
 import { LoaderFactory } from './factory';
 import { Manifest, ManifestItem, Loader, LoaderConstructor } from './types';
 import { DefineLoader } from './decorator';
+import BaseLoader from './base';
 
 // Import inner impls
-import './impl';
+import * as LoaderImpls from './impl';
+
+// Register inner impls
+for (const [_, impl] of Object.entries(LoaderImpls)) {
+  LoaderFactory.register(impl);
+}
 
 export {
   // Decorator
@@ -11,6 +17,7 @@ export {
 
   // Class
   LoaderFactory,
+  BaseLoader,
 
   // Typings
   Loader,
diff --git a/src/scanner/scan.ts b/src/scanner/scan.ts
index 736a0e1..1610c3e 100644
--- a/src/scanner/scan.ts
+++ b/src/scanner/scan.ts
@@ -11,6 +11,7 @@ import {
     DEFAULT_CONFIG_DIR,
     ARTUS_DEFAULT_CONFIG_ENV,
     ArtusInjectEnum,
+    LOADER_NAME_META,
 } from '../constraints';
 import {
     ScannerOptions,
@@ -44,7 +45,17 @@ export class Scanner {
 
         this.itemMap = new Map(
           this.options.loaderListGenerator(DEFAULT_LOADER_LIST_WITH_ORDER)
-            .map((loaderName) => ([loaderName, []]))
+            .map((loaderNameOrClazz) => {
+              if(typeof loaderNameOrClazz === 'string') {
+                return [loaderNameOrClazz, []];
+              }
+              const loaderClazz = loaderNameOrClazz;
+              const loaderName = Reflect.getMetadata(LOADER_NAME_META, loaderClazz);
+              if (!loaderName) {
+                throw new Error(`Loader ${loaderClazz.name} must have a @DefineLoader() decorator.`);
+              }
+              return [loaderName, []];
+            })
         );
         this.loaderFactory = LoaderFactory.create(new Container(ArtusInjectEnum.DefaultContainerName));
     }
diff --git a/src/scanner/types.ts b/src/scanner/types.ts
index d5cda14..4c21035 100644
--- a/src/scanner/types.ts
+++ b/src/scanner/types.ts
@@ -1,3 +1,5 @@
+import { BaseLoader } from "../loader";
+
 export interface ScannerOptions {
   appName: string;
   extensions: string[];
@@ -5,7 +7,7 @@ export interface ScannerOptions {
   excluded?: string[];
   configDir: string;
   envs?: string[];
-  loaderListGenerator: (defaultLoaderList: string[]) => string[];
+  loaderListGenerator: (defaultLoaderList: string[]) => (string|typeof BaseLoader)[];
 }
 
 export interface WalkOptions {


Test patch:
diff --git a/test/fixtures/module-with-custom-loader/src/index.ts b/test/fixtures/module-with-custom-loader/src/index.ts
index 8330d23..6ee0889 100644
--- a/test/fixtures/module-with-custom-loader/src/index.ts
+++ b/test/fixtures/module-with-custom-loader/src/index.ts
@@ -1,21 +1,8 @@
 import path from 'path';
-import { DefineLoader, Loader, LoaderCheckOptions, Manifest, ManifestItem } from '../../../../src';
-import compatibleRequire from '../../../../src/utils/compatible-require';
+import { Manifest } from '../../../../src';
 
 const rootDir = path.resolve(__dirname, './');
 
-@DefineLoader('test-custom-loader')
-export class TestCustomLoader implements Loader {
-  async is(opts: LoaderCheckOptions) {
-    return opts.filename === 'testClazz.ts'
-  }
-
-  async load(item: ManifestItem) {
-    const clazz = await compatibleRequire(item.path);
-    console.log('TestCustomLoader.load ' + clazz.name);
-  }
-}
-
 
 export default ({
   items: [
diff --git a/test/fixtures/module-with-custom-loader/src/loader/test_custom_loader.ts b/test/fixtures/module-with-custom-loader/src/loader/test_custom_loader.ts
new file mode 100644
index 0000000..c14e5b2
--- /dev/null
+++ b/test/fixtures/module-with-custom-loader/src/loader/test_custom_loader.ts
@@ -0,0 +1,15 @@
+
+import { DefineLoader, Loader, LoaderCheckOptions, ManifestItem } from '../../../../../src';
+import compatibleRequire from '../../../../../src/utils/compatible-require';
+
+@DefineLoader('test-custom-loader')
+export default class TestCustomLoader implements Loader {
+  async is(opts: LoaderCheckOptions) {
+    return opts.filename === 'testClazz.ts'
+  }
+
+  async load(item: ManifestItem) {
+    const clazz = await compatibleRequire(item.path);
+    console.log('TestCustomLoader.load ' + clazz.name);
+  }
+}
\ No newline at end of file
diff --git a/test/loader.test.ts b/test/loader.test.ts
index 250b181..7e898e8 100644
--- a/test/loader.test.ts
+++ b/test/loader.test.ts
@@ -3,8 +3,7 @@ import 'reflect-metadata';
 import assert from 'assert';
 import path from 'path';
 import { Container } from '@artus/injection';
-import { Application, ArtusInjectEnum, LifecycleManager, LoaderFactory } from '../src';
-import ConfigurationHandler from '../src/configuration';
+import { LoaderFactory } from '../src';
 
 describe('test/loader.test.ts', () => {
   describe('module with ts', () => {
@@ -12,11 +11,6 @@ describe('test/loader.test.ts', () => {
       const container = new Container('testDefault');
       const loaderFactory = LoaderFactory.create(container);
 
-      // Mock for loader
-      const lifecycleManager = new LifecycleManager(null as unknown as Application, container);
-      container.set({ id: ArtusInjectEnum.LifecycleManager, value: lifecycleManager });
-      container.set({ type: ConfigurationHandler });
-
       const manifest = require('./fixtures/module-with-ts/src/index').default;
       await loaderFactory.loadManifest(manifest);
       assert((container.get('testServiceA') as any).testMethod() === 'Hello Artus');
@@ -27,11 +21,6 @@ describe('test/loader.test.ts', () => {
       const container = new Container('testDefault');
       const loaderFactory = LoaderFactory.create(container);
 
-      // Mock for loader
-      const lifecycleManager = new LifecycleManager(null as unknown as Application, container);
-      container.set({ id: ArtusInjectEnum.LifecycleManager, value: lifecycleManager });
-      container.set({ type: ConfigurationHandler });
-
       const manifest = require('./fixtures/module-with-js/src/index');
       await loaderFactory.loadManifest(manifest);
       const appProxy = new Proxy({}, {
@@ -44,16 +33,15 @@ describe('test/loader.test.ts', () => {
   });
   describe('module with custom loader', () => {
     it('should load module test.ts with custom loader', async () => {
+      // SEEME: the import&register code need be replaced by scanner at production.
+      const { default: TestCustomLoader } = require('./fixtures/module-with-custom-loader/src/loader/test_custom_loader');
+      LoaderFactory.register(TestCustomLoader);
+
       const { default: manifest } = require('./fixtures/module-with-custom-loader/src/index');
 
       const container = new Container('testDefault');
       const loaderFactory = LoaderFactory.create(container);
 
-      // Mock for loader
-      const lifecycleManager = new LifecycleManager(null as unknown as Application, container);
-      container.set({ id: ArtusInjectEnum.LifecycleManager, value: lifecycleManager });
-      container.set({ type: ConfigurationHandler });
-
       const loaderName = await loaderFactory.getLoaderName({
         filename: 'testClazz.ts',
         root: path.resolve(__dirname, './fixtures/module-with-custom-loader/src'),
diff --git a/test/scanner.test.ts b/test/scanner.test.ts
index a1db4b5..1f9eec7 100644
--- a/test/scanner.test.ts
+++ b/test/scanner.test.ts
@@ -1,5 +1,6 @@
 import { Scanner } from '../src/scanner';
 import path from 'path';
+import { LoaderFactory } from '../src';
 
 
 describe('test/scanner.test.ts', () => {
@@ -26,8 +27,16 @@ describe('test/scanner.test.ts', () => {
     });
 
     it('should scan module with custom loader', async () => {
-      await import('./fixtures/module-with-custom-loader/src');
-      const scanner = new Scanner({ needWriteFile: false, extensions: ['.ts', '.js', '.json'], configDir: '.', loaderListGenerator: () => ['test-custom-loader'] });
+      // TODO: allow scan custom loader
+      const { default: TestCustomLoader } = await import('./fixtures/module-with-custom-loader/src/loader/test_custom_loader');
+      LoaderFactory.register(TestCustomLoader);
+
+      const scanner = new Scanner({
+        needWriteFile: false,
+        extensions: ['.ts', '.js', '.json'],
+        configDir: '.',
+        loaderListGenerator: () => [TestCustomLoader]
+      });
       const scanResults = await scanner.scan(path.resolve(__dirname, './fixtures/module-with-custom-loader'));
       const { default: manifest } = scanResults;
       // console.log('manifest', manifest);