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
@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
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 .
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.
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.
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?
@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.
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.
@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.
@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
Although this topic is quite old I’d like to add some info to this thread for future readers and published this package that you should be able to use when you plan to expose your aurelia-framework app in a Single SPA setup.
In my search for a way to integrate aurelia (v1) with single-spa I landed at this thread quite some times and together with these teardown suggestions helped me in “proper” unmounting of the aurelia apps.
Is there perhaps an example application of how single-spa-aurelia-framework is correctly included in single-spa? The documentation is unfortunately incomplete and does not contain an example.
Nope, there is no such thing afaik. The readme should sufficiently guide you and the code in there is only meant as a reference on where to integrate the adapter. Single SPA docs should handle the rest.
If you can point me to your code that needs an implementation I might be able to throw in a few comments.
In the meantime I managed to include Aurelia applications in the Single Spa Framework. However, the dialogs from aurelia-dialog (v1.1.0 & v2.0.0) no longer work on production. The following error occurs: ‘Uncaught (in promise) Error: Cannot determine default view strategy for object. function e(e){this.dialogController=e}’
Is there maybe a spontaneous idea what could be the reason for that?