Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serving styles and assets with Webpack 5 module federation

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?

like image 450
John White Avatar asked May 21 '21 08:05

John White


People also ask

What is webpack 5 module federation?

Webpack 5 offers the Module Federation Plugin that lets you create multiple separate builds without dependencies between each other so they can be developed and deployed individually.

What are assets in webpack?

Asset Modules is a type of module that allows one to use asset files (fonts, icons, etc) without configuring additional loaders. Prior to webpack 5 it was common to use: raw-loader to import a file as a string.

What kind of modules can webpack support?

Webpack supports the following module types natively: ECMAScript modules. CommonJS modules. AMD modules.

What is Webpack module Federation?

What is Webpack Module Federation? Module Federation is a new concept (architecture) of JavaScript application development, which then became a new feature in Webpack as a plugin. This approach is very much related to the idea of ​​Micro Frontends:

What should a good Webpack architecture look like?

It should be possible to expose and use any module type that webpack supports. Chunk loading should load everything needed in parallel (web: single round-trip to server). Overriding modules is a one-directional operation. Sibling containers cannot override each other's modules. Concept should be environment-independent.

How to choose between inline and resource file types in Webpack?

Now webpack will automatically choose between resource and inline by following a default condition: a file with size less than 8kb will be treated as a inline module type and resource module type otherwise. You can change this condition by setting a Rule.parser.dataUrlCondition.maxSize option on the module rule level of your webpack configuration:

How to use shared modules in a chunk in module Federation?

You can set the dependency as eager inside the advanced API of Module Federation, which doesn’t put the modules in an async chunk, but provides them synchronously. This allows us to use these shared modules in the initial chunk.


2 Answers

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.

like image 191
Andrei Cristea Avatar answered Oct 31 '22 18:10

Andrei Cristea


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.

like image 45
John White Avatar answered Oct 31 '22 16:10

John White