10

I am working on an open source platform, similar to WordPress and other CMS apps, but focusing on interopability with the typical Java build pipeline.

The goal is to create some reusable libraries that provide build/runtime dependencies for developers to create feature modules that can be applied to the platform after it's deployed [plugins].

I created an original version that did this well, using AngularJS + Spring framework.
Essentially it would scan dependency jars for static files matching a specific pattern, create MVC resource resolution to the files, then include those file urls into the index page.

This effectively allowed a resource library to include server and client side resources to extend the running platform.

*BUT! A coworker said my font-end was out of date, that I needed to update Angular =P

So I've began rewriting the UI in Angular 4 as a separate resources dependency that can be used in a feature module project for testing, before publishing as a jar.
I really love Angular 4 [TS] as it's more like developing server code which I am proficient at.

Getting to the point... if a feature modules provides client side resources, how do I load them at runtime?
I think I could use JIT and load the js paths in the browser, but I want to use AOT for performance reasons.

I'm considering - unpacking the resources, running a compilation, then serving the page.
Or is there a way to dynamically load an Angular module after the AOT has already been performed?

Referenced Project - https://github.com/savantly-net/sprout-platform

Jeremy
  • 2,970
  • 1
  • 26
  • 50
  • perhaps the Angular documentation would help? https://angular.io/guide/dynamic-component-loader – GreyBeardedGeek Sep 16 '17 at 23:10
  • Unfortunately, if I understand it correctly, the app would still need to know about the components that would be created dynamically. – Jeremy Sep 17 '17 at 00:50
  • I'd like to specifically add new modules after the app has been compiled – Jeremy Sep 17 '17 at 00:51
  • 1
    Unfortunately, I don't believe this is possible with AOT. What would be interesting is if you can have both AOT and JIT components running together. Maybe using some type of proxy `ngModule` with a common interface and loading the runtime components in a separate file – Everest Sep 25 '17 at 16:33
  • thanks @Everest, I do know we can import the compiler so that JIT can be used with an AOT compiled application. It wouldn't satisfy the "no compilation" requirement, but it's a step in the right direction. – Jeremy Sep 25 '17 at 18:41
  • Currently I'm looking at using AOT on the plugins, then using SystemJS to load them at run-time. I'm not sure this will work - I might need to use the compiler to create the components. Got some inspiration from this blog - https://blog.angularindepth.com/here-is-what-you-need-to-know-about-dynamic-components-in-angular-ac1e96167f9e – Jeremy Sep 25 '17 at 18:42
  • Why you do not look on LazyLoading. If I understand right you want to load some modules from the server after you init base app. And then when you give asses to some modules you load it. Lazy loading gives you the possibility to load different files with your modules only using right routing and right configured guard. Maybe this helps you but I'm not sure that right understand you https://blog.thoughtram.io/angular/2016/07/18/guards-in-angular-2.html, http://blog.angular-university.io/angular2-ngmodule/ – Roman Skydan Sep 25 '17 at 21:02
  • Thanks @RomaSkydan . Unfortunately, the app module would still need to know about the feature module to have it lazy loaded, afaik. I'd like the feature modules loaded at runtime, but without the app module knowing anything about them when it starts – Jeremy Sep 25 '17 at 21:10
  • 1
    No app will not know anything about loaded modules. Only link to it. And after you call this link you can call the function that load module from the server for you like there https://stackoverflow.com/questions/42953858/loading-modules-from-different-server-at-runtime :) I think this better way than use AOT and JIT in the same time. And it gives you a better performance – Roman Skydan Sep 25 '17 at 21:21
  • 1
    For more security, you can look on https://universal.angular.io/ it gives you a server-side rendering. But I do not work with it :( that's why I don't know how hard it for setup and using – Roman Skydan Sep 25 '17 at 21:23
  • 1
    You can have multiple AOT compiled Angular apps in the same page. Would that work for you? (but personally I think it's best to pre-compile combinations of feature modules in separate app-bundles and then only have one app in the page) – Peter Salomonsen Sep 26 '17 at 14:01
  • The Universal.angular project is interesting. It's similar to how my first platform was designed. It used Thymeleaf for server side rendering of an index page, with the boostrap scripting added at 'request' time. Then when the page loaded, AngularJS would kick in. – Jeremy Sep 26 '17 at 22:10
  • @PeterSalomonsen - I've been curious if having more than one app on the page would satisfy the requirement. If they could share common services from a root app, it seems like that should work. I've started extracting a 'menu' and 'security' components that can be referenced at compile time from the host app and a feature module; If there is some way to just drop a UMD or IIFE on the page after the host app starts, the feature modules could register with the host app, and get the menu/security services injected into them. – Jeremy Sep 26 '17 at 22:15

1 Answers1

0

I was able to load an external plugin by referencing the generated NgModuleFactory files in my main app, but still required some pre-compiled configuration in the host app.

After many hours of trying different solutions, I've determined the best for my use-case is to provide the plugins as typescript files in a folder external to the application directory, then do a compile when ready to start serving the app.

An example of how this is working for me - https://github.com/savantly-net/sprout-platform/tree/development/web/sprout-web-ui

I use a simple ts module to import/export the plugins - [using a relative path here, but also works with an absolute path]
https://github.com/savantly-net/sprout-platform/blob/development/web/sprout-web-ui/sprout.conf.ts

import { WikiModule } from './plugins/wiki/';

export const plugins = [
  WikiModule
]

Then use the spread operator to re-export the modules -
https://github.com/savantly-net/sprout-platform/blob/development/web/sprout-web-ui/src/app/plugins/plugins.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { plugins } from '../../../sprout.conf';
import { MenuModule } from '@savantly/ngx-menu';
import { SecurityModule } from '@savantly/ngx-security';
import { SproutPluginModule } from '@savantly/ngx-sprout-plugin';
import { Observable } from 'rxjs/Observable';

const log = (msg: string, obj?: any) => {
  console.log('[PluginsModule] ' + msg);
  if (obj) {
    console.log(obj);
  }
}


@NgModule({
  imports: [
    CommonModule,
    SproutPluginModule,
    MenuModule,
    SecurityModule,
    ...plugins
  ],
  exports: [...plugins],
  declarations: []
})
export class PluginsModule {
  private ngModules: NgModule[] = [];

  getNgModules(): Observable<NgModule[]> {
    return Observable.of(this.ngModules);
  }

  constructor() {
   log('Loaded plugins');
  }
}  

This is not a true plug-and-play solution, but without having the plugin folders referenced explicitly in the [pre-compiled] configuration file, the build process would not be able to perform tree-shaking.

Jeremy
  • 2,970
  • 1
  • 26
  • 50