前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微前端架构实战

微前端架构实战

原创
作者头像
西岭老湿
修改2021-04-26 10:12:36
3.8K0
修改2021-04-26 10:12:36
举报
文章被收录于专栏:西岭老湿西岭老湿

微前端架构实战

如何实现多个应用之间的资源共享

之前比较多的处理方式是npm包形式抽离和引用,比如多个应用项目之间,可能有某业务逻辑模块或者其他是可复用的,便抽离出来以npm包的形式进行管理和使用。但这样却带来了以下几个问题:

  • 发布效率低下。如果需要迭代npm包内的逻辑业务,需要先发布npm包之后,再每个使用了该npm包的应用都更新一次npm包版本,再各自构建发布一次,过程繁琐。如果涉及到的应用更多的话,花费的人力和精力就更多了。
  • 多团队协作容易不规范。包含通用模块的npm包作为共享资产,“每个人”拥有它,但在实践中,这通常意味着没有人拥有它。它很快就会充满杂乱风格不一致的代码,没有明确的约定或技术愿景。

这些问题让我们意识到,扩展前端开发规模以便于多个团队可以同时开发一个大型且复杂的产品是一个重要但又棘手的难题。

因此,早在2016年,微前端概念诞生了。

第1章 什么是微前端?

1-1 微前端的概念

Micro Frontends: https://micro-frontends.org/ 官网定义了微前端概念:

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently.

从 Micro Frontends 官网可以了解到,微前端概念是从微服务概念扩展而来的,摒弃大型单体方式,将前端整体分解为小而简单的块,这些块可以独立开发、测试和部署,同时仍然聚合为一个产品出现在客户面前。可以理解微前端是一种将多个可独立交付的小型前端应用聚合为一个整体的架构风格。

值得留意的几个点:

  • 微前端不是一门具体的技术,而是整合了技术、策略和方法,可能会以脚手架、辅助插件和规范约束这种生态圈形式展示出来,是一种宏观上的架构。这种架构目前有多种方案,都有利弊之处,但只要适用当前业务场景的就是好方案。
  • 微前端并没有技术栈的约束。每一套微前端方案的设计,都是基于实际需求出发。如果是多团队统一使用了react技术栈,可能对微前端方案的跨技术栈使用并没有要求;如果是多团队同时使用了react和vue技术栈,可能就对微前端的跨技术栈要求比较高。
微前端的使用场景
  1. 拆分巨型应用,使应用变得更加可维护
  2. 兼容历史应用,实现增量开发

1-2 微前端的优势

同步更新

对比了npm包方式抽离,让我们意识到更新流程和效率的重要性。微前端由于是多个子应用的聚合,如果多个业务应用依赖同一个服务应用的功能模块,只需要更新服务应用,其他业务应用就可以立马更新,从而缩短了更新流程和节约了更新成本。

增量升级

迁移是一项非常耗时且艰难的任务,比如有一个管理系统使用 AngularJS 开发维护已经有三年时间,但是随时间的推移和团队成员的变更,无论从开发成本还是用人需求上,AngularJS 已经不能满足要求,于是团队想要更新技术栈,想在其他框架中实现新的需求,但是现有项目怎么办?直接迁移是不可能的,在新的框架中完全重写也不太现实。

使用微前端架构就可以解决问题,在保留原有项目的同时,可以完全使用新的框架开发新的需求,然后再使用微前端架构将旧的项目和新的项目进行整合。这样既可以使产品得到更好的用户体验,也可以使团队成员在技术上得到进步,产品开发成本也降到的最低。

独立部署与发布

在目前的单页应用架构中,使用组件构建用户界面,应用中的每个组件或功能开发完成或者bug修复完成后,每次都需要对整个产品重新进行构建和发布,任务耗时操作上也比较繁琐。

在使用了微前端架构后,可以将不能的功能模块拆分成独立的应用,此时功能模块就可以单独构建单独发布了,构建时间也会变得非常快,应用发布后不需要更改其他内容应用就会自动更新,这意味着你可以进行频繁的构建发布操作了。

独立团队决策

因为微前端构架与框架无关,当一个应用由多个团队进行开发时,每个团队都可以使用自己擅长的技术栈进行开发,也就是它允许适当的让团队决策使用哪种技术,从而使团队协作变得不再僵硬。

1-3 微前端落地方案

自组织模式:通过约定进行互调,但会遇到处理第三方依赖等问题。

基座模式:通过搭建基座、配置中心来管理子应用。如基于SIngle Spa的偏通用的乾坤方案,也有基于本身团队业务量身定制的方案。

去中心模式:脱离基座模式,每个应用之间都可以彼此分享资源。如基于Webpack 5 Module Federation实现的EMP微前端方案,可以实现多个应用彼此共享资源分享。

其中,目前值得关注是去中心模式中的EMP微前端方案,既可以实现跨技术栈调用,又可以在相同技术栈的应用间深度定制共享资源,如果刚开始调研微前端的话,可以先尝试了解一下EMP微前端方案,或许会给你带来不错的使用体验

第2章 Systemjs模块化解决方案

Systemjs:https://github.com/systemjs/systemjs

在微前端架构中,微应用被打包为模块,但浏览器不支持模块化,需要使用 systemjs 实现浏览器中的模块化。

systemjs 是一个用于实现模块化的 JavaScript 库,有属于自己的模块化规范。

在开发阶段我们可以使用 ES 模块规范,然后使用 webpack 将其转换为 systemjs 支持的模块。

案例:通过 webpack 将 react 应用打包为 systemjs 模块,在通过 systemjs 在浏览器中加载模块

npm install webpack@5.17.0 webpack-cli@4.4.0 webpack-dev-server@3.11.2 html-webpack-plugin@4.5.1 @babel/core@7.12.10 @babel/cli@7.12.10 @babel/preset-env@7.12.11 @babel/preset-react@7.12.10 babel-loader@8.2.2

package.json

代码语言:javascript
复制
{
  "name": "systemjs-react",
  "scripts": {
    "start": "webpack serve"
  },
  "dependencies": {
    "@babel/cli": "^7.12.10",
    "@babel/core": "^7.12.10",
    "@babel/preset-env": "^7.12.11",
    "@babel/preset-react": "^7.12.10",
    "babel-loader": "^8.2.2",
    "html-webpack-plugin": "^4.5.1",
    "webpack": "^5.17.0",
    "webpack-cli": "^4.4.0",
    "webpack-dev-server": "^3.11.2"
  }
}

webpack.config.js

代码语言:javascript
复制
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  mode: "development",
  entry: "./src/index.js", // 入口
  output: { // 出口
    // 打包目录及文件
    path: path.join(__dirname, "build"), 
    filename: "index.js",
    // 指定构建时所需要的库
    libraryTarget: "system"
  },
  devtool: "source-map",
  // 服务器运行配置
  devServer: {
    port: 9000, // 端口
    // 静态资源文件夹
    contentBase: path.join(__dirname, "build"),
    historyApiFallback: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            // 对应语法转换
            presets: ["@babel/preset-env", "@babel/react"]
          }
        }
      }
    ]
  },
  plugins: [ // 插件
    new HtmlWebpackPlugin({
      /* 打包时,不需要自动引入JS文件(<script> 标签) */
      inject: false, 
      /* 使用微前端的方式,我们需要自己加载对应的 JS 文件 */ 
​
      template: "./src/index.html"
    })
  ],
  // 添加打包排除选项,微前端中需要使用公共的 React ,打包是不需要的
  externals: ["react", "react-dom", "react-router-dom"]
}

src/index.html

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>systemjs-react</title>
    <!-- 按照 systemjs 模块化的方式引入React框架应用 -->
    <script type="systemjs-importmap">
      {
        "imports": {
          "react": "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js",
          "react-dom": "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js",
          "react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js"
        }
      }
    </script>
    <!--  systemjs 库 -->
    <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.0/dist/system.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script>
      // 按照 systemp 的方式,引入具体应用
      System.import("./index.js")
    </script>
  </body>
</html>

src/index.js

代码语言:javascript
复制
import React from "react"
import ReactDom from "react-dom"
​
import App from './App.js'
​
ReactDom.render(<App />, document.getElementById("root")) 

src/App.js

代码语言:javascript
复制
import React from "react"
​
export default function App(){
    return <div>React micro for systemjs</div>
}

第3章 微前端框架 single-spa

https://zh-hans.single-spa.js.org/

single-spa:https://single-spa.js.org/ 是一个实现微前端架构的框架。

在 single-spa 框架中有三种类型的微前端应用:

  1. single-spa-application / parcel:微前端架构中的微应用,可以使用 vue、react、angular 等框架。
  2. single-spa root config:创建微前端容器应用。
  3. utility modules:公共模块应用,非渲染组件,用于跨应用共享 javascript 逻辑的微应用。

3-1 创建容器应用

安装 single-spa 脚手架工具:npm install create-single-spa@2.0.3 -g

创建微前端容器应用:create-single-spa

  1. 应用文件夹填写 container
  2. 应用选择 single-spa root config
  3. 组织名称填写 study 组织名称可以理解为团队名称,微前端架构允许多团队共同开发应用,组织名称可以标识应用由哪个团队开发。 应用名称的命名规则为 @组织名称/应用名称,比如 @study/todos

4. 启动应用:cd ./singletest && npm start

5. 访问应用:localhost:9000

3-2 容器默认代码解析

src/xx-root-config.js

代码语言:javascript
复制
// 从框架中引入 两个 方法,下面调用
import { registerApplication, start } from "single-spa";
​
/*
    注册微前端应用
        1. name: 字符串类型, 微前端应用名称 "@组织名称/应用名称"
    2. app: 函数类型, 返回 Promise, 通过 systemjs 引用打包好的微前端应用模块代码 (umd)
        3. activeWhen: 路由匹配时激活应用
*/
registerApplication({
  name: "@single-spa/welcome",
  app: () =>
    System.import(
      "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
    ),
  activeWhen: ["/"],
});
代码语言:javascript
复制
// 当前组织的微应用引入示例
// registerApplication({
//   name: "@xl/navbar",
//   app: () => System.import("@xl/navbar"),
//   activeWhen: ["/"]
// });
​
​
// start 方法必须在 single spa 的配置文件中调用
// 在调用 start 之前, 应用会被加载, 但不会初始化, 挂载或卸载.
start({
  // 是否可以通过 history.pushState() 和 history.replaceState() 更改触发 single-spa 路由
  // true 不允许 false 允许 (先了解,有印象)
  urlRerouteOnly: true,
});
​

index.ejs

代码语言:javascript
复制
<body>
  <main></main>
  <!-- 导入微前端容器应用 -->
  <script>
    System.import('@xl/root-config');
  </script>
  <!-- 
    import-map-overrides 可以覆盖导入映射
    当前项目中用于配合 single-spa Inspector 调试工具使用.
    可以手动覆盖项目中的 JavaScript 模块加载地址, 用于调试.
-->
  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>
代码语言:javascript
复制
  <!-- 用于支持 Angular 应用 -->
  <!-- <script src="https://cdn.jsdelivr.net/npm/zone.js@0.11.3/dist/zone.min.js"></script> -->
  <!-- 用于覆盖通过 import-map 设置的 JavaScript 模块下载地址 -->
  <script src="https://cdn.jsdelivr.net/npm/import-map-overrides@2.2.0/dist/import-map-overrides.js"></script>
​
  <!-- 判断是否是本地 -->
  <% if (isLocal) { %>
  <!-- 模块加载器 -->
  <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.js"></script>
  <!-- systemjs 用来解析 AMD 模块的插件 -->
  <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/amd.js"></script>
  <% } else { %>
  <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/amd.min.js"></script>
  <% } %>
代码语言:javascript
复制
  <script type="systemjs-importmap">
    {
      "imports": {
        "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js"
      }
    }
  </script>
  <!-- single-spa 预加载 -->
  <link rel="preload" href="https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js" as="script">
​
  <!-- Add your organization's prod import map URL to this script's src  -->
  <!-- <script type="systemjs-importmap" src="/importmap.json"></script> -->
<!-- JavaScript 模块下载地址 此处可放置微前端项目中的公共模块 -->
  <% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@xl/root-config": "//localhost:9000/xl-root-config.js"
      }
    }
  </script>
  <% } %>
​

3-3 创建基于 React 的微应用

3-3-1 创建 React 微应用

创建应用:create-single-spa ,注意组织及项目名字,后面注册微应用是会用到

  1. 应用目录输入 todos
  2. 框架选择 react

修改应用端口 && 启动应用

代码语言:javascript
复制
{
  "scripts": {
     "start": "webpack serve --port 9002",
  }
}

启动应用: npm start

3-3-2 注册应用

将 React 项目的入口文件注册到基座应用 (容器应用) 中

\container\src\study-root-config.js :

代码语言:javascript
复制
// React -- todos 
registerApplication({
  name: "@study/todos",
  app: () => System.import("@study/todos"),
  activeWhen: ["/todos"]
});

指定微前端应用模块的引用地址:

(可以直接访问对应应用服务器,有提示 URL 加载地址)

代码语言:javascript
复制
<% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@study/root-config": "//localhost:9000/study-root-config.js",
        "@study/todos": "//localhost:9002/study-todos.js"
      }
    }
  </script>
  <% } %>
​

指定公共库的访问地址,默认情况下,应用中的 react 和 react-dom 没有被 webpack 打包, single-spa 认为它是公共库,不应该单独打包。

代码语言:javascript
复制
<script type="systemjs-importmap">
    {
      "imports": {
        "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js",
        "react": "https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js",
        "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.1/umd/react-dom.production.min.js"
      }
    }
  </script>

修改默认应用代码,已独立页面展示应用内容

container\src\study-root-config.js

代码语言:javascript
复制
// registerApplication({
//   name: "@single-spa/welcome",
//   app: () =>
//     System.import(
//       "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
//     ),
//   activeWhen: ["/"],
// });
​
// 修改默认应用注册方式,独立页面展示应用内容
registerApplication(
  "@single-spa/welcome",
  () => System.import(
    "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
  ),
  location=>location.pathname === '/'
);
​
3-3-3 指定应用渲染位置

micro\container\src\index.ejs

代码语言:javascript
复制
<body>
  <main></main>
​
  <h2>
    <!-- 指定应用展示位置 -->
    <div id="myreact"></div>
  </h2>
​
  <script>
    System.import('@study/root-config');
  </script>
  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>

micro\todos\src\study-todos.js

代码语言:javascript
复制
const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  // 渲染根组件
  rootComponent: Root,
  // 错误边界函数
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    return null;
  },
  // 指定根组件的渲染位置
  domElementGetter:()=>document.getElementById('myreact')
});
3-3-4 React 应用代码解析

micro\todos\src\study-todos.js

代码语言:javascript
复制
import React from "react";
import ReactDOM from "react-dom";
// single-spa-react 用于创建使用 React 框架实现的微前端应用
import singleSpaReact from "single-spa-react";
// 用于渲染在页面中的根组件 就相当于传统React应用的App.js文件
import Root from "./root.component";
​
​
const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  // 渲染根组件
  rootComponent: Root,
  // 错误边界函数
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    // return null;
    return ()=> <div>发生错误时此处内容将会被渲染</div>
  },
  // 指定根组件的渲染位置
  domElementGetter:()=>document.getElementById('myreact')
});
​
// 暴露必要的生命周期函数
export const { bootstrap, mount, unmount } = lifecycles;
​
3-3-5 React 微前端路由配置

准备好两个路由组件

micro\todos\src\home.js && micro\todos\src\about.js

代码语言:javascript
复制
import React, { Component } from 'react'
​
export default class home extends Component {
    render() {
        return (
            <div>
                <h2>什么是快乐星球</h2>
            </div>
        )
    }
}
​
=========我是两个组件之间的秀丽华美分割线=============
​
import React from 'react'
​
function about() {
    return (
        <div>
            <h2>快乐星球就是学习微前端</h2>
        </div>
    )
}
​
export default about
​

micro\todos\src\root.component.js

代码语言:javascript
复制
import React from "react";
// 引入路由相关组件
import {BrowserRouter, Switch, Route, Redirect, Link} from "react-router-dom"
// 引入组件
import Home from './home'
import About from './about'
​
​
export default function Root(props) {
  // return <section>{props.name} is mounted! && 拉勾大前端</section>;
  return (
    // 使用路由组件,设计基础路由路径
    <BrowserRouter basename="/todos">
      <div>{props.name}</div>
      {/* 设置点击链接,跳转路由 */}
      <div>
        <Link to="/home">Home</Link> | 
        <Link to="/about">About</Link>
      </div>
​
      {/* 路由展示 */}
      <Switch>
        <Route path="/home">
          <Home />
        </Route>
        <Route path="/about">
          <About></About>
        </Route>
        <Route path="/">
          {/* 路由重定向 */}
          <Redirect to="/home"></Redirect>
        </Route>
      </Switch>
    </BrowserRouter>
  )
}

路由文件已公共模块引入,\micro\container\src\index.ejs

代码语言:javascript
复制
<script type="systemjs-importmap">
    {
      "imports": {
        "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js",
        "react": "https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js",
        "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.1/umd/react-dom.production.min.js",
        "react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js"
      }
    }
</script>

修改 webpack 配置文件,排除路由模块打包,micro\todos\webpack.config.js

代码语言:javascript
复制
return merge(defaultConfig, {
    // modify the webpack config however you'd like to by adding to this object
    externals: ["react-router-dom"]
});

3-4 创建基于 Vue 的微应用

3-4-1 创建应用

创建应用:create-single-spa

  1. 项目文件夹填写 realworld
  2. 框架选择 Vue
  3. 生成 Vue 2 项目

因为 vue && vue-router 需要通过公共模块打包,所以,在应用内部需要配置不打包

micro\realworld\vue.config.js

代码语言:javascript
复制
module.exports = {
    chainWebpack:config=>{
        // 配置不打包 Vue 及 vue-router
        config.externals(["vue","vue-router"])
    }
}

修改项目启动命令:micro\realworld\package.json

代码语言:javascript
复制
"scripts": {
    "serve": "vue-cli-service serve",
    "start": "vue-cli-service serve --port 9003",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "serve:standalone": "vue-cli-service serve --mode standalone"
  },

注册应用:micro\container\src\study-root-config.js

代码语言:javascript
复制
// Vue -- todos
registerApplication({
  name: "@study/realworld",
  app: () => System.import("@study/realworld"),
  activeWhen: ["/realworld"],
});

micro\container\src\index.ejs

加载 vue && vue-router

代码语言:javascript
复制
  <script type="systemjs-importmap">
    {
      "imports": {
        "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js",
        "react": "https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js",
        "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.1/umd/react-dom.production.min.js",
        "react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js",
        "vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
        "vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js"
      }
    }
  </script>

导入应用,应用地址可以直接访问应用后,在浏览器的提示中获取;

代码语言:javascript
复制
<% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@study/root-config": "//localhost:9000/study-root-config.js",
        "@study/todos": "//localhost:9002/study-todos.js",
        "@study/realworld": "//localhost:9003/js/app.js"
      }
    }
  </script>
  <% } %>
3-4-2 应用路由配置

\micro\realworld\src\main.js

代码语言:javascript
复制
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
import App from './App.vue';
​
import VueRouter from 'vue-router'
Vue.use(VueRouter)
​
// 路由组件
const Foo = { template: "<div>Foooooo</div>" }
const Bar = { template: "<div>Barrrrr</div>" }
​
// 路由规则
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar },
]
​
// 路由实例
const router = new VueRouter({ routes, mode: "history", base: "/realworld" })
​
​
Vue.config.productionTip = false;
​
const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: {
    // 注册路由
    router,
    render(h) {
      return h(App, {
        props: {
          // 组件传参
        },
      });
    },
  },
});
​
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
​

micro\realworld\src\App.vue

代码语言:javascript
复制
<template>
  <div id="app">
    <div>
      <router-link to="/foo">Goto Foo</router-link> |
      <router-link to="/bar">Goto Bar</router-link>
    </div>
    <div>
      <router-view></router-view>
    </div>
  </div>
</template>

3-5 创建 utility modules

3-5-1 utility 独立应用创建

用于放置跨应用共享的 JavaScript 逻辑,它也是独立的应用,需要单独构建单独启动。

  1. 创建应用:create-single-spa
    1. 文件夹填写 tools
    2. 应用选择 in-browser utility module (styleguide, api cache, etc)
  2. 修改端口,启动应用, \micro\tools\package.json "scripts": { "start": "webpack serve --port 9005", }

导出公共方法 : micro\tools\src\study-tools.js

代码语言:javascript
复制
export function happyStar(who){
    console.log(`${who} hahahhahahhah`)
    return 'happy star 之 快乐的源泉'
}

在模板文件中声明应用模块访问地址 : micro\container\src\index.ejs

代码语言:javascript
复制
 <% if (isLocal) { %>
  <script type="systemjs-importmap">
    {
      "imports": {
        "@study/root-config": "//localhost:9000/study-root-config.js",
        "@study/todos": "//localhost:9002/study-todos.js",
        "@study/realworld": "//localhost:9003/js/app.js",
        "@study/tools": "//localhost:9005/study-tools.js"
      }
    }
  </script>
  <% } %>
3-5-2 在 React 应用中使用该方法

MicroFrontends\micro\todos\src\about.js

代码语言:javascript
复制
import React, { useEffect, useState } from "react";
​
// 自定义钩子函数
function useToolsModule() {
  const [toolsModule, setToolsModule] = useState();
  useEffect(() => {
    // 导入,异步promise返回
    System.import("@study/tools").then(setToolsModule);
  }, []);
  return toolsModule;
}
​
function about() {
  var back = "";
  // 调用钩子函数
  const toolsModule = useToolsModule();
  if (toolsModule) {
    // 调用共享逻辑的方法
    back = toolsModule.happyStar("React todo");
  }
​
  return (
    <div>
      <h2>快乐星球就是学习微前端--{back}</h2>
    </div>
  );
}
​
export default about;
​
3-5-3 在 Vue 应用中使用该方法

micro\realworld\src\main.js

代码语言:javascript
复制
​
// 路由组件
// const Foo = { template: "<div>猜不到吧</div>" };
import Foo from './components/Foo'
const Bar = { template: "<div>还是快乐星球啊哈哈哈哈</div>" };
​

micro\realworld\src\components\Foo.vue

代码语言:javascript
复制
<template>
  <div>
    <h2>什么是快乐星球 {{ msg }}</h2>
    <button @click="getHappy">点我获取答案</button>
  </div>
</template>
​
<script>
export default {
  data() {
    return {
      msg: "",
    };
  },
  methods: {
    async getHappy() {
      let toolsModule = await window.System.import("@study/tools");
      this.msg = toolsModule.happyStar("Vue");
    },
  },
};
</script>
​
<style>
</style>

第4章 模块联邦实现微应用

4-1 基础构建-React

基础应用代码安装

代码语言:javascript
复制
// webpack5 
npm install webpack webpack-cli html-webpack-plugin css-loader style-loader babel-loader @babel/core  @babel/preset-env  @babel/preset-react  webpack-dev-server  -D 
​
npm install react react-dom 

基础代码:

代码语言:javascript
复制
​
// ======insex.html========
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>RootReact</title>
</head>
<body>
  <!-- 加一行注释 -->
  <div id="root"></div>
</body>
</html>
代码语言:javascript
复制
// ======insex.js========
​
import React from "react"
import ReactDom from "react-dom"
import App from "./App"
ReactDom.render(<App/>,document.getElementById('root'))
​
​
// ======App.js===========
import React from "react"
import User from "./User"
​
​
let App = () => {
    return (
        <div>
            <h3>webpack55</h3>
            <User/>
        </div>
    )
}
export default App;
​
​
// ===== User.js==========
import React from "react"
​
const User = ()=>{
  return (
    <div>
      UserList
    </div>
  )
}
​
export default User

4-2 基础配置 webpack.config.js

代码语言:javascript
复制
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
​
module.exports = {
    // entry 入口,output出口,module模块,plugins 插件  mode工作模式,devServer开发服务器
​
    //  mode 工作模式
    mode: 'development', // production  、 development、none
​
    // 入口 
    entry: './src/index.js',
​
    //  出口 
    output: {
        filename: './bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    // 模块 
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                '@babel/preset-env',
                                '@babel/preset-react'
                            ]
                        }
                    }
                ]
            },
        ]
    },
​
    //  插件 
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
​
    //  服务器
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        port: 3000,
        open: true
    },
​
​
}

package.json 启动命令:

代码语言:javascript
复制
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build":"webpack",
    "start":"webpack serve"
  },

4-3 导出微应用

代码语言:javascript
复制
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 导入模块联邦插件
const Mfp = require('webpack').container.ModuleFederationPlugin;
​
^^^^^^^^Codes^^^^^^^^^^^^^
   
    //  插件 
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        // 实例化模块联邦插件
        new Mfp({
            // 对外提供的打包后的文件名,引入时使用
            filename:'myuser.js',
            // 微应用(模块) 名称,类似 single-spa的团队名字
            name:'study',
            exposes:{
                // 具体的一个文件可以当作一个模块应用,
                // 每一个应用都有一个名字和具体指定的代码文件
                // 名字:具体打包的代码文件
               './xx':'./src/User.js',
               './goods':'./src/Goods.js'
            }
        })
    ],

4-4 导入应用模块

代码语言:javascript
复制
const Mfp = require('webpack').container.ModuleFederationPlugin    
​
//  插件 
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
​
        new Mfp({
            // // 微应用(模块) 名称,当前模块自己的名字
            name:'roots',
            // 导入模块
            remotes:{
                // 导入后给模块起个别名:"微应用名称@地址/导出的文件名"
                one:"study@http://localhost:3001/myuser.js"
            }
        })
    ],

在组件中使用

代码语言:javascript
复制
// const xx = React.lazy(()=>import("导入时模块别名/导出时具体文件对应的名字"))
const Us = React.lazy(()=>import("one/xx"))
const Gos = React.lazy(()=>import("one/goods"))
​
let App = () => {
    return (
        <div>
            <h3>webpack5 root</h3>
            <User/>
            <React.Suspense fallback="Loading app">
                <Us />
                <Gos />
            </React.Suspense>
        </div>
    )
}
export default App;

4-5 模块联邦实现 Vue3.0 微前端架构

完整代码示例:modulefederationvue3: 基于模块联邦实现的 Vue3.0 微前端架构示例 (gitee.com)

package.json

代码语言:javascript
复制
{
  "name": "@vue3-demo/layout",
  "private": true,
  "version": "1.0.0",
  "scripts": {
    "start": "webpack serve",
    "serve": "serve dist -p 3001",
    "build": "webpack --mode production",
    "clean": "rm -rf dist"
  },
  "dependencies": {
    "@babel/core": "^7.13.16",
    "babel-loader": "^8.2.2",
    "serve": "^11.3.2",
    "vue": "^3.0.0-rc.5",
    "vue-router": "^4.0.0",
    "vuex": "^4.0.0"
  },
  "devDependencies": {
    "@vue/compiler-sfc": "3.0.0",
    "css-loader": "5.2.4",
    "file-loader": "6.2.0",
    "html-webpack-plugin": "5.3.1",
    "mini-css-extract-plugin": "0.9.0",
    "url-loader": "4.1.1",
    "vue-loader": "16.0.0-beta.8",
    "webpack": "5.35.0",
    "webpack-dev-server": "3.11.2",
    "webpack-cli": "4.6.0",
    "sass": "^1.26.5",
    "sass-loader": "^8.0.2"
  }
}
​
4-5-1 在 home 应用中导出模块

home\webpack.config.js

代码语言:javascript
复制
const { ModuleFederationPlugin } = require("webpack").container;
​
module.exports = (env = {}) => ({
^^^^^^^………………^^^^^^^^^^^
  plugins: [
    new VueLoaderPlugin(),
​
    // 模块联邦
    new ModuleFederationPlugin({
      name: "home",
      filename: "remoteEntry.js",
      // 导出
      exposes: {
        "./User": "./src/components/User",
        // "./Button": "./src/components/Button",
      },
    }),
​
  ]
});
​
4-5-2 在layout应用中导入

layout\webpack.config.js

代码语言:javascript
复制
const { ModuleFederationPlugin } = require("webpack").container;
…………
module.exports = (env = {}) => ({
  
  plugins: [
    …………
    new ModuleFederationPlugin({
      name: "layout",
      filename: "remoteEntry.js",
      // 导入
      remotes: {
        importUser: "home@http://localhost:3002/remoteEntry.js",
      },
      exposes: {},
    }),
  ]
});

layout\src\views\About.vue

代码语言:javascript
复制
<template>
  <div class="about">
    永远记得 西岭老湿 爱你
    <hr>
    <p>开启home应用的3002端口应用可见,模块联邦内容</p>
    <User />
  </div>
</template>
​
<script>
 import { defineAsyncComponent } from "vue";
// 导入模块联邦组件
const User = defineAsyncComponent(() => import("importUser/User"));
export default {
  components: {
    User
  }
};
</script>

写好基础代码及对应配置后,分别启动 home 及 layout 两个应用项目就可以在 layout 应用的 about 中看到 home 应用中的 User 组件的内容了;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 微前端架构实战
    • 第1章 什么是微前端?
      • 1-1 微前端的概念
      • 1-2 微前端的优势
      • 1-3 微前端落地方案
    • 第2章 Systemjs模块化解决方案
      • 第3章 微前端框架 single-spa
        • 3-1 创建容器应用
        • 3-2 容器默认代码解析
        • 3-3 创建基于 React 的微应用
        • 3-4 创建基于 Vue 的微应用
        • 3-5 创建 utility modules
      • 第4章 模块联邦实现微应用
        • 4-1 基础构建-React
        • 4-2 基础配置 webpack.config.js
        • 4-3 导出微应用
        • 4-4 导入应用模块
        • 4-5 模块联邦实现 Vue3.0 微前端架构
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档