14

I've successfully implemented the relatively new webpack 5 module federation system in my Angular 11 app, so it can load modules remotely on-demand from another build.

One thing I've found nothing about is how to handle assets like stylesheets and images. For example, there's a menu element in the federated module that requires its own styles:

  • Putting them in the component's stylesheet bloats the chunks and the compiler complains about that, plus they're not loaded until the menu is shown
  • If the styles are instead on the federated module's global stylesheet, they don't get loaded at all, because I'm requesting a sub-module and not the main one (I presume)
  • The style is specific to the federated module, so it can't be put in the loader application

I suppose that the styles could be compiled and put in the federated module's build assets, but that'd break links when it's used with and without federation.

I'm still experimenting with this, but I thought it'd be good to ask. Anybody had this issue?

John White
  • 917
  • 1
  • 12
  • 26
  • did you manage to find a solution to this problem? – Bracher Jun 21 '21 at 13:45
  • @Bracher yes, kinda. Biggest issue is with assets, but for the styles I found a good enough solution, which involves loading it along with the module in the router. I'll post an update maybe in the future when it's thoroughly tested – John White Jun 22 '21 at 15:35
  • cool, thanks. I found the `asset-loader` in webpack 5 as one solution for images, but it has the drawback of not being able to set images in your scss files. You need to add the images via imports in your .ts files. if you have the type set as `asset/resource` in your webpack config it will point to the images of your MF app. Alternatively you could inline images to base64 with something like postcss-assets. – Bracher Jun 24 '21 at 06:29
  • @JohnWhite Hi, Can you post a sample snippet on how you are handelling the Assets and styles for remotes in webpack5 module federation using Angular11/12 ? – Shaik Nizamuddin Jul 18 '21 at 06:10
  • 1
    @ShaikNizamuddin assets seem to work because of some dark magic I can't understand. My CSS loading is a bit hacky and I don't think it's worth sharing, it'd be likely downvoted. Unfortunately I don't have much time to refine it right now – John White Jul 18 '21 at 12:43
  • 1
    @JohnWhite I have used the following copyPlugin to copy assets to shell app and load them. Its working fine for me. new CopyPlugin({ patterns: [ { from:'projects/mfe1/src/assets' , to:'assets' }, ], }), – Shaik Nizamuddin Jul 19 '21 at 13:04
  • @JohnWhite, a few months down the line now, have u managed to find a better solution for this? – Gerald Chifanzwa Sep 28 '21 at 14:47
  • @GeraldChifanzwa yes, please see my answer below. I'm still using this method and it seems robust for now. – John White Sep 30 '21 at 08:52
  • I am a same problem. Do you have a solution please. My assets of module not loaded – dna Jul 09 '22 at 22:52

3 Answers3

8

I think the most elegant way to achieve that would be to import your global styles.scss of the micro frontend into it's entry module component.scss and in your entry module component.ts set encapsulation: ViewEncapsulation.None in order to break style encapsulation for it, which will in turn lead for that styles to be applied globally.

Andrei Cristea
  • 141
  • 1
  • 4
  • This works. Excellent method, indeed it's super clean and simple. – John White Jan 04 '22 at 11:58
  • Still new to angular and I'm facing the same issue where I want to have one style sheet file to be used within all projects (shell or not) to avoid repeat writing the same styles in different files. Do you perhaps have a working example of what you proposed? Thanks – bonnu18 Jan 04 '22 at 15:58
  • 1
    Well, if you don't care if it's shell or not then you could just add regular custom styling in your shell app's angular.json file [documentation here](https://angular.io/guide/workspace-config), in that case your angular.json would look something like ` { ... "architect": { "build": { "options": { "styles": [ "src/styles.scss" ] } } }, ... } ` and you create a styles.scss file in your ./src dir. @bonnu18 – Andrei Cristea Jan 05 '22 at 19:48
  • Thanks for your help, it worked for me. Added reference to a CSS file from project A inside the styles section defined for Project B and C, and worked! Thanks for your help – bonnu18 Jan 10 '22 at 06:45
  • Working great for me. I have question how to use this approach with i18n files so the host app will use remote files? – s.alhaj Apr 12 '22 at 11:53
  • If you are mixing CSS libraries, this will not work. For example, I have a legacy app with bootstrap styles and a new app with primeNG styles. Setting view encapsulation to none on the shell component allows the bootstrap styles to bleed into other micro-frontends. To resolve this, I wrapped the shell.component.html in a div with a class. Then, I wrapped the entire styles.scss file in that class. Finally, I imported the styles.scss file into my shell.scss. This allows the whole application to have the styles it needs without bleeding into other apps. ViewEncapsulation should be none. – Laura Slocum Jan 05 '23 at 16:44
  • This seems to work nicely. You just have to watch out in the remote app to not load the global styling twice: in the component module component scss and in your app style.scss. Just if you care about the bundlesize of the remote app :) – spierala Jan 26 '23 at 11:35
1

Well, I'm gonna post what I came up with, it's not pretty, but it seems to work fine for CSS assets.

First of all, I separated them in the remote module's build: angular.json:

"styles": [
  "projects/xxx-admin/src/styles/styles.scss",
  "projects/xxx-admin/src/styles/admin.scss",
  {
    "input": "projects/xxx-admin/src/styles/admin.scss",
    "bundleName": "admin_module_styles",
    "inject": false
  }
],

This generates a CSS with a clear non-autogenerated name. Then we load the federated module from the app in the routes:

{
  path: "admin",
  component: AdminPanelComponent,
  canActivate: [XxxAdminGuard],
  loadChildren: () => {
    const baseUrl = getAdminFrontendBaseUrl();
    return loadAdminStyles().then(
      () => loadRemoteModule({
        remoteName: "xxx_admin",
        remoteEntry: `${baseUrl}/remoteEntry.js`,
        exposedModule: "AdminModule",
      }).then((m) => m.AdminModule));
  },
},

...

export function loadAdminStyles(): Promise<void> {
  return new Promise((resolve => {
    const baseUrl = getAdminFrontendBaseUrl();
    const el = document.getElementById("admin-module-styles");

    // Load one instance, do it like this to handle errors and retrying
    if (el) {
      el.remove();
    }
    const headEl = document.getElementsByTagName("head")[0];
    const styleLinkEl = document.createElement("link");
    styleLinkEl.rel = "stylesheet";
    styleLinkEl.id = "admin-module-styles";
    styleLinkEl.href = `${baseUrl}/admin_module_styles.css`;
    headEl.appendChild(styleLinkEl);
    resolve();
  }));
}

It's suboptimal, but I couldn't figure anything better.

John White
  • 917
  • 1
  • 12
  • 26
  • but this would work in monorepo approach. What about when microfrontends are in different workspace. – Vugar Abdullayev Jan 12 '22 at 12:07
  • 1
    It does work with microfrontends from different compilations and workspaces. Please also see the accepted answer, I think it's neater. – John White Jan 19 '22 at 13:21
  • Nice approach! I like that the impact on the remote-app code is low. Just let the angular json export a dedicated bundle of mfe CSS. You could create a dedicated mfe-styles.scss file with all necessary imports and give it to angular.json as input. The shell part could be improved by using a manifest file as described here: https://www.angulararchitects.io/en/aktuelles/dynamic-module-federation-with-angular In the manifest file you could add metadata about the URL to the CSS. – spierala Jan 26 '23 at 14:45
0

In microfront, transfer assets to assets/mfe_name. By the name of microfront, you can do proxying from the parent host.

HOST:

"/assets/*mfe_name*/*": {
           "target": "*mfe_host*",
           "secure": true,
           "changeOrigin": true
    },

Start: ng serve --proxyConfig=proxy

So you can get files both from localhost and on the prod to access from a remote host

  • OK, I found the downside... The proxy config is meant for local development. In production you have to find another solution. – spierala Jan 25 '23 at 13:07