Aurelia front-end microservices and Single-Spa


#1

We’re planning on consolidating some of our front-end apps into a portal application.
The idea is that the portal application will handle cross-cutting concerns such as notifications, login, …

For that we want to build a single application, which handles those things and then load separate apps into the page. Some of these apps may or may not be Aurelia based.

I’ve come accross a project called Single Spa (https://single-spa.js.org/) which allows you to do exactly that. It has plugins for mounting Angular, Vue, React, and a whole bunch of other frameworks. Unfortunately, Aurelia is not one of them.

Has anyone attempted this and can share the mounting plugin? And, in the other (more likely case), does anyone have any pointers on how you could build one?

Essentially, Single-Spa requires you to:

  • Register the application
  • provide a file with three methods:
    • Bootstrap
    • Mount
    • Unmount

The ultimate goal is to have each application in a separate repo, with a separate build process, deployment process, aka, completely independent (potentially even served on different URL’s, but that maybe difficult).

There was a previous thread regarding micro front-ends, but that talks about loading multiple Aurelia apps in one page. In this case, I’d prefer to use Single-Spa as it allows you to load multiple non-related apps with different frameworks into on page.
Thread for reference: Micro frontends with Aurelia


#2

@huochunpeng I believe you have a solution that demonstrates this. Would you mind sharing that with @Kennethtruyers It’s something that’s been coming up more often recently. I think it might also make a good blog post for the official blog if you’re interested in putting that together :wink:


#3

@Kennethtruyers your requirement matches what I did.

Yes, I have a POC here, an Aurelia host app can load extension apps written in aurelia/vue/react/what-ever. I plan to write two more examples with host app in vue/react, when I got enough time :slight_smile: .

This setup should be considerably simpler and more flexible than any other webpack based solution. It’s tricky for webpack because it does not have real runtime module loader.

AMD loader natively has the ability to import unknown modules on the fly.

Note: it runs with my AMD loader implementation called dumber-module-loader, packed with my WIP bundler called dumber. If you have question, find me on gitter or just create a github issue on the examples repo.

I am running all my production apps with dumber, so it’s considered solid enough for common usage.


#4

@EisenbergEffect I need to write the doc for dumber things before I can introduce it here :sweat_smile:

There are also some todos in dumber, but the doc is the biggest task to get it in shape.


#5

There’s no rush @huochunpeng. I just wanted to let you know the invitation was open and I think the community would really enjoy the post whenever you get around to it.


#6

I ended up creating a solution that is working quite well using webpack dll plugins with named modules. All shared dependencies go into one or more dll plugins, managed via a monorepo. With the lessons learned, I’d like to make an open source example, maybe a framework - but preferably with aurelia vnext. There’s a lot of moving parts to do get it working. @Kennethtruyers, it is very similar to your idea - portal application with cross-cutting concerns, but with a custom solution instead of singla-spa to register/bootstrap the microservice frontends.

@EisenbergEffect - I think a solution specific to Aurelia would really set it apart from other SPAs and gain some momentum for this framework. I’m somewhat new to web development, would you be willing to review my current solution before I start working on the open source one?


#7

oh I missed @huochunpeng’s post about dumberjs, I’ll take a look at that


#8

@jbockle Definitely let us know what you think after looking at @huochunpeng’s solution. I think there’s some interesting opportunity here. If we can look across what you’ve done and what @huochunpeng has done we could start working on something for the broader community. That can start with an architecture recommendation which we can talk about in our documentation and maybe the blog. But, from there, we could look into adding things like CLI support for generating a solution like this and an accompanying plugin, if relevant. Doing this as part of the vNext effort would be cool.


#9

Still looking into it, I’m really interested in the simplicity of @huochunpeng’s solution-also putting me down a rabbit whole of researching/second guessing the webpack module id collision avoidance I’ve done with my own solution. I see dumberjs has spaces and looking into how I can do something similar with webpack (maybe DllPlugin scoped modules).

@EisenbergEffect - I had a separate but related issue though, I foresee scoped CSS becoming an issue in my own micro frontends. I saw there was a PR in templating resources (#344) to address it that was abandoned because it doesn’t properly unload CSS by component instances but rather per instance. I’m going to see if I can address that first, as many small teams can really butcher the style globally if they don’t remember to use <require as="scoped"/> or better specificity. I haven’t looked if vNext handles css resources differently yet.


#10

I believe vNext component css can be naturally scoped because it builds on top of native shadow DOM.


#11

I think we should mention is that scoped CSS has always been supported via native shadowDOM, and you can use it today, via decorator @useShadowDOM:

import { useShadowDOM } from 'aurelia-framework';

@useShadowDOM({ mode: 'open' }) /* or 'closed' */
export class MyElement {

}

#12

@jbockle I just want to have some idea about your solution in webpack.

To my limited understanding of webpack, I don’t think it can support arbitrary app extension (new modules) at runtime. Does your solution require pre-built of all possible app extensions at bundling time? I mean I guess all app extensions are pre-built together with the app, but loaded on demand at runtime.


#13

@huochunpeng, correct, I have to load the scripts at runtime manually instead of using webpack, also, because the modules aren’t separated in their own context I have to take extra measures to ensure module ids are unique

basically I have the following setup:

  • monorepo
    • core-dev - contains shared dev dependencies: i.e. webpack loaders
    • core
      • contains base webpack configuration used by host/microfrontends so that plugins/loaders are the same across host/microfrontends/dllplugins
      • contains all shared dependencies of host/microfrontends in webpack dllplugins: i.e. manifest/aurelia/bootstrap/etc
    • shell: contains app navbar/layout and some general routes like 404/home page
    • manifest
      • contains definition of microfrontends, the baseRoute is a modified strict RouteConfig interface - {baseRoute:BaseRouteConfig,bundles:[path]}
      • a bundle cache and loader, adds one or more <script src="bundle"> from manifest.bundles
  • host repo
    • main starts shell/app
    • loads core’s dllplugins
    • loads microfrontend manifests from a database, adds manifest.baseRoute to approuter and appends an async navigationStrategyto use bundle loader on route load and returns the baseRoute
  • microfrontend repo:
    • main starts shell/app, allows local debug to have some look and feel/behavior as prod
    • a manifest is defined internally that uses PLATFORM.moduleName(‘projects/frontend-name/app’,‘frontend-name’) on baseRoute, this is transpiled and ran during CICD to get manifest and distributed to the host’s database a chunk is created for the frontend code and that is what is added to the manifest’s bundles
    • a microfrontend startup script is ran to load the manifest to add the baseRoute to approuter