Skip to content

浅玩微前端

前言

这里不会大篇幅讲述微前端是啥,也没有很深入的知识点,只是一篇简单试玩微前端的文章,可能看完后什么都没学到,嘻嘻🙃🙃🙃。

  1. 什么是微前端?

类似微服务的概念,具有多个独立发布功能的web应用技术。详情链接

  1. 微前端的特点?
  • 与技术栈无关
  • 微应用可独立部署
  • 每个微应用状态隔离
  1. 提出微前端概念的缘由?

当一个单体应用经过长时间的迭代、人员的变动,很可能变成一个巨大且难以维护的应用;而微前端架构旨在把巨石单体应用可拆为多个可独立部署的微应用,可组合使用,也可单体部署使用,可使各团队更加专注于自己的模块。

常见使用于 ToB 应用。

或许你遇到过这样的一个场景:你在网页上使用 iframe 嵌套了另一个网站,使当前网站使用 额外的功能,而被嵌套的网站,也是单独部署的。

可以把它理解为微前端的一种场景,但只是最终微前端架构都没有采用 iframe 来实现而已。为什么不使用iframe?

有兴趣可以阅读一下这两篇文章👇

Demo

demo 案例主要是使用 qiankun 阿里的一套微前端方案,有在 umi 中使用 和 在搭建的 webpack 中使用,其实都差不多😄。

qiankun 微前端方案个人理解

主程序负责中介者的角色,负责管理所有子应用,将子应用渲染到对应的dom。子应用是通过 html emtry 进来,因为 html 是一个天然的沙盒,可以隔离不同应用之间的变量或样式。

首先是主程序管理所有子应用的父路由,当匹配到某个路由时,加载子应用然后再把路由交给子应用去匹配。

子应用的打包方式原理是利用 打包插件库的形式,主页面类似以插件的形式加载子应用。

umi 中使用 qiankun

Umi 3 版本需要安装 @umijs/plugin-qiankun 插件,最新版 Umi 4 已经集成在 @umijs/max 中了

  1. 主应用安装
bash
$ mkdir micro-web-demo && cd micro-web-demo
$ yarn create @umijs/umi-app
$ yarn add @umijs/plugin-qiankun -D
  1. 主应用配置
javascript
// .umirc.ts
export default {
  qiankun: {
    master: {
      // 注册子应用信息
      apps: [
        {
          name: 'app1', // 子应用名,唯一
          entry: '//localhost:3000', // 入口文件
        },
        {
          name: 'app2', // 唯一 id
          entry: '//localhost:8080',
        },
      ],
    },
  },
}
javascript
// config/routes.ts
// 这里使用自定义了 layout 和 将 .umirc.js 中抽出来
export default [
  { 
    path: '/', 
    name: '主页',
    component: '@/layouts/index', 
    routes: [
      {
        name: 'app1',
        path: '/app1',
        microApp: 'app1'
      },
      {
        name: 'app2',
        path: '/app2',
        microApp: 'app2'
      }
    ] 
  },
]
tsx
// src/layouts/index.tsx
import React, { useState } from 'react'
import { PageContainer, ProLayout } from '@ant-design/pro-components'
import { Link } from 'umi'
import routes from '../../config/routes'

interface Props {
  children?: React.ReactNode
}

const Layout = (props: Props) => {
  const { children } = props

  return <ProLayout
    style={{ height: '100vh' }}
    route={{
      routes: routes
    }}
    menuItemRender={(item) => {
      return <Link
        to={item.path}
      >
        {item.name}
      </Link>
    }}
  >
    <PageContainer>
      {children}
    </PageContainer>
  </ProLayout>
}

export default Layout
  1. 子应用安装
bash
# 这里使用了两种方式创建的子应用,1. create-react-app 2.vue-cli, 都是使用了最新的版本。
$ npx create-react-app micro-app-react
$ vue create micro-app-vue
  1. 子应用的配置可以查看 qiankun 项目实践 进行操作,已经写得很详细了。

⚠️ 注意点

因为 create-react-app 默认创建的 react 应用使用了最新的 版本,文档中有一些配置已经弃用或会报警告。react 18 版本更新日志

javascript
// src/index.js
// 这里 只展示需要修改 的代码
import ReactDOM from 'react-dom/client'

let root = null
function render(props) {
  const { container } = props
  // react 18 的新变化
  root = ReactDOM.createRoot(
    container
      ? container.querySelector('#root')
      : document.querySelector('#root')
  )
  root.render(<App />)
}
              
export async function unmount(props) {
  // 卸载,ReactDOM.unmountComponentAtNode 已弃用
  root.unmount()
}

文档中使用了 @rescripts/cli , 它好像只支持到 16.8 版本,建议使用 react-app-rewired,配置如下👇

javascript
// config-overrides.js
const { name } = require('./package')

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`
    config.output.libraryTarget = 'umd'
    // webpack 5 jsonpFunction 改为 chunkLoadingGlobal
    config.output.chunkLoadingGlobal = `webpackJsonp_${name}`
    config.output.globalObject = 'window'

    return config
  },

  devServer: (configFunction) => {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost)

      config.headers = {
        'Access-Control-Allow-Origin': '*',
      }
      config.historyApiFallback = true
      config.hot = false
      // config.watchContentBase = false 废弃
      config.liveReload = false

      return config
    }
  },
}

同理 vue cli 4 默认创建的 vue 应用也是最新的版本,vue 3 更新日志

javascript
// src/main.js
function render(props = {}) {
  const { container } = props
  // 这里不是不行,只是简单使用,没有用到 vue router 就暂时注释了
  // router = new VueRouter({
  //   base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
  //   mode: 'history',
  //   routes,
  // });
  
  // 这里才是要改的 
  instance = createApp(App)
  instance.mount(
    container ? container.querySelector('#app') : '#app'
  )
}

export async function unmount() {
  // 卸载
  instance.unmount()
  // qiankun 文档使用的是 $el, 因为 vue3 改了创建应用的方法,因此不能使用 $el
  instance._container.innerHTML = ''
  instance = null
  // router = null
}

vue.config.js 中的 jsonpFunction 也需要改为 chunkLoadingGlobal

✴️ 提醒

public-path.js 须放在 entry.js 最上面。publicPath

最后,把各个应用跑起来就好啦 🎉🎉🎉

webpack 中使用 qiankun 可以看 完整案例代码中的 main-app-webpack ✈️

🔗完整案例代码

Released under the MIT License.