title: dynamically include another webpack bundle inside an angular application
body: <p>I'm working on an Angular2 application that are bundled together with webpack. Everything works just fine. However this bundled application will be shipped independant and on runtime, before bootstrapping the app, I need to specify other NgModules into one of the modules I have bundled in my app. Basically think of it as two bundles included in index.html, and when bootstraped, bundle 1 goes into bundle2. Only bundle 2 bootstraps an app. So I've done a few experiments that I'd like to share with you to shed some light on this (I might be taking a rather dirty and completely wrong approach, so please correct if so. I don't mind dirty if it is the only way it can work though.). Only requirement is that nothing should be done during transpilation. The requirement is that I can include one or more bundles on my index.html and they can be hooked into the actual app without any prior knowledge of each other. </p>
  
  <p>The reason I'm doing this in the first place is because i work on a platform where other developers can hook in code by installing extensions. This means that I deliver the initial bundle, that has no knowledge of other bundles on transpile time what-so-ever. This was easy in AngularJS, but moving to Angular2 and onwards, this is not so easy anymore.</p>
  
  <p>I managed to delay bootstrapping the app with the following approach:</p>
  
  <p>In main.ts:</p>
  
  <pre><code>window["platformBrowserDynamicRef"] = platformBrowserDynamic();
  window["appModule"] = AppModule;
  </code></pre>
  
  <p>This works fine and when i call bootstrap manually, the app starts up just fine. 
  I've created another bundle that independently works fine as another app.</p>
  
  <p>When i try to bundle them together by importing the components in an implicit array like above, and i bootstrap the app, i get an error i don't know what to do about.</p>
  
  <pre><code>window["app"].modulesImport.push(CommonModule);
  window["app"].modulesImport.push(FormsModule);
  window["app"].modulesImport.push(BrowserModule);
  window["app"].modulesImport.push(NgGridModule);
  window["app"].modulesImport.push(ReactiveFormsModule);
  window["app"].modulesImport.push(BrowserAnimationsModule);
  window["app"].modulesImport.push(HttpModule);
  
  @NgModule({
    imports: window["app"].modulesImport,
    declarations: [
        DYNAMIC_DIRECTIVES,
        PropertyFilterPipe,
        PropertyDataTypeFilterPipe,
        LanguageFilterPipe,      
        PropertyNameBlackListPipe      
    ],
    exports: [
        DYNAMIC_DIRECTIVES,
        CommonModule,
        FormsModule,
        HttpModule
    ]
  })
  export class PartsModule {
  
      static forRoot()
      {
          return {
              ngModule: PartsModule,
              providers: [ ], // not used here, but if singleton needed
          };
      }
  }
  </code></pre>
  
  <p>Having the modules in an implicit array works fine, but as soon as i add a module from my other bundle i get the following error:</p>
  
  <pre><code>Uncaught Error: Unexpected value 'ExtensionsModule' imported by the module 'PartsModule'. Please add a @NgModule annotation.
      at syntaxError (http://localhost:3002/dist/app.bundle.js:43864:34) [&lt;root&gt;]
      at http://localhost:3002/dist/app.bundle.js:56319:44 [&lt;root&gt;]
      at Array.forEach (native) [&lt;root&gt;]
      at CompileMetadataResolver.getNgModuleMetadata (http://localhost:3002/dist/app.bundle.js:56302:49) [&lt;root&gt;]
      at CompileMetadataResolver.getNgModuleSummary (http://localhost:3002/dist/app.bundle.js:56244:52) [&lt;root&gt;]
      at http://localhost:3002/dist/app.bundle.js:56317:72 [&lt;root&gt;]
      at Array.forEach (native) [&lt;root&gt;]
      at CompileMetadataResolver.getNgModuleMetadata (http://localhost:3002/dist/app.bundle.js:56302:49) [&lt;root&gt;]
      at CompileMetadataResolver.getNgModuleSummary (http://localhost:3002/dist/app.bundle.js:56244:52) [&lt;root&gt;]
      at http://localhost:3002/dist/app.bundle.js:56317:72 [&lt;root&gt;]
      at Array.forEach (native) [&lt;root&gt;]
      at CompileMetadataResolver.getNgModuleMetadata (http://localhost:3002/dist/app.bundle.js:56302:49) [&lt;root&gt;]
      at JitCompiler._loadModules (http://localhost:3002/dist/app.bundle.js:67404:64) [&lt;root&gt;]
      at JitCompiler._compileModuleAndComponents (http://localhost:3002/dist/app.bundle.js:67363:52) [&lt;root&gt;]
  </code></pre>
  
  <p>I've also tried to AoT compile the ExtensionsModule which doesn't work either. 
  I also tried dynamically compiling the components on runtime using the JitCompiler to see if I could get them into the app that way around. Same error though.</p>
  
  <p>Are components decorated during boostrapping of the app or during transpilation?
  Peeking at the modules in my implicit array I can see they are decorated, but mine is not. Is that webpack inserting the calling code of the decorator method? I assume the decorator information is lost during transpilation, so maybe i need to temporary store it somehow so i can grab it elsewhere. (note that extensionsmodule in as the first element doesn't contain the decorator array)</p>
  
  <p><a href="https://i.stack.imgur.com/I7d2O.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/I7d2O.png" alt="modules that should be imported in my module"></a></p>
  
  <p>I guess Im looking for pointers on how to move forward from here.Maybe the NgModule annotation function would be a good place to start. Couldn't find that anywhere though. </p>
  
  <p><strong>EDIT</strong></p>
  
  <p>digging more into the issue I've tried the lazy loading approach using the Router, where first I'm getting this error, trying to load it with a js file (network tab shows that it never issues the request for the file):</p>
  
  <pre><code>const appRoutes: Routes = [
        {
          path: 'edit-entity/:type/:id/:culture',
          component: EntityEditor,
          resolve: {
              entity: EntityResolver,
              navigation: NavigationResolver
          },
          data: { shouldDetach: true},
          loadChildren: "/dist/extensions.bundle.js#ExtensionsBundle",
  
        },
        {
          path: '',
          component: Dashboard,
          resolve: {
              navigation: NavigationResolver
          },
          data: { shouldDetach: true}        
        }
      ];
  </code></pre>
  
  <p>Resolves in the following error:
  "Could not find module with name ExtensionsModule.js" </p>
  
  <p>Secondly i tried using a loadChildren function like this:</p>
  
  <pre><code>const appRoutes: Routes = [
        {
          path: 'edit-entity/:type/:id/:culture',
          component: EntityEditor,
          resolve: {
              entity: EntityResolver,
              navigation: NavigationResolver
          },
          data: { shouldDetach: true},
          loadChildren: () =&gt; {
              //"/dist/extensions.bundle.js#ExtensionsModule",            
              // return System.import('./dynamic.module').then((comp: any) =&gt; {
              //     return comp.otherExport;
              // });
  
              return window["lux"].modulesLazyImport[0];
          }
        },
        {
          path: '',
          component: Dashboard,
          resolve: {
              navigation: NavigationResolver
          },
          data: { shouldDetach: true}        
        }
      ];
  </code></pre>
  
  <p>Resolves in:</p>
  
  <pre><code>app.bundle.js:1548 ERROR Error: Uncaught (in promise): Error: No NgModule metadata found for 'ExtensionsModule'.
  Error: No NgModule metadata found for 'ExtensionsModule'.
      at NgModuleResolver.resolve (app.bundle.js:55709) [angular]
      at CompileMetadataResolver.getNgModuleMetadata (app.bundle.js:56288) [angular]
      at JitCompiler._loadModules (app.bundle.js:67404) [angular]
      at JitCompiler._compileModuleAndComponents (app.bundle.js:67363) [angular]
      at JitCompiler.compileModuleAsync (app.bundle.js:67325) [angular]
      at ModuleBoundCompiler.compileModuleAsync (app.bundle.js:67693) [angular]
      at MergeMapSubscriber.project (app.bundle.js:30608) [angular]
      at MergeMapSubscriber._tryNext (app.bundle.js:69744) [angular]
      at MergeMapSubscriber._next (app.bundle.js:69734) [angular]
      at MergeMapSubscriber.Subscriber.next (app.bundle.js:14584) [angular]
      at ScalarObservable._subscribe (app.bundle.js:69206) [angular]
      at ScalarObservable.Observable.subscribe (app.bundle.js:118) [angular]
      at MergeMapOperator.call (app.bundle.js:69709) [angular]
      at Observable.subscribe (app.bundle.js:115) [angular]
      at NgModuleResolver.resolve (app.bundle.js:55709) [angular]
      at CompileMetadataResolver.getNgModuleMetadata (app.bundle.js:56288) [angular]
      at JitCompiler._loadModules (app.bundle.js:67404) [angular]
      at JitCompiler._compileModuleAndComponents (app.bundle.js:67363) [angular]
      at JitCompiler.compileModuleAsync (app.bundle.js:67325) [angular]
      at ModuleBoundCompiler.compileModuleAsync (app.bundle.js:67693) [angular]
      at MergeMapSubscriber.project (app.bundle.js:30608) [angular]
      at MergeMapSubscriber._tryNext (app.bundle.js:69744) [angular]
      at MergeMapSubscriber._next (app.bundle.js:69734) [angular]
      at MergeMapSubscriber.Subscriber.next (app.bundle.js:14584) [angular]
      at ScalarObservable._subscribe (app.bundle.js:69206) [angular]
      at ScalarObservable.Observable.subscribe (app.bundle.js:118) [angular]
      at MergeMapOperator.call (app.bundle.js:69709) [angular]
      at Observable.subscribe (app.bundle.js:115) [angular]
      at resolvePromise (app.bundle.js:77503) [angular]
      at resolvePromise (app.bundle.js:77488) [angular]
      at :3000/dist/app.bundle.js:77537:17 [angular]
      at Object.onInvokeTask (app.bundle.js:4599) [angular]
      at ZoneDelegate.invokeTask (app.bundle.js:77289) [angular]
      at Zone.runTask (app.bundle.js:77179) [&lt;root&gt; =&gt; angular]
      at drainMicroTaskQueue (app.bundle.js:77433) [&lt;root&gt;]
      at HTMLDivElement.ZoneTask.invoke (app.bundle.js:77364) [&lt;root&gt;]
  </code></pre>
  
  <p><strong>EDIT</strong>
  I thought that maybe the error was because my annontations were never called. Not sure if that is the case. I desiced to go ES5 on it and push a component to my implicit list like below. The same error originates after all when trying to push a component to the declarations part of the parts.module.ts. </p>
  
  <pre><code>    var HelloWorldComponent = function () {
  
  };
  
  HelloWorldComponent.annotations = [
      new ng.core.Component({
          selector: 'hello-world',
          template: '&lt;h1&gt;Hello World!&lt;/h1&gt;',
      })
  ];
  
  window["lux"].componentsLazyImport.push(HelloWorldComponent);
  </code></pre>
  
  <p>So is this a bug in Angular 4.0.0 or is it not possible at all to create a plugin mechanism?</p>
  
  <p>Best regards
  Morten</p>