webpack4 module-federation
Tags: javascript, webpack, webdev
Published: 2022-10-23 10:55:17

[module-federation](https://module-federation.github.io/blog/get-started) It is a new feature of webpack5 update, and it is easier to share front-end code, This article will introduce [module-federation/webpack-4](https://www.npmjs.com/package/@module-federation/webpack-4) ealization principle and its difference from webpack5

## module-federation/webpack-4 ealization principle

Briefly explain the implementation principle, how do webpack4 and webpack5 achieve interoperability? There are three key points

* [usemf](https://www.npmjs.com/package/usemf)（Use the sdk output by webpack5 build to simulate a webpack5 environment to load module-federation in a non-webpack5 environment）
* Follow the loading process of module-federation（1. init all remote container 2. merge shareScopes 3. Restore the sharing rules of webpack5-share）; output module-federation-container
    ``` js
    // container
    { 
        async init(shareScope){}, 
        get(name){ return async factory() } 
    }
    
    // shareScopes example
    {
      [default]: {
          [react]: {
              [18.0.2]: {
                  get() {
                    return async function factory() {
                        return module
                    }
                  },
                  ...other
              },
              [17.0.2]: {
                  get() {
                    return async function factory() {
                        return module
                    }
                  },
                  ...other
              }
          }
      }
    }

    ```
* Finally, a capability that webpack4 lacks, enabling jsonp-chunk to support waiting for dependencies (remote modules) to load

Implement the above process through plug-ins
[Enlarge picture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8o55iap04p3htc5wviz.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8o55iap04p3htc5wviz.png)
1. Add a new entry to implement the loading process of module-federation and output the container
2. Intercept the module loading of remotes, no longer load local modules directly, but use remote modules
3. Intercept shared module loading, no longer load local modules directly, but use remote modules
4. Shared requests are intercepted, but the shared bundle still needs to be output, and the function merge shareScopes will be loaded

It introduces the two red parts in the figure, how to change the webpack4 loading process to support loading remote modules
* Intercept imports, reserve dependency tags
    1. Set alias, transfer remotes to a non-existent url (no existence can be intercepted in the second step)
    2. Forward remotes to specific loader in "compiler.resolverFactory.plugin("resolver normal") --> resolver.hooks.resolve.tapAsync" hook
    3. Leave a string in the loader to mark that the current module depends on the remote module, get and export the value of the remote module
    1. The "compilation.mainTemplate.hooks.jsonpScriptchunk" hook makes the jsonp chunk wait for the remote module to load before executing

## Source code analysis
[https://github.com/module-federation/webpack-4](https://github.com/module-federation/webpack-4)

``` js
// module-federation/webpack-4/lib/plugin.js
apply(compiler) {
    // 1. Generate unique jsonpFunction global variables to prevent conflicts
    compiler.options.output.jsonpFunction = `mfrename_webpackJsonp__${this.options.name}`
    // 2. Generate 4 dummy modules for spare
    this.genVirtualModule(compiler)
    // 3. Initialize the remote module mapping relationship in entry chunks
    // 4. Load all container initialization dependency collections (shareScopes) in entry chunks
    this.watchEntryRecord(compiler)
    this.addLoader(compiler)
    // 5. Generate mf entry file (usually remoteEntry.js)
    this.addEntry(compiler)
    this.genRemoteEntry(compiler)
    // 6. Intercept webpack compilation of remotes and shared modules
    this.convertRemotes(compiler)
    this.interceptImport(compiler)
    // 7. Make webpack jsonp chunk wait for remote dependencies to load
    this.patchJsonpChunk(compiler)
}
```

### 1. Generate unique jsonpFunction global variables to prevent conflicts
``` js
compiler.options.output.jsonpFunction = `mfrename_webpackJsonp__${this.options.name}`
```

### 2. Generate 4 dummy modules for spare
Just register these 4 file code modules as webpack virtual modules, which can be introduced and used by subsequent processes

### 3. Initialize the remote module mapping relationship in entry chunks
### 4. Load all container initialization dependency collections 
Initialize all containers (other mf modules), and export the loading process as a promise to mark the completion of the initialization phase (all jsonp chunks need to wait for the initialization phase to complete)
> module-federation/webpack-4/lib/virtualModule/exposes.js

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yjr7bs2f8plgwr3h9po.png)

### 5. Generate mf entry file (usually remoteEntry.js)
``` js
// 1. Add mf entry using singleEntry
new SingleEntryPlugin(compiler.options.context, virtualExposesPath, "remoteEntry").apply(compiler)

// 2. Copy the last file generated by the remoteEntry entry and rename it
entryChunks.forEach(chunk => {
    this.eachJsFiles(chunk, (file) => {
      if (file.indexOf("$_mfplugin_remoteEntry.js") > -1) {
        compilation.assets[file.replace("$_mfplugin_remoteEntry.js", this.options.filename)] = compilation.assets[file]
        // delete compilation.assets[file]
      }
    })
})
```
2. output container api（module-federation/webpack-4/lib/virtualModule/exposes.js）
``` js
`
  /* eslint-disable */
  ...
  const {setInitShared} = require("${virtualSetSharedPath}")
  
  // All exposed modules are preset here with dynamic-import
  const exposes = {
    [moduleName]: async () {}
  }
  
  // 1. Register the container globally in a global-like manner
  module.exports = window["${options.name}"] = {
    async get(moduleName) {
      // 2. Use code splitting to expose exported modules
      const module = await exposes[moduleName]()
      return function() {
        return module
      }
    },
    async init(shared) {
      // 4. Merge shares and wait for the init phase to complete
      setInitShared(shared)
      await window["__mfplugin__${options.name}"].initSharedPromise
      return 1
    }
  }
  
  `
```
### 6. Intercept webpack compilation of remotes and shared modules
1. Set aliases for remotes and shared modules, identify special paths, and forward them to a non-existing file path（Only non-existing file paths can be intercepted by resolver hooks and forwarded）（module-federation/webpack-4/lib/virtualModule/plugin.js）
``` js
const { remotes, shared } = this.options
    Object.keys(remotes).forEach(key => {
      compiler.options.resolve.alias[key] = `wpmjs/$/${key}`
      compiler.options.resolve.alias[`${key}$`] = `wpmjs/$/${key}`
    })
    Object.keys(shared).forEach(key => {
      compiler.options.resolve.alias[key] = `wpmjs/$/mfshare:${key}`
      compiler.options.resolve.alias[`${key}$`] = `wpmjs/$/mfshare:${key}`
    })
```
2. Intercept remotes, shared aliases, and forward to import-wpm-loader.js to generate code for requesting remote resources（module-federation/webpack-4/lib/plugin.js）
``` js
compiler.resolverFactory.plugin('resolver normal', resolver => {
  resolver.hooks.resolve.tapAsync(pluginName, (request, resolveContext, cb) => {
      if (is an alias from remotes, shared) {
          // Forward to import-wpm-loader with pkgName parameter
          cb(null, {
            path: emptyJs,
            request: "",
            query: `?${query.replace('?', "&")}&wpm&type=wpmPkg&mfName=${this.options.name}&pkgName=${encodeURIComponent(pkgName + query)}`,
          })
      } else {
          // request native module
          cb()
      }
  });
});
```
3. Generate code to request remote resources（module-federation/webpack-4/lib/import-wpm-loader.js）
``` js
module.exports = function() {
    `
    /* eslint-disable */
    if (window.__wpm__importWpmLoader__garbage) {
      // 1. Leave a code mark to identify the dependent remote module, which is used to make the chunk wait for the remote dependency to load
      window.__wpm__importWpmLoader__garbage = "__wpm__importWpmLoader__wpmPackagesTag${pkgName}__wpm__importWpmLoader__wpmPackagesTag";
    }
    // 2. When entering the code of this module, the remote module has been loaded, you can use get to get the synchronization value of the module, and return
    module.exports = window["__mfplugin__${mfName}"].get("${decodeURIComponent(pkgName)}")
    `
}
```

### 7. Make webpack jsonp chunk wait for remote dependencies to load
1. Use regular matching to the remote module that the jsonp chunk depends on, so that the chunk waits for the dependency to load
2. Make webpack jsonp load function support jsonp waiting to load dependencies（module-federation/webpack-4/lib/plugin.js）

## Differences with webpack5
[module-federation/webpack-4](https://www.npmjs.com/package/@module-federation/webpack-4)The plugin has implemented the main capabilities of module-federation, and can be found in [webpack4 and webpack5 refer to each other](https://stackblitz.com/github/wpmjs/wpmjs/tree/main/examples/module-federation/webpack-4) , The following describes which parameters are not supported by the plugin

## unsupported parameter

### options.library
The priority of this parameter is not very high, the implementation in webpack4 is more complicated, and there are still problems in using it in webpack5, see for details "https://github.com/webpack/webpack/issues/16236" , Therefore, the implementation in webpack4 is similar to setting library.type = "global"

### options.remotes.xxx.shareScope
The same mf container can only be initialized with one shareScope. If inconsistent webpack is set by using shareScope multiple times, it will report an error, and the shareScope can be set too much, which is confusing. Even in pure webpack5, the performance is unpredictable. It is recommended to use options. shared.xxx.shareScope, options.shareScope alternative

### module-federation ecological package
The webpack-4 plugin has not yet integrated the ability of webpack-5 related packages（[ssr、typescript、hmr、dashboard...](https://github.com/module-federation)）, However, 4 and 5 interoperability has been achieved, which can help you to use webpack5 to implement new projects without refactoring existing projects.

## Supported parameters
* options.remotes
* options.name
* options.shareScope
* options.shared
* options.exposes