I'm trying to design a new application based on Micro Frontends with Angular 9 and WebComponents. The main goal that I want to achieve with WebComponents is tha ability to have different teams working independently, deploying their updates whenever they want and having the main wrapper just download the corresponding self contained bundle and inject it in the wrapping page.
As for now I do not need to handle different frameworks, but I just need to have very low coupling between each of them, both on the dependencies (each WebComponent should bring its own dependencies by itself, with the allowed exception of Polyfills and Runtime for the moment) and on the deployments (just deploy/expose the WebComponent bundle and get it injected in the wrapper through a GET API). The architecture that I have in mind is as follows:
The examples of WebComponents that I found on the web are all very basic; I mean they just use standard html elements (buttons, paragraphs, ...), hence no dependency to incorporate in the output bundle. In my case, DashboardOne-UI relies on Prime-NG and Prime-Icons, and I would avoid having suche dependencies installed also on the UI.
In order to build the DashboardOne-UI and serve the output files, I use NGX-Build-Plus and HTTP-Server by running the following commands:
"scripts": {
"start": "http-server dist/DashboardOne-UI/ --port 8084",
"build": "ng build --prod --single-bundle"
}
After the build, the dist folder contains:
and this is the content of index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>OrganizationUI</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<organization-dashboard></organization-dashboard>
<script src="polyfills.js" defer></script>
<script src="scripts.js" defer></script>
<script src="polyfill-webcomp-es5.js" defer></script>
<script src="polyfill-webcomp.js" defer></script>
<script src="main.js" defer></script>
</body>
</html>
By running the start script I am able to get the WebComponent working as expected in the browser, but when I inject only the main.js script in the UI application it fails to get all the fonts and styles:
The only way I found to avoid having such dependencies on the wrapping UI, is to make the DashboardOne WebComponent'css point to the styles on NPM instead of local node_modules, by using:
@import url("https://unpkg.com/primeicons@2.0.0/primeicons.css");
@import url("https://unpkg.com/primeng@9.0.0/resources/primeng.min.css");
@import url("https://unpkg.com/primeng@9.0.0/resources/themes/nova-light/theme.css");
@import url("https://unpkg.com/bootstrap@4.4.1/dist/css/bootstrap.min.css");
instead of:
@import url("~primeicons/primeicons.css");
@import url("~primeng/resources/primeng.min.css");
@import url("~primeng/resources/themes/nova-light/theme.css");
@import url("~bootstrap/dist/css/bootstrap.min.css");
The problem with this approach is that I need to handle the dependencies in two places, css and package.json. Is there a better way to build self contained WebComponents in Angular 9, such that I just need to import a single JS file in the wrapping UI application, without caring about WebComponents' dependencies?
A possible approach towards a better solution, I just found, is to provide the
ng build
with a path for the deploy URL (more info here):
"scripts": {
"start": "http-server dist/DashboardOne-UI/ --port 8084",
"build": "ng build --single-bundle --deploy-url http://localhost:8084/"
}
By this way, the DashboardOne-UI web-component element, once injected in the UI wrapper, will point to its own server where all the needed resources can be found.