快速开始

本节分别从主、子 应用视角出发,介绍如何通过 Garfish API 来将应用接入 Garfish 框架

在线预览

主应用

通过 Garfish API 接入主应用整体流程分为 2 步:

  1. 添加 garfish 依赖包
  2. 通过 Garfish.run,提供挂载点、basename、子应用列表

1.安装依赖

npm install garfish --save

2.注册子应用并启动 Garfish

// index.js(主应用入口处)
import Garfish from 'garfish';
Garfish.run({
  basename: '/',
  domGetter: '#subApp',
  apps: [
    {
      name: 'react',
      activeWhen: '/react',
      entry: 'http://localhost:3000', // html入口
    },
    {
      name: 'vue',
      activeWhen: '/vue',
      entry: 'http://localhost:8080/index.js', // js入口
    },
  ],
});

当引入 Garfish 实例,执行实例方法 Garfish.run 后,Garfish 将会立刻启动路由劫持能力。

这时 Garfish 将会监听浏览器路由地址变化,当浏览器的地址发生变化时,Garfish 框架内部便会执行匹配逻辑,当解析到当前路径符合子应用匹配逻辑时,便会自动将应用挂载至指定的 dom 节点上,并在此过程中会依次触发子应用加载、渲染过程中的 生命周期钩子函数.

注意

请确保指定的节点存在于页面中,否则可能会导致出现 Invalid domGetter "xxx"​ 错误。在 Garfish 开始渲染时,无法查询到该挂载节点则会提示该错误

解决方案

  1. 将挂载点设置为常驻挂载点,不要跟随路由变化使子应用挂载点销毁和出现
  2. 保证 Garfish 在渲染时挂载点存在

如果你的业务需要手动控制应用加载,可以使用 Garfish.loadApp 手动加载 APP:

// 使用 loadApp 动态挂载应用
import Garfish from 'garfish';
const app = await Garfish.loadApp('vue-app', {
  domGetter: '#container',
  entry: 'http://localhost:3000',
  cache: true,
});

// 若已经渲染触发 show,只有首次渲染触发 mount,后面渲染都可以触发 show 提供性能
app.mounted ? app.show() : await app.mount();

子应用

通过 Garfish API 接入子应用整体流程分为 3 步:

  1. 调整子应用的构建配置(目前 Garfish 仅支持 umd 格式的产物)
  2. 导出子应用生命周期
  3. 设置应用路由 basename

1.调整子应用的构建配置

Webpack
Vite
// webpack.config.js
const webpack = require('webpack');
const isDevelopment = process.env.NODE_ENV !== 'production';

module.exports = {
  output: {
    // 开发环境设置 true 将会导致热更新失效
    clean: isDevelopment ? false : true,
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
    // 需要配置成 umd 规范
    libraryTarget: 'umd',
    // 修改不规范的代码格式,避免逃逸沙箱
    globalObject: 'window',
    // webpack5 使用 chunkLoadingGlobal 代替,或不填保证 package.json name 唯一即可
    jsonpFunction: 'garfish-demo-react',
    // 保证子应用的资源路径变为绝对路径
    publicPath: 'http://localhost:8080',
  },
  plugin: [
    // 保证错误堆栈信息及 sourcemap 行列信息正确
    new webpack.BannerPlugin({
      banner: 'Micro front-end',
    }),
  ],
  devServer: {
    // 保证在开发模式下应用端口不一样
    port: '8000',
    headers: {
      // 保证子应用的资源支持跨域,在上线后需要保证子应用的资源在主应用的环境中加载不会存在跨域问题(**也需要限制范围注意安全问题**)
      'Access-Control-Allow-Origin': '*',
    },
  },
};

【重要】注意:

  1. libraryTarget 需要配置成 umd 规范;
  2. globalObject 需要设置为 'window',以避免由于不规范的代码格式导致的逃逸沙箱;
  3. 如果你的 webpack 为 v4 版本,需要设置 jsonpFunction 并保证该值唯一(否则可能出现 webpack chunk 互相影响的可能)。若为 webpack5 将会直接使用 package.json name 作为唯一值,请确保应用间的 name 各不相同;
  4. publicPath 设置为子应用资源的绝对地址,避免由于子应用的相对资源导致资源变为了主应用上的相对资源。这是因为主、子应用处于同一个文档流中,相对路径是相对于主应用而言的
  5. 'Access-Control-Allow-Origin': '*' 允许开发环境跨域,保证子应用的资源支持跨域。另外也需要保证在上线后子应用的资源在主应用的环境中加载不会存在跨域问题(也需要限制范围注意安全问题);

2.导出 provider 函数

针对子应用需要导出生命周期函数,我们提供了桥接函数 @garfish/bridge-react 自动包装应用的生命周期,使用@garfish/bridge-react 可以降低接入成本与用户出错概率,也是 garfish 推荐的子应用接入方式。

// 安装 @garfish/bridge-react:
npm install @garfish/bridge-react --save
使用 @garfish/bridge-react 接入
自定义导出函数
import { reactBridge } from '@garfish/bridge-react';

export const provider = reactBridge({
  el: '#root',
  rootComponent: RootComponent,
  errorBoundary: () => <Error />,
});

3. 设置应用路由 basename

// src/component/rootComponent
import React from "react";
import { BrowserRouter } from "react-router-dom";

const RootComponent = ({ basename }) => {
  return (
    <BrowserRouter basename={basename}>
      <Routes>
        <Route path="/" element={<App />}>
          <Route path="/home" element={<Home />} />
          <Route path="*" element={<PageNotFound />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

我们在 接入指南 章节详细中介绍了各框架的子应用接入 Garfish 的 demo 案例及接入过程注意事项,目前提供了:

  • react (version 16, 17, 18)
  • vue (version 2, 3)
  • vite (version 2)
  • angular (version 13)

可移步 接入指南 查看详细接入步骤。

总结

使用 Garfish API 搭建一套微前端主子应用的主要成本来自两方面

  • 主应用的搭建
    • 注册子应用的基本信息
    • 使用 Garfish 在主应用上调度管理子应用
  • 子应用的改造
    • 增加对应的构建配置
    • 使用 @garfish/bridge-react 包提供的函数包装子应用后返回 provider 函数并导出
    • 子应用针对不同的框架类型,添加不同 basename 的设置方式
      • React 在根组件中获取 basename 将其传递至 BrowserRouterbasename 属性中
      • Vue 将 basename 传递至 VueRouterbasename 属性中