Explorar o código

fix(core): Ensure plugins instantiated only once per process

Fixes #213
Michael Bromley %!s(int64=6) %!d(string=hai) anos
pai
achega
7198b85ee7

+ 12 - 1
packages/core/e2e/fixtures/test-plugins.ts

@@ -16,17 +16,28 @@ import {
 
 export class TestPluginWithAllLifecycleHooks
     implements OnVendureBootstrap, OnVendureWorkerBootstrap, OnVendureClose, OnVendureWorkerClose {
+    private static onConstructorFn: any;
     private static onBootstrapFn: any;
     private static onWorkerBootstrapFn: any;
     private static onCloseFn: any;
     private static onWorkerCloseFn: any;
-    static init(bootstrapFn: any, workerBootstrapFn: any, closeFn: any, workerCloseFn: any) {
+    static init(
+        constructorFn: any,
+        bootstrapFn: any,
+        workerBootstrapFn: any,
+        closeFn: any,
+        workerCloseFn: any,
+    ) {
+        this.onConstructorFn = constructorFn;
         this.onBootstrapFn = bootstrapFn;
         this.onWorkerBootstrapFn = workerBootstrapFn;
         this.onCloseFn = closeFn;
         this.onWorkerCloseFn = workerCloseFn;
         return this;
     }
+    constructor() {
+        TestPluginWithAllLifecycleHooks.onConstructorFn();
+    }
     onVendureBootstrap(): void | Promise<void> {
         TestPluginWithAllLifecycleHooks.onBootstrapFn();
     }

+ 9 - 3
packages/core/e2e/plugin.e2e-spec.ts

@@ -16,6 +16,7 @@ import {
 
 describe('Plugins', () => {
     const bootstrapMockFn = jest.fn();
+    const onConstructorFn = jest.fn();
     const onBootstrapFn = jest.fn();
     const onWorkerBootstrapFn = jest.fn();
     const onCloseFn = jest.fn();
@@ -25,6 +26,7 @@ describe('Plugins', () => {
         ...testConfig,
         plugins: [
             TestPluginWithAllLifecycleHooks.init(
+                onConstructorFn,
                 onBootstrapFn,
                 onWorkerBootstrapFn,
                 onCloseFn,
@@ -51,16 +53,20 @@ describe('Plugins', () => {
         await server.destroy();
     });
 
+    it('constructs one instance for each process', () => {
+        expect(onConstructorFn).toHaveBeenCalledTimes(2);
+    });
+
     it('calls onVendureBootstrap', () => {
-        expect(onBootstrapFn).toHaveBeenCalled();
+        expect(onBootstrapFn).toHaveBeenCalledTimes(1);
     });
 
     it('calls onWorkerVendureBootstrap', () => {
-        expect(onWorkerBootstrapFn).toHaveBeenCalled();
+        expect(onWorkerBootstrapFn).toHaveBeenCalledTimes(1);
     });
 
     it('can modify the config in configure()', () => {
-        expect(bootstrapMockFn).toHaveBeenCalled();
+        expect(bootstrapMockFn).toHaveBeenCalledTimes(1);
         const configService: ConfigService = bootstrapMockFn.mock.calls[0][0];
         expect(configService instanceof ConfigService).toBe(true);
         expect(configService.defaultLanguageCode).toBe(LanguageCode.zh);

+ 1 - 1
packages/core/src/app.module.ts

@@ -11,7 +11,7 @@ import { I18nService } from './i18n/i18n.service';
 import { PluginModule } from './plugin/plugin.module';
 
 @Module({
-    imports: [ConfigModule, I18nModule, ApiModule, PluginModule],
+    imports: [ConfigModule, I18nModule, ApiModule, PluginModule.forRoot()],
 })
 export class AppModule implements NestModule, OnApplicationShutdown {
     constructor(private configService: ConfigService, private i18nService: I18nService) {}

+ 1 - 1
packages/core/src/data-import/data-import.module.ts

@@ -14,7 +14,7 @@ import { Populator } from './providers/populator/populator';
     // Important! PluginModule must be defined before ServiceModule
     // in order that overrides of Services (e.g. SearchService) are correctly
     // registered with the injector.
-    imports: [PluginModule, ServiceModule.forRoot(), ConfigModule],
+    imports: [PluginModule.forRoot(), ServiceModule.forRoot(), ConfigModule],
     exports: [ImportParser, Importer, Populator, FastImporterService, AssetImporter],
     providers: [ImportParser, Importer, Populator, FastImporterService, AssetImporter],
 })

+ 8 - 2
packages/core/src/plugin/plugin.module.ts

@@ -26,10 +26,16 @@ const PLUGIN_PROCESS_CONTEXT = 'PLUGIN_PROCESS_CONTEXT';
  * modules and in responsible for executing any lifecycle methods defined by the plugins.
  */
 @Module({
-    imports: [ConfigModule, ...getConfig().plugins],
-    providers: [{ provide: PLUGIN_PROCESS_CONTEXT, useValue: PluginProcessContext.Main }],
+    imports: [ConfigModule],
 })
 export class PluginModule implements OnModuleInit, OnModuleDestroy {
+    static forRoot(): DynamicModule {
+        return {
+            module: PluginModule,
+            providers: [{ provide: PLUGIN_PROCESS_CONTEXT, useValue: PluginProcessContext.Main }],
+            imports: [...getConfig().plugins],
+        };
+    }
     static forWorker(): DynamicModule {
         return {
             module: PluginModule,