2020/10/14

background

The development of front-end applications and microservices makes the concept of modularity more and more important. It is also inevitable that many different projects will have similar or even identical features. So it’s really important to share code across applications, and we used to deal with that in the past,

1, CTRL + V large method is good, no mental reuse directly. But this will lead to low reusability of the code in the project, code redundancy and other problems.

2. With the advent of microservices, many businesses generally manage common packages in the form of NPM distribution. NPM is better suited for packages with small, fully tool-like coupling to business logic. However, for modules with heavy business logic and frequent updates, NPM package will have the problem of iterating and updating versions. In addition, the NPM package has a lot of work to split the business code, and the maintenance cost is relatively large. The code has certain quality requirements, otherwise it will lead to the problem of too large modules.

Module Federation solves the problem of code sharing across applications

What is Module Federation

Module federation is a federation of modules. It is a federation of modules.

Module federation enables JavaScript applications to load code dynamically from another JavaScript application — while sharing dependencies. By refining function modules, component reuse, sharing third-party libraries, and loading NPM packages online, it can better serve multi-page applications and micro-front-end development modes.

How do I use the ModuleFederationPlugin

ModuleFederation as a whole is concatenated through the ModuleFederationPlugin.

Configuration of the sample

 new ModuleFederationPlugin({
    filename: "remoteEntry.js",
    name: "app1",
    library: { type: "var", name: "app1" },
    exposes: {
        './Header': "./src/components/Header.vue",
    },
    remotes: {
        app1: "app2@http://localhost:3000/remoteEntry.js",
    },
    shared: ['vue']
})
Copy the code

Configuration properties:

  • Name, must, unique ID, as the output module name, use the name/{name}/name/{expose} mode;
  • Library, must, where name is the name used as umD;
  • Remotes (optional) is the most critical configuration item for the reference party. It is used to declare the name of the Remote resource package and module to be referenced.
  • Exposes, optional, which attributes are exported and consumed while being Remote.
  • Shared, optional
    1. If this property is configured. Webpack checks whether there is a corresponding package for the local application before loading. If not, it loads the dependent package for the remote application.
    2. In the case of App1, since it is a remote application, configured with [“vue”] and consumed by App1, WebPack looks for the package in App1 and uses the package in App2 if it doesn’t exist. App2 (which will be presented in the form of a case later) also declares these two parameters. Because App2 is a local application, it will be directly used in the project that references remote resources with the dependency of App2. Remote resource entry file should be introduced first, which can be loaded asynchronously, or script tag can be introduced.

After the project is built

  • Main.js, the main application file;
  • Remoteentry. js, as the file referenced when remote;
  • Src_components_header_vue.js asynchronously loaded file, main.js or remoteentry.js may be loaded file;

Vue project case

Complete case github

Create two projects

We simply created two projects, APP1 and App2, both of which were built with WebPack 5 and vUE architecture

Project directory

Creating a common component

Now we create a Header component in the app1 SRC/Components directory

< the template > < div > < h1 > this is a head navigation {{name}} < / h1 > < h2 > success ha ha ha ha ha!!!!!!!!!!! </h2> </div> </template> <script> export default { props: { name: { type: String, default: '' } } } </script>Copy the code

App1 imports components

App.vue of App1 introduces this component

<template> <div id="app"> <Header name="app1"/> </div> </template> <script> import Header from './components/Header.vue'  export default { components: { Header } }Copy the code

Run the page and we can see

How do we use the Header component directly in our app2 application next?

App1 Exports components

First we now export this component in APP1

module.exports = { ... Filename: "remoteentry. js", // unique ID, which is used to mark the current service name: "App1 ", library: {type: "var", name: "app1"}, // Expose: {'./Header': "./src/components/Header.vue", } }) ] ... }Copy the code

Restart app1 and we access it directlyhttp://localhost:3000/remoteEntry.js

The access and configuration are successful

App2 Imports the APP1 module

module.exports = {
...
    plugins: [
        ...
        new ModuleFederationPlugin({
          name: "app2",
          remotes: {
            app1: "app1@http://localhost:3000/remoteEntry.js",
          }
        })
      ]
      ...
}
Copy the code

App2 imports the current component

App2 app.vue imports the Header component of APP1

<template>
  <div id="app">
      <Header name="app2"/>
  </div>
</template>
<script>

export default {
  components: {
    Header: () => import('app1/Header')
  }
}
</script>
Copy the code

The introduction of remote components must pay attention to the asynchronism of the code. You can load the components directly using the above asynchronous component loading mode, or load the components dynamically using async/await mode.

Now let’s take a look at the page effect of App2

Is configuration very simple, is true sweet

The implementation process

App1 and App2 have their own modules, so the key is how the modules of the two projects are synchronized, or how components from App1 are injected into App2. Let’s look at how Module Federation is implemented.

Let’s start by looking at how files are requested when app2 accesses them.

Let’s take a look at how main.js in App2 introduces app1.

"use strict"; var error = new Error(); module.exports = new Promise((resolve, reject) => { if(typeof app1 ! == "undefined") return resolve(); __webpack_require__.l("http://localhost:3000/remoteEntry.js", (event) => { if(typeof app1 ! == "undefined") return resolve(); var errorType = event && (event.type === 'load' ? 'missing' : event.type); var realSrc = event && event.target && event.target.src; error.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')'; error.name = 'ScriptExternalLoadError'; error.type = errorType; error.request = realSrc; reject(error); }, "app1"); }).then(() => app1);Copy the code

Export an asynchronous function to request the entry file of our configured app1 and get the return parameter of remoteentry. js

Next take a look at app1/ remoteentry.js

var app1; app1 = /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "webpack/container/entry/app1": /*! * * * * * * * * * * * * * * * * * * * * * * *! * \! *** container entry ***! A \ * * * * * * * * * * * * * * * * * * * * * * * / / *! unknown exports (runtime-defined) */ /*! runtime requirements: __webpack_require__.d, __webpack_require__.o, __webpack_exports__, __webpack_require__.e, __webpack_require__, __webpack_require__.* */ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var moduleMap = { "./Header": () => { return __webpack_require__.e("src_components_Header_vue").then(() => () => (__webpack_require__(/*! ./src/components/Header.vue */ "./src/components/Header.vue"))); }}; var get = (module, getScope) => { __webpack_require__.R = getScope; getScope = ( __webpack_require__.o(moduleMap, module) ? moduleMap[module]() : Promise.resolve().then(() => { throw new Error('Module "' + module + '" does not exist in container.'); })); __webpack_require__.R = undefined; return getScope; }; var init = (shareScope, initScope) => { if (! __webpack_require__.S) return; var oldScope = __webpack_require__.S["default"]; var name = "default" if(oldScope && oldScope ! == shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope"); __webpack_require__.S[name] = shareScope; return __webpack_require__.I(name, initScope); }; // This exports getters to disallow modifications __webpack_require__.d(exports, { get: () => get, init: () => init }); .Copy the code

This file defines the global variable of app1, which exports two methods, git and init. When we request app1/Header, we call the get method asynchronously and request the file named src_components_Header_vue.

Src_components_header_vue. js This is a WebPack chunk file of the Header component that can be run in a browser

. "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "render": () => /* binding */ render, /* harmony export */ "staticRenderFns": () => /* binding */ staticRenderFns /* harmony export */ }); var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c("div", [_c (" h1 ", [_vm _v (" this is a header navigation + "_vm. _s (_vm. Name))]), _vm. _v (" "), _c (" h2," [_vm. _v (" success ha ha ha ha ha!!!!!!!!! ")]]])}...Copy the code

From the above, we can simply conclude that cross-project code sharing requires that the project code to be shared should be separately packaged according to the export module of the configuration file to generate the corresponding modules, and then a global variable is used to establish the connection between two different projects.

Scope of application

It is applicable to create a special component application service to manage all components and applications, and the rest of the business layer only needs to load corresponding components and functional modules according to its own business needs.

Unified module management, high code quality, fast construction. Especially suitable for matrix APP, or visual page building and other scenes. (The stacks and versions of local and remote applications must be compatible.)

Refer to the article

Webpack 5 official documentation

Explore the new webpack5 feature module-federation

Webpack 5 Module Federation: A JavaScript architecture changer

The original address