微前端

9,252 阅读3分钟

wallhaven-l8327y (1).png

什么是微前端

微前端,听这名字感觉很超前,其实已经有很多公司都已经用上了该技术,例如我的项目组也用上了,跟后端的微服务很类似,用作解耦。现在的网站开发日渐复杂,通常会有很多独立的模块组成,分成不同的业务组,在之前用的是iframe 进行嵌套。随着技术的发展现在出现了微前端的概念,目前微前端框架有很多,如single-spa qiankun(阿里) Micro App(京东) wujie(腾讯) 擎天(vivo)

无界官网

micro-app官网

乾坤官网

在微前端的架构下,需要一个主应用,也可以叫基座应用,其次每一个独立的模块都可以叫做子应用,子应用和主应用不受框架的限制,可以用任何框架任何技术。

举个栗子

image.png

在这张图我们可以把菜单和头部看做是一个主应用,然后菜单中的每一个模块都可以看做一个子应用(也就是每一个单独的项目),这样就可以就行解耦,并且应用之间是可以进行通讯的。

这一章我们主要体验一下micro-app支持一下自家产品。他的原理也很简单,就是把子应用做成一个webComponents 进行沙箱隔离,应用之间的通讯用的是CustomEvent

开整

在开整之前得吐槽一下,在github的issues提的很多问题,得不到及时回复,平时忙也能理解,如果使用vite将变的比较复杂,做好准备。

tips:当子应用是vite应用时需要做特别的适配,适配vite的代价是巨大的,我们必须关闭沙箱功能,因为沙箱在module script下不支持,这导致大部分功能失效,包括:环境变量、样式隔离、元素隔离、资源路径补全、baseroute 等。

  1. 框架我这边主子应用使用vite+vue3,大家可以随意选择框架。

  2. 目录结构mfe->主应用,web放子应用,子应用可以有多个第一个是main

image.png

3.主子应用安装依赖npm install 然后 启动主应用,和子应用使用 npm run dev。启动完成之后我这边主应用端口是5173,子应用是5174,当然也可以自己配置不要冲突就行。

4.主应用安装micro-app

npm install  @micro-zoe/micro-app

5.主应用在views新建一个文件如views/page.vue子应用修改vite.config.ts主子应用不要搞错, 子应用的vite.config.ts 增加代码

tips:如果是production记得换成服务器的地址,如果是本地增加一个自定义前缀随便写我这儿写的basename

export default defineConfig({
    base: `${process.env.NODE_ENV === 'production' ? 'http://xxxxx.com' : ''}/basename/`,
    plugins:[vue()]
})

6.在刚才主应用新建的page.vue 加入以下代码

<template>
    <div>
        <h1>子应用</h1>
        <micro-app disable-sandbox disable-scopecss name='main' :url='url'></micro-app>
    </div>
</template>

<script lang="ts" setup>
import { ref, reactive } from "vue"
let url = ref('')

if (import.meta.env.MODE == "development") {
   
    url.value = "http://localhost:5174/basename"
} else {
    url.value = "http://xxxxxxxx/basename"
}
</script>

<style lang="less" scoped>
</style>

解释一下 micro-app 是个自定义组件可以直接用,如果报错可以配置以下代码忽略警告

image.png

主应用的vite.config.ts 修改

vue({
    template: {
        compilerOptions: {
            isCustomElement: tag => /^micro-app/.test(tag)
        }
    }
})

image.png

其次是必须要设置 disable-scopecss 这个属性取消沙箱,不然打包之后会有乱码,也就是必须得放弃沙箱。

url 就是子应用的地址,本地的地址和服务器地址做个切换。

7.主应用配置路由 主应用使用history模式,子应用换成hash模式createWebHashHistory

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/vue3:page*', //自定义地址
      component: ()=> import('@/views/page.vue') //刚才主应用创建的page.vue
    },
  ]
})

export default router

8.主应用加载子应用 main.ts

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import microApp from '@micro-zoe/micro-app'

const app = createApp(App)

app.use(router)

app.mount('#mfe')

microApp.start({
    plugins:{
        modules:{
            main: [{
                loader(code) {
                  if (import.meta.env.MODE === 'development') {
                    // 这里 basename 需要和子应用vite.config.js中base的配置保持一致
                    code = code.replace(/(from|import)(\s*['"])(\/basename\/)/g, all => {
                      return all.replace('/basename/', 'http://localhost:5174/basename/')
                    })
                  }
        
                  return code
                }
              }]
        }
    }
})

固定写法,有额外的子应用只需要改一下code.replace正则和all.replace第一个参数,以及第二个参数对应的地址就好,其他不用动

tips:注意modules下面的名字需要跟micro-app 的name对应 我这儿都是main

tips:主子应用不能使用同一个#App名字 我这儿主应用的名称换成了mfe,app.mount('#mfe')

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>微前端</title>
  </head>
  <body>
     <!--名字要变-->
    <div id="mfe"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

访问主应用你会发现子应用已经出来了

tips:注意我得路由写的是/vue3

image.png

坑来了:你的图片如果加载不出来,加载静态资源的方式变了

//js获取
const logo = new URL('./assets/logo.png',import.meta.url).href
//template使用
 <img alt="Vue logo" class="logo" :src="logo" width="125" height="125" />

如果还出不来,我也遇到了 需要把vite的版本降级为3.0.0方可好使

上线

打包主应用npm run build 记得换成服务器地址之前提到的

子应用需要修改vite.config.ts

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import {join} from 'path'
import {writeFileSync} from 'fs'



// https://vitejs.dev/config/
export default defineConfig({
  base: `${process.env.NODE_ENV === 'production' ? '上线地址' : ''}/basename/`,
  plugins: [vue(), vueJsx(),(function () {
    let basePath = ''
    return {
      name: "vite:micro-app",
      apply: 'build',
      configResolved(config:any) {
        basePath = `${config.base}${config.build.assetsDir}/`
      },
      writeBundle (options:any, bundle:any) {
        for (const chunkName in bundle) {
          if (Object.prototype.hasOwnProperty.call(bundle, chunkName)) {
            const chunk = bundle[chunkName]
            if (chunk.fileName && chunk.fileName.endsWith('.js')) {
              chunk.code = chunk.code.replace(/(from|import\()(\s*['"])(\.\.?\/)/g, (all:any, $1:any, $2:any, $3:any) => {
                return all.replace($3, new URL($3, basePath))
              })
              const fullPath = join(options.dir, chunk.fileName)
              writeFileSync(fullPath, chunk.code)
            }
          }
        }
      },
    }
  })() as any],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  build:{
    outDir:"../basename"
  }
})

解释一下 这是micro-app写的一个vite插件主要是子应用支持微前端做的直接贴进去就行了

outDir打包之后我输出到上层目录这个随意

子应用然后进行打包 npm run build

服务器我用的是宝塔方便

目录结构 主应用放到最外面,子应用直接把文件夹拖进去我得是basename

image.png

修改nginx代理

image.png

    location /{
     add_header Access-Control-Allow-Origin *;
      if ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg|eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ ){
      add_header Cache-Control max-age=7776000;
      add_header Access-Control-Allow-Origin *;
    }
     try_files $uri $uri/ /index.html;
    }
    
    location /basename {
      add_header Access-Control-Allow-Origin *;
    if ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg|eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ ){
      add_header Cache-Control max-age=7776000;
      add_header Access-Control-Allow-Origin *;
     }
      try_files $uri $uri/ /basename/index.html;
     }

上线预览

image.png