zoukankan      html  css  js  c++  java
  • Module Federation原理剖析

    【转自团队掘金原文: https://juejin.im/post/6895324456668495880】

    为什么需要学习webpack5 module Federation原理呢?因为EMP微前端方案正是基于该革命性功能进行的,具有历史突破意义。通过本文,可以让你深入学习webpack5 module Federation原理,掌握EMP微前端方案的底层基石,更好使用和应用EMP微前端方案。

    最近webpack5正式发布,其中推出了一个非常令人激动的新功能,即今日的主角——Module Federation(以下简称为mf),下面将通过三个方面(what,how,where)来跟大家一起探索这个功能的奥秘。

    一. 是什么

    Module Federation中文直译为“模块联邦”,而在webpack官方文档中,其实并未给出其真正含义,但给出了使用该功能的motivation, 即动机,原文如下

    Multiple separate builds should form a single application. These separate builds should not have dependencies between each other, so they can be developed and deployed individually.
    
    This is often known as Micro-Frontends, but is not limited to that.

    翻译成中文即

    多个独立的构建可以形成一个应用程序。这些独立的构建不会相互依赖,因此可以单独开发和部署它们。
    这通常被称为微前端,但并不仅限于此。

    结合以上,不难看出,mf实际想要做的事,便是把多个无相互依赖、单独部署的应用合并为一个。通俗点讲,即mf提供了能在当前应用中远程加载其他服务器上应用的能力。对此,可以引出下面两个概念:

    • host:引用了其他应用的应用
    • remote:被其他应用所使用的应用

     

    鉴于mf的能力,我们可以完全实现一个去中心化的应用部署群:每个应用是单独部署在各自的服务器,每个应用都可以引用其他应用,也能被其他应用所引用,即每个应用可以充当host的角色,亦可以作为remote出现,无中心应用的概念。 

    二. 如何使用

    配置示例:

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
    
    module.exports = {
      // 其他webpack配置...
      plugins: [
        new ModuleFederationPlugin({
            name: 'empBase',
            library: { type: 'var', name: 'empBase' },
            filename: 'emp.js',
            remotes: {
              app_two: "app_two_remote",
              app_three: "app_three_remote"
            },
            exposes: {
              './Component1': 'src/components/Component1',
              './Component2': 'src/components/Component2',
            },
            shared: ["react", "react-dom","react-router-dom"]
          })
      ]
    }

    通过以上配置,我们对mf有了一个初步的认识,即如果要使用mf,需要配置好几个重要的属性:

    字段名类型含义
    name string 必传值,即输出的模块名,被远程引用时路径为${name}/${expose}
    library object 声明全局变量的方式,name为umd的name
    filename string 构建输出的文件名
    remotes object 远程引用的应用名及其别名的映射,使用时以key值作为name
    exposes object 被远程引用时可暴露的资源路径及其别名
    shared object 与其他应用之间可以共享的第三方依赖,使你的代码中不用重复加载同一份依赖

    三. 构建解析原理

    让我们看看构建后的代码:

    var moduleMap = {
        "./components/Comonpnent1": function() {
            return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_react_react"), __webpack_require__.e("src_components_Close_index_tsx")]).then(function() { return function() { return (__webpack_require__(16499)); }; });
        },
    };
    var get = function(module, getScope) {
        __webpack_require__.R = getScope;
        getScope = (
            __webpack_require__.o(moduleMap, module)
                ? moduleMap[module]()
                : Promise.resolve().then(function() {
                    throw new Error('Module "' + module + '" does not exist in container.');
                })
        );
        __webpack_require__.R = undefined;
        return getScope;
    };
    var init = function(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);
    }

    可以看到,代码中包括三个部分:

    • moduleMap:通过exposes生成的模块集合
    • get: host通过该函数,可以拿到remote中的组件
    • init:host通过该函数将依赖注入remote中

    再看moduleMap,返回对应组件前,先通过__webpack_require__.e加载了其对应的依赖,让我们看看__webpack_require__.e做了什么:

    __webpack_require__.f = {};
    // This file contains only the entry chunk.
    // The chunk loading function for additional chunks
    __webpack_require__.e = function(chunkId) {
        // 获取__webpack_require__.f中的依赖
      return Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {
        __webpack_require__.f[key](chunkId, promises);
         return promises;
     }, []));
    };
    __webpack_require__.f.consumes = function(chunkId, promises) {
    // 检查当前需要加载的chunk是否是在配置项中被声明为shared共享资源,如果在__webpack_require__.O上能找到对应资源,则直接使用,不再去请求资源
     if(__webpack_require__.o(chunkMapping, chunkId)) {
         chunkMapping[chunkId].forEach(function(id) {
             if(__webpack_require__.o(installedModules, id)) return promises.push(installedModules[id]);
             var onFactory = function(factory) {
                 installedModules[id] = 0;
                 __webpack_modules__[id] = function(module) {
                     delete __webpack_module_cache__[id];
                     module.exports = factory();
                 }
             };
             try {
                 var promise = moduleToHandlerMapping[id]();
                 if(promise.then) {
                     promises.push(installedModules[id] = promise.then(onFactory).catch(onError));
                 } else onFactory(promise);
             } catch(e) { onError(e); }
         });
     }
    }

    通读核心代码之后,可以得到如下总结:

    • 首先,mf会让webpack以filename作为文件名生成文件
    • 其次,文件中以var的形式暴露了一个名为name的全局变量,其中包含了exposes以及shared中配置的内容
    • 最后,作为host时,先通过remoteinit方法将自身shared写入remote中,再通过get获取remoteexpose的组件,而作为remote时,判断host中是否有可用的共享依赖,若有,则加载host的这部分依赖,若无,则加载自身依赖。

    四. 应用场景

    英雄也怕无用武之地,让我们看看mf的应用场景有哪些:

    • 微前端:通过shared以及exposes可以将多个应用引入同一应用中进行管理,由YY业务中台web前端组团队自主研发的EMP微前端方案就是基于mf的能力而实现的。
    • 资源复用,减少编译体积:可以将多个应用都用到的通用组件单独部署,通过mf的功能在runtime时引入到其他项目中,这样组件代码就不会编译到项目中,同时亦能满足多个项目同时使用的需求,一举两得。

    五. 最后

    目前仅有EMP微前端方案是基于Module Federation实现的一套具有成熟脚手架和完整生态的微前端方案,并且在欢聚时代公司内部应用了80%的大型项目,通过本文我们也可以认知到EMP微前端方案是具有前瞻性的、可扩展性的、基石可靠的。针对EMP微前端方案的学习,有完整的wiki学习目录供大家参考:

  • 相关阅读:
    js对象的所有方法
    js数组的所有方法
    Scss语法
    new一个对象的过程
    promises的深入学习
    jsonp的原理介绍及Promise封装
    Vue页面缓存和不缓存的方法
    JavaScript数据类型
    JS常用函数原理的实现
    @Autowired注解在抽象类中实效的原因分析
  • 原文地址:https://www.cnblogs.com/BoatGina/p/13983628.html
Copyright © 2011-2022 走看看