Home > Software engineering >  Auto import modules with Module Federation
Auto import modules with Module Federation

Time:01-25

I have a simple application written in vanilla javascript and using Module Federation to wrap things up. So far, I've separated the javascript and the styling into two separate "apps":

├ src/
│ ├ lib/
│ │ └ myApp.js
│ ├ scss/
│ │ └ styles.scss
│ ├ index.js
│ └ styles.js
└ webpack.config.js

The index.js imports myApp.js that has all the logic and styles.js simply imports a SASS-file with all necessary styling like this:

import './scss/signing-widget.scss';

The ModuleFederationPlugin in webpack.config.js is setup as follows:

module.exports = {
  entry: {
    index: ['./src/index.js'],
    styles: ['./src/styles.js'],
  },
  ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'myApp',
      filename: 'remoteEntry.js',
      exposes: [
        './myApp': './src/index.js'
        './myAppStyles': './src/styles.js'
      ],
      shared: [
        require('./package.json').dependencies
      ],
    })
  ],
  ...

And to implement and use myApp you need to do the following:

<html>
  <head>
    ...
    <script defer src="http://path-to-my-app/index.js"></script>
    <script defer src="http://path-to-my-app/styles.css"></script>
    <script defer src="http://path-to-my-app/remoteEntry.js"></script>
  </head>
  <body>
      <my-app></my-app>
  </body>
</html>

But, I only want to implement the app by only importing the remoteEntry.js like this:

<html>
  <head>
    ...
    <script defer src="http://path-to-my-app/remoteEntry.js"></script>
  </head>
  <body>
      <my-app></my-app>
  </body>
</html>

But I can't figure out how to do it and I've done a lot of research but I haven't found any example nor documentation on how to achieve this with ModuleFederationPlugin. Can someone help me on this matter?

Thanks in advance, Clydefrog

CodePudding user response:

I ended up making my own "mounting" script. I'll share my solution to anyone who find this interesting or experiencing the same problem.

├ src/
│ ├ lib/
│ │ └ myApp.js
│ ├ scss/
│ │ └ styles.scss
│ ├ index.js
│ ├ mount.js <------ NEW MOUNT
│ └ styles.js
└ webpack.config.js

Instead of manipulating or change the rmeoteEntry.js to auto import modules, I created a simple javascript file (mount.js) that detects its own script-tag. Then it extracts the base URL and then iterates through each file that needs to get imported with the same base URL:

mount.js

(() => {
  const parseUrlString = (url) => !url || url.length <= 0 ? '' : new URL(url).origin;

  var startsWith = '^',
    contains = '*',
    endsWith = '$',
    scriptElement = document.querySelector(`script[src${endsWith}="widgetMount.js"]`),
    url = parseUrlString(scriptElement?.getAttribute('src')),
    head = document.getElementsByTagName('head')[0];

  [
    'styles.css',
    'index.js',
    'remoteEntry.js',
  ].forEach((filename) => {
    var newElement;

    switch (filename.split('.')[1]) {
      case 'css':
        newElement = document.createElement('link');
        newElement.setAttribute('rel', 'stylesheet');
        newElement.href = `${url}/${filename}`;
        break;
      case 'js':
        newElement = document.createElement('script');
        newElement.setAttribute('defer', '');
        // newElement.setAttribute('async', '');
        newElement.type = 'module';
        newElement.src = `${url}/${filename}`;
        break;
    }
    
    head.appendChild(newElement);
  });
})();

Adding it to webpack.config.js in my remote application:

module.exports = {
  entry: {
    ...,
    widgetMount: ['./src/mount.js'], // widget mount
    ...
  },
  ...
  plugins: [
    new ModuleFederationPlugin({
      ...,
      exposes: [
        ...,
        './myMount': './src/mount.js'
        ...
      ],
      ...,
    })
  ],
  ...
}

Last but not least, adding the mount into my hosting application:

<html>
  <head>
    ...
    <script defer src="http://path-to-my-app/widgetMount.js"></script>
  </head>
  <body>
      <my-app></my-app>
  </body>
</html>

And Voilà:

<html>
  <head>
    ...
    <script defer src="http://path-to-my-app/widgetMount.js"></script>
    <link rel="stylesheet" href="http://path-to-my-app/styles.css">
    <script defer src="http://path-to-my-app/index.js" type="module"></script>
    <script defer src="http://path-to-my-app/remoteEntry.js" type="module"></script>
  </head>
  <body>
      <my-app></my-app>
  </body>
</html>
  • Related