最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    正文概述 掘金(望道同学)   2021-08-02   753

    现代 WEB 开发中,越来越多的 IT 技术厂使用微前端。所谓微前端,简单点就是多个单独构建子应用可以形成一个统一的应用程序。 这些单独的构建之间不存在依赖关系,因此可以单独开发和部署它们。

    微前端简介

    前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    微前端现有的落地方案可以分为三类:

    • 自组织模式
    • 基座模式
    • 模块加载模式

    与基座模式相比,模块加载模式没有中心容器,这就意味着,我们可以将任意一个微应用当作项目入口,整个项目的微应用与微应用之间相互串联,打破项目的固定加载模式,彻底释放项目的灵活机动性,这样的模式,也被称为去中心化模式

    其实这个方案在微前端的架构理念中早已提及,但直到 2020 年 10 月 Webpack 5 正式发布之后才被真正落地应用。

    因为 Webpack 5 带来了一个全新特性:Module Federation(模块联邦),这是我们使用模块加载模式实现微前端架构的核心特性。

    今天,我们来看看 Module Federation 的基本使用,然后再通过解读源码的方式,带你深入了解 Webpack 5 实现微前端的工作原理,以及实战中常见的应用场景,详细介绍如何使用模块联邦落地微前端架构。

    Module Federation 是什么

    在官方文档中,关于 Module Federation 的动机中,有这样一段介绍:

    注:如果你感兴趣,可以点击这里查看英文原文,以获取更多信息。

    Module Federation 中文直译为“模块联邦”,为了方便我们这里简称为 MF。如果你去 Webpack 官方文档中查看,最多可以从前面的“动机”中看到模糊的解释,而对于“模块联邦”准确的定义,其实并没有给出。

    但是,根据 “动机”的描述,不难看出,MF 实际想要做的事,便是把多个无相互依赖、单独部署的应用合并为一个。

    通俗点讲,MF 提供了能在当前应用中加载其他应用的能力。

    所以,在 MF 中,如果一个模块想要载入其他模块:

    • 就需要一个引入的动作;
    • 同样如果想让其他模块使用,就需要一个导出的动作。

    对此,可以引出下面两个概念

    • expose:导出应用,被其他应用导入

    • remote:引入其他应用

    一个模块既可以导出给其他模块使用,又可以导入一个其他模块,这与“基座模式”完全不同。

    要知道,无论是 single-spa 还是 qiankun,加载不同模块,都需要有一个容器中心来承载;而在 MF 中,没有且也不需要容器中心。

    总之

    鉴于 MF 的能力,我们完全可以实现一个去中心化的应用部署群:

    多个微应用单独部署在各自的服务器中,而每个微应用都可以引用其他应用,也能被其他应用导入使用,即每个应用都可以导出又导入,也就没有了容器中心的概念

    Module Federation 如何使用

    下面我们再来说说具体的操作方法。

    在本讲开篇我们说过,Module Federation 是 Webpack 5 中新增的特性,所以,我们需要安装对应版本的 Webpack 及所需的工具。

    因为是多个应用之间互相导入导出,因此,我们这里需要创建至少两个应用,来展示相关配置操作,然后分别在两个应用下安装相关工具,这里我以 remoteApp(用来引入子模块的父应用)  和 exposeApp(用来导出模块的子应用)  两个应用为例进行展示。

    exposeApp(用来导出模块的子应用)项目基础配置

    基础配置内容如下

    使用 MF,需要在配置文件中引入 ModuleFederationPlugin,从名字就可以看出这是一个插件。因为是内置插件,所以也不需要单独安装,直接通过 require 关键字引入即可,这和一个普通插件的引入方式并没有区别。

    const path = require("path");
    const Mfp = require("webpack").container.ModuleFederationPlugin;
    
    const deps = require("../package.json").dependencies;
    
    module.exports = {
      //  mode 工作模式
      mode: "development",
      //  服务器
      devServer: {
        port: 4000,
      },
      //  插件
      plugins: [
        new Mfp({
          // 当前微应⽤名称
          name: "studentProject",
          // 对外提供的打包后的⽂件名(引⼊时使⽤)
          filename: "student.js",
          // 暴露的应用内具体模块
          exposes: {
            // 格式   名称:代码路径
            "./studentModule": path.join(__dirname, "../src/App"),
          },
          // 共享依赖
          shared: {
            ...deps,
            antd: { singleton: true },
            react: {
              singleton: true,
            },
            "react-dom": {
              singleton: true,
            },
          },
        }),
      ],
    };
    

    因为导出应用中的模块是具体可选的,因此需要将导出的模块进行单独文件的打包。

    参数

    • filename 就是指定具体导出的模块文件名;
    • name 参数则代表当前导出的应用名称;
    • exposes 参数是一个对象,在这里具体指定哪个模块需要导出,对象中的属性表示具体导出模块的名字,值则是指定具体导出模块的路径及文件名。

    总体来说,我们导出的是一个微应用,而组成微应用的,便是当前应用下的某些模块,可以是一个,也可以指定多个。

    其他详细配置可以参考module-federation-examples 源码

    需要注意的是,为了防止开发服务器的端口冲突,我这里将两个应用端口分别设置了 40004001

    remoteApp(用来引入子模块的父应用)项目基础配置

    WebPack 配置

    const Mfp = require("webpack").container.ModuleFederationPlugin;
    
    const deps = require("../package.json").dependencies;
    
    module.exports = {
      plugins: [
        new Mfp({
          name: "userProject",
          // 导⼊远程子应用模块
          remotes: {
            // 导⼊后给模块起个别名:“微应⽤名称@地址/导出的⽂件名”
            remoteStudentProject: "student@http://localhost:4000/student.js",
          },
          // 共享依赖
          shared: {
            ...deps,
            antd: { singleton: true },
            react: {
              singleton: true,
            },
            "react-dom": {
              singleton: true,
            },
          },
        }),
      ],
    };
    

    如果只作为导入其他应用的微前端配置,其实非常简单,只需要在 remotes 中具体配置要导入的应用即可,具体的导入规则是:

    导入应用别名:' 微应⽤名称@应用远程地址/导出的⽂件名 '
    

    业务代码配置

    导入的相关配置完成后,接下来如何在应用中使用导入的内容呢?我们在 src/index.js 中引入使用,具体代码如下:

    import React from "react";
    import ReactDOM from "react-dom";
    import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
    
    // 该组件是动态加载的
    const Home = React.lazy(() => import("@/pages/Home"));
    const About = React.lazy(() => import("@/pages/About"));
    
    // MF导入
    // import("子应用别名/子应用路径"))
    const ChildApp = React.lazy(() => import("remoteStudentProject/studentModule"));
    
    import store from "@/redux/store";
    
    import "./index.less";
    
    const App = () => (
      <Router>
        <div>
          <ul>
            <li>
              <Link to='/'>Home</Link>
            </li>
            <li>
              <Link to='/about'>About</Link>
            </li>
            <li>
              <Link to='/ChildApp'>ChildApp</Link>
            </li>
          </ul>
    
          <hr />
    
          <React.Suspense fallback={<div>loading...</div>}>
            <Switch>
              <Route exact path='/'>
                <Home />
              </Route>
              <Route path='/about'>
                <About />
              </Route>
              {/*远程子应用 */}
              <Route path='/ChildApp'>
                <ChildApp />
              </Route>
            </Switch>
          </React.Suspense>
        </div>
      </Router>
    );
    
    ReactDOM.render(<App />, document.getElementById("root"));
    

    通过上面的代码你能够发现,引入微应用返回的是一个 Promise,最终会返回一个 "模块对象" 的结果,

    我们前面说过,MF 是去中心化的,一个微应用,既可以导出也可以导入,如果你想将 remoteApp 作为一个微应用导出,那么,你可以在配置中继续添加导出的配置选项,比如:

    const Mfp = require("webpack").container.ModuleFederationPlugin;
    
    const deps = require("../package.json").dependencies;
    
    module.exports = {
      plugins: [
        new Mfp({
          // 名称
          name: "userProject",
          // 导⼊远程子应用模块
          remotes: {
            // 导⼊后给模块起个别名:“微应⽤名称@地址/导出的⽂件名”
            remoteStudentProject: "student@http://localhost:4000/student.js",
          },
          // 作为子应用还可以再次导出,同样可以被其他项目引用
          exposes: {
            "./userAbout": path.join(__dirname, "../src/About"),
          },
          // 共享依赖
          shared: {
            ...deps,
            antd: { singleton: true },
            react: {
              singleton: true,
            },
            "react-dom": {
              singleton: true,
            },
          },
        }),
      ],
    };
    

    最后,不管是导入还是导出,绝大多数插件模块都需要实例化对象,我们这里的 ModuleFederationPlugin 也不例外,使用它,就是将这个实例对象,放入 plugins 数组中,实例化时,在传入的对象中,设置不同的属性参数。

    为了方便你记忆,我把前面用到的不同参数的含义整理在下面这张表格中:

    前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    其中,

    • remotes 代表导入远程模块,
    • exposes 表示导出了当前模块,这样就完成了模块的导入和导出

    这就是前面介绍的去中心化的体现——一个模块既可以导出又可以导入,不需要通过中心基座在各个微应用之间连接,任何一个微应用都可以当作一个中心,也都可以被其他模块导入。

    通过以上简单的应用,我们对 MF 有了一个初步的认识,而上面的配置在 Webpack 打包时会执行怎样的操作呢?

    打包后的结果代码,是如何加载远程模块的?自己的模块又是如何导出提供给其他应用导入的呢?这就需要我们阅读打包之后的代码去一探究竟了。

    Module Federation 的构建解析

    我们分别在 exposeApp(用来导出模块的子应用) 和 remoteApp(用来引入子模块的父应用) 中, 找到打包后的 dist 目录。

    应用导出分析

    其中 exposeApp打包后的 student.js 文件,具体如下所示:

    前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    • moduleMap:通过 exposes 生成的模块集合。

    应用导入分析

    而最关键的则是导入部分,在 remoteApp 中,我们找到打包后的 bundle.js 文件,其中具体代码如下:

    前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    我们能到 Promise 方式的代码加载

    其实 Promise 的作用就是加载了指定 URL 路径的 em.js,这就是我们导出的模块文件名。

    • 其中__webpack_require__.l函数作用就是读取 URL 路径,然后动态生成script标签并插入到代码中

    现在已经可以读取到远程模块的内容了,那如何加载指定的模块代码呢?

    继续往下看,我们能够看到 webpack_require.f.remotes 的处理:

    前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    1. 首先,MF 会让 Webpack 以 filename 作为文件名生成文件,并将具体代码打包到 xxx,bundle.js 文件中。

    2. 其次,文件中以 var 的形式暴露了一个名为 name 的全局变量,其中包含 exposes 中配置的内容。

    3. 最后,先通过 remoteinit 方法将自身写入 remote 中,再通过 get 获取 remoteexpose 的组件;

    4. 而作为 remote 时,先判断是否有可用的共享依赖。若有,则加载这部分依赖;如果没有,则加载自身依赖。

    Module Federation 的应用场景

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

    最明显的就是微前端的应用,通过 remoteexpose 可以将一个应用作为微应用导入导出,微应用之间相互独立,也可以互相导入导出,没有中心基座的限制。而由 YY 业务中台 Web 前端组团队自主研发的 EMP 微前端方案就是基于 MF 的能力而实现的。

    再就是资源复用,以减少编译体积,可以将多个应用的通用组件进行单独部署,通过 MF 的功能在运行时引入到其他项目中,这样组件代码就不会编译到项目中,同时也能满足多个项目同时使用的需求,一举两得。

    总结

    我们通过对 Module Federation 特性的解读,简单了解了 Webpack 通过插件的方式导入导出一个模块,实现一个微前端架构应用。

    从一定程度上来说,Module Federation 是目前去中心化模式唯一落地的技术,通过简单的配置就能够很轻松地完成一个微前端架构的基本模型。而去中心化的方案,让我们脱离了固定的中心基座,极大地增加了项目的灵活性。

    但是,如果换一个角度想,没有了统一管理的基座中心,每一个微应用的管理维护就显得极其重要了,这也对我们开发者团队提出了挑战。此外,随着项目数量和规模越来越大,一个项目下的微应用必然增加,如果管理不到位,极有可能带来致命的混乱。

    最后,也请你思考一下,你现在的项目是否适合做去中心化的微前端架构的升级呢?欢迎在评论区留言交流哦。

    思考

    1. webpack 设置了 publicPath:"auto"会有什么效果
    2. 当子项目单独抽离 CSS 时,在主项目中引用,可能会找不到 CSS 文件吗?如何解决
    3. Module Federation 的导入导出与 ES6 的导入导出有什么区别
    4. 如何搭建 Module Federation 并用于远程部署环境

    最后

    文章浅陋,欢迎各位看官评论区留下的你的见解!

    觉得有收获的同学欢迎点赞,关注一波!

    前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    往期好文

    • 15 张前端高清知识地图,强烈建议收藏
    • 二维码扫码登录是什么原理
    • 让我告诉你一些强无敌的 NPM 软件包
    • 最全 ECMAScript 攻略
    • 前端开发者应该知道的 Centos/Docker/Nginx/Node/Jenkins 操作(? 长文)

    参考文档

    • 精读《Webpack5 新特性 - 模块联邦》
    • webpack 官方文档-动态远程容器
    • webpack 5 模块联邦实现微前端疑难问题解决

    起源地下载网 » 前端工程化之 Webpack5 Module-Federation 入门手册 | 8月更文挑战

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元