Inthis article, we will talk about how we can leverage Angular CLI build tools to create an AOT precompiled Angular plugin, which can share common code with other plugins and even work with Angular universal. This is an unofficial solution, but it works well for our case.
AngularInDepth_ is moving away from Medium. More recent articles are hosted on the new platform _inDepth.dev. Thanks for being part of indepth movement!
Here’s a simple representation of what we’re building:
Here we have a simple page where we get plugins configuration from a plugins-config.json
file. Then we first lazy load the AOT compiled plugin (plugin1.js
) which has a dependency to shared.js
. The shared library contains a Tabs component and Tabs ng factories. Sometime later we load the second plugin(plugin2.js
) that reuses the code from the previously loaded shared.js
library.
Here’s the angular-plugin-architecture Github repository if you want to take a look at the source code.
We use Angular CLI 7.3.6 in the demo
The Angular team and community are continuing the rapid growth of its ecosystem.
Angular helps us to structure our code in a consistent manner so every new developer can easily be involved in a project.
We like the fact that we’re following best web practices with Angular, just think of typescript, observables, server-side rendering, web workers, differential loading, progressive web application(PWA), lazy loading, etc. All of this helps us to adopt those features in a fast manner.
There are also more features that Angular offers us, like built-in dependency injection system, reactive forms, schematics and so on.
That is why we usually choose Angular when building an enterprise application.
One day our client asked us to add a new feature to his existing Angular Universal application. He wanted to have a pluggable Content Management System(CMS). The goal was to add the possibility to extend the functionality of the current app so that a 3rd party developer could easily develop a new module on his own and upload it. Then, the Angular app should pick it up without having to recompile the whole application and redeploy.
Simply put, we need to develop a plugin system.
Plugin systems allow an application to be extended without modification of the core application code.
It sounds simple but writing a plugin with Angular is always a challenge.
One of my colleagues, who worked with AngularJS long time ago, said that he saw an Angular 2 application written in plain es5 (hmm… maybe he found my jsfiddleor my old repo). So he suggested creating an Angular module in es5, putting it in a folder and that the main app should make it work somehow.
Don’t get me wrong but I’m with Angular since 2 alpha and Angular 2 (or just Angular) is a completely new thing.
Of course, the main pitfall here is Ahead Of Time (AOT) compilation. The main advantage of using AOT is better performance for your application.
I saw lots of examples out there that use JitCompiler for building a pluggable architecture. This is not the way we want to go. We have to keep our app fast and not include compiler code in the main bundle. That’s why we shouldn’t use es5 because only TypeScript code can be AOT precompiled. Also, the current implementation of Angular AOT is based on the @NgModule
transitive scope and requires that all should be compiled together. All of this makes things harder.
Another pitfall is that we need to share code between plugins to avoid code duplication. What kind of duplication can we consider? We distinguish two types of code that can be duplicated:
node_modules
component.ngfactory.js
and module.ngfactory.js
). Actually, this is a huge amount of code.In order to avoid these code duplications, we need to deal with the fact of how ViewEngine generates a factory.
If you don’t know, the ViewEngine is the current Angular rendering engine
The problem here is that code generated by the Angular compiler can point to ViewFactory from another piece of generated code. For instance, here’s how element definition is linked to ViewDefinitionFactory (Github source code)
elementDef(…, componentView?: null | ViewDefinitionFactory, componentRendererType?: RendererType2 | null)
So this results in getting duplicates of all the factories from the shared library.
Duplicates of ngFactories in non-optimized plugin
So when we were discussing the Angular plugin system, we should keep the following in mind:
@angular/core{common,forms,router},rxjs,tslib
)All of these considerations led us to our own solution.
#angular #javascript