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.jslibrary.

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

Let’s to not wait for Ivy but instead deal with the current ViewEngine today#

What are we going to discuss here?

  • Why Angular?
  • The goal
  • What’s a plugin?
  • Why is it so hard to create a plugin with Angular?
  • Requirements
  • Other solutions
  • Towards a solution
  • Single bundle
  • Externals
  • Dynamic exports
  • Build plugin
  • Externals and Shared Angular library
  • Load plugin (client, server)
  • How to render a plugin?
  • How to isolate the main app from errors in a plugin?

Why Angular?

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.

The goal

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.

What is a plugin?

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.

Why is it so hard to create a plugin with Angular?

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 @NgModuletransitive 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:

  • The code that we write or the code that we take from node_modules
  • The code produced by AOT, think of component and module factories (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:

Requirements

  • AOT
  • Avoid duplicated code (packages like @angular/core{common,forms,router},rxjs,tslib)
  • Use a shared library in all plugins. But, DO NOT SHIP generated factories from that shared library in each plugin. Rather, reuse library code and factories.
  • For importing the external modules we just need to know one thing: their bundle file path.
  • Our code should recognize the module and place the plugin into the page.
  • Support server-side rendering
  • Load the module only when needed
  • Support the same level of optimization that Angular CLI gives us

All of these considerations led us to our own solution.

#angular #javascript

Building an extensible Dynamic Pluggable Enterprise Application with Angular
37.75 GEEK