前言:

微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。

它主要解决了两个问题:

  • 1、随着项目迭代应用越来越庞大,难以维护。
  • 2、跨团队或跨部门协作开发项目导致效率低下的问题

 基于vite的实现:

首先创建一个主应用和一个子应用

yarn add @vitejs/app microapp_tool --template vue-ts

npm init @vitejs/app microapp_tool --template vue-ts

这是一个主应用

 

yarn add @vitejs/app spoon_tool --template vue-ts

npm init @vitejs/app spoon_tool --template vue-ts

这是一个子应用

 子应用:

router/index.ts


/**
 * @param  {Function}   ...
 */

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import cubetoolPath from "./modules/cube"

const routes: RouteRecordRaw[] = [
    { path: "/ceshi", name: "ceshi", component: () => import("@/views/output/ceshi.vue") },
    { path: '/host', name: "host", component: () => import("@/components/spoon/handsontable.vue") },
    { path: '/table', name: "table", component: () => import("@/components/spoon/EditableProTable.vue") },
    {
        path: '/cube', name: 'cube', component: () => import('@/views/output/cube.vue'),
        children: [...cubetoolPath]
    },
    {path:"/",redirect:"/cube"}
]

const options = {
    // 👇 设置基础路由,子应用可以通过window.__MICRO_APP_BASE_ROUTE__获取基座下发的baseroute,如果没有设置baseroute属性,则此值默认为空字符串
    history: createWebHashHistory(),
    routes,
}

const router = createRouter(options)

export default router


这里要注意的是:路由模式要用hash模式  即:history: createWebHashHistory()

vite.config.ts

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
//import hook reactive ref....
import AutoImport from "unplugin-auto-import/vite"
//import  ele-plus....
import Components from "unplugin-vue-components/vite"
//elementplus
import { ElementPlusResolver } from "unplugin-vue-components/resolvers"
//es6-modules 语法解析
import path from 'path'
import { resolve, join } from "path"
import { writeFileSync } from "fs"

export default (({ mode }) => {
  //检查process.cwd()路径下.env.development....
  loadEnv(mode, process.cwd())

  const { VITE_APP_BASE_API, VITE_BASE_API, VITE_ENV } = loadEnv(mode, process.cwd());


  // https://vitejs.dev/config/
  return defineConfig({
    base: `${VITE_ENV === 'production' ? 'http://my-site.com' : ''}/spoon_tool/`,//baseName 主应用所需
    plugins: [
      vue(),
      //ele 按需导入
      AutoImport({
        //ref 、reactive.....
        imports: ['vue', 'vue-router'],
        dts: "src/auto-imports.d.ts",
        // ele-plus
        resolvers: [ElementPlusResolver()]
      }),
      Components({
        // ele..
        resolvers: [ElementPlusResolver()]
      }),
      // 自定义插件  micro-app微前端 子应用 配置
      (function () {
        let basePath = ''
        return {
          name: "vite:micro-app",
          apply: 'build',
          configResolved(config) {
            basePath = `${config.base}${config.build.assetsDir}/`
          },
          writeBundle(options, bundle) {
            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, $1, $2, $3) => {
                    return all.replace($3, new URL($3, basePath))
                  })
                  const fullPath = join(options.dir, chunk.fileName)
                  writeFileSync(fullPath, chunk.code)
                }
              }
            }
          },
        }
      })(),
    ],
    resolve: {
      alias: {
        "@": resolve(__dirname, "src"),
        "@assets": resolve(__dirname, "src/assets"),
        "@store": resolve(__dirname, "src/store"),
        "@views": resolve(__dirname, "src/views")
      }
    },
    css: {
      preprocessorOptions: {
        less: {
          modifyVars: {
            hack: `true; @import (reference) "${path.resolve("src/assets/css/base.less")}";`,
          },
          javascriptEnabled: true,
        },
      },
    },
    server: {
      // port: 3000,
      // proxy: {
      //   [VITE_BASE_API]: {
      //     target: VITE_APP_BASE_API, // 实际请求地址
      //     changeOrigin: true,
      //     rewrite: (path) => path.replace(/^\/api/, ""),
      //   },
      // },
      port: 8081,//主应用所挂载的url端口
      headers: {
        'Access-Control-Allow-Origin': '*',//子应用必须开启跨域,否则无法访问
      }
    },
  })
})


这里主要注意的是跨域处理、端口号定义、自定义插件的应用

index.html

 <div id="my_vite_app"></div>

模板ID的修改,这里是为了不与主应用ID发生冲突,

当然,对应的main.ts也得保持一致

app.mount('#my_vite_app')

主应用:

yarn add @micro-zoe/micro-app

router/index.ts


/**
 * @param  {Function}   ...
 */

import { createRouter,createWebHistory, Router, RouteRecordRaw } from 'vue-router'
import MyPage from '../views/spoon_tool.vue'

const routes: RouteRecordRaw[] = [
    {
        //严格模式
        path: "/spoon_tool/:page*",
        name: "spoon_tool",
        component: MyPage
    }
]

const options = {
    // 👇 设置基础路由,子应用可以通过window.__MICRO_APP_BASE_ROUTE__获取基座下发的baseroute,如果没有设置baseroute属性,则此值默认为空字符串
    history: createWebHistory(),
    // base: process.env.BASE_URL,
    routes,
}

const router: Router = createRouter(options)

export default router


这里要注意的是所需的path为子应用的baseName值,其路由模式要为history模式

views/spoon_tool.vue

<!-- my-page.vue -->
<template>
    <div class="base">
        <!-- 
        name(必传):应用名称
        url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
        baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`
       -->
        <micro-app class="micro-app" disableScopecss disableSandbox inline style="height: 100%;" name='spoon_tool' url='http://localhost:8081/spoon_tool/#/cube' baseroute='/my-page'></micro-app>
    </div>
</template>
  <style lang="less" scoped>
    .base{
        width: 100%;
        height: 100%;
        // background: red;
        display: flex;
        flex-direction: column;
        h1{
            background: pink;

        }
        .micro-app{
            flex: 1;
        }
    }
  </style>

这里的micro-app标签中对应子应用的url

main.ts

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router/index"
// import "./public-path"
import ElementPlus from "element-plus"
import "element-plus/theme-chalk/index.css"

// 鼠标右键
import contextmenu from "v-contextmenu";
import "v-contextmenu/dist/themes/default.css";

//ele icon
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

//pinia
import pinia from "./store/index"

//i18n多语言配置
import i18n from "./lang/index"

//sql
// import Codemirror from "codemirror-editor-vue3"
// import Codemirror from "codemirror-editor-vue3";

import microApp from "@micro-zoe/micro-app"
microApp.start({
    plugins: {
        modules: {
            // appName即应用的name值
            'spoon_tool': [{
                loader(code) {
                    if (import.meta.env.VITE_ENV === 'development') {
                        // 这里 basename 需要和子应用vite.config.js中base的配置保持一致
                        code = code.replace(/(from|import)(\s*['"])(\/spoon_tool\/)/g, all => {
                            return all.replace('/spoon_tool/', 'http://localhost:8081/spoon_tool/')
                        })
                    }
                    return code
                }
            }]
        }
    }
})

/**
 * @params 解决Chrome报错 Added non-passive event listener to a scroll-blocking <some> event. Consider marking event handler as ‘passive’ to make the page more responsive. See <URL>
 * @Desc 通过添加 passive,来阻止 touchstart 事件 提高滚动性能和防止滚动阻塞
 */
// import "default-passive-events"

const app = createApp(App)

//导入所有图标进行全局注册
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}


app.use(router)
app.use(ElementPlus)
app.use(pinia)
app.use(i18n)
app.use(contextmenu)
// app.use(Codemirror)
app.mount('#app')

// 监听卸载操作
window.addEventListener('unmount', function () {
    app.unmount()
})

microApp 的引入与使用

效果:

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐