module-federation 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 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(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
// 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
- Add a new entry to implement the loading process of module-federation and output the container
- Intercept the module loading of remotes, no longer load local modules directly, but use remote modules
- Intercept shared module loading, no longer load local modules directly, but use remote modules
- 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
- Set alias, transfer remotes to a non-existent url (no existence can be intercepted in the second step)
- Forward remotes to specific loader in "compiler.resolverFactory.plugin("resolver normal") --> resolver.hooks.resolve.tapAsync" hook
- 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
- 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
// 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
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
5. Generate mf entry file (usually remoteEntry.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]
}
})
})
- output container api(module-federation/webpack-4/lib/virtualModule/exposes.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
- 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)
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}`
})
- 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)
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()
}
});
});
- Generate code to request remote resources(module-federation/webpack-4/lib/import-wpm-loader.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
- Use regular matching to the remote module that the jsonp chunk depends on, so that the chunk waits for the dependency to load
- Make webpack jsonp load function support jsonp waiting to load dependencies(module-federation/webpack-4/lib/plugin.js)
Differences with webpack5
module-federation/webpack-4The plugin has implemented the main capabilities of module-federation, and can be found in webpack4 and webpack5 refer to each other , 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...), 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
Top comments (0)