Vite

警告

在正式版中接入vite子应用需要关闭沙箱,这会导致很多问题,暂时不建议接入。

在1.0公测版中我们推出了为vite打造的沙箱方案,对于新接入的项目,推荐使用新的方案,具体详情请跳转1.0公测版文档在新窗口打开

作为基座应用

vite作为基座应用时没有特殊之处,具体方式参考各框架接入文档。

作为子应用

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

在嵌入vite子应用时,micro-app的功能只负责渲染,其它的行为由应用自行决定,这包括如何防止样式、JS变量、元素的冲突。

在module模式下,引入的资源大多为相对地址,兼容主要做的事情就是将地址补全。

👇 子应用的修改

1、修改vite.config.js

import { join } from 'path'
import { writeFileSync } from 'fs'

// vite.config.js
export default defineConfig({
  base: `${process.env.NODE_ENV === 'production' ? 'http://my-site.com' : ''}/basename/`,
  plugins: [
    // 自定义插件
    (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)
              }
            }
          }
        },
      }
    })(),
  ],
})

2、修改容器元素id

因为vite子应用没有元素隔离的保护,建议修改容器元素的id值,以确保与其它元素不冲突。

1、修改index.html中容器元素的id值

<!-- index.html -->
<body>
  <div id="my-vite-app"></div>
</body>

2、使用新的id渲染

// main.js
createApp(App).mount('#my-vite-app')

当多个vite子应用同时渲染时,必须修改容器元素的id值,确保每个子应用容器元素id的唯一性,否则无法正常渲染。

3、路由

推荐基座使用history路由,vite子应用使用hash路由,避免一些可能出现的问题。

子应用如果是vue3,在初始化时路由时,createWebHashHistory不要传入参数,如下:

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

const router = createRouter({
  history: createWebHashHistory(),
  routes,
})

4、静态资源

图片等静态资源需要使用绝对地址,可以使用 new URL('../assets/logo.png', import.meta.url).href 等方式获取资源的全链接地址。

👇 基座应用的修改

注意这里的基座应用是指嵌入了vite子应用的基座,它可以是任何框架,和上面作为基座应用一节无关。

1、关闭沙箱并使用内联script模式

<micro-app
  name='child-name'
  url='http://localhost:3001/basename/'
  inline // 使用内联script模式
  disableSandbox // 关闭沙箱
>

2、处理子应用静态资源

写一个简易的插件,对开发环境的子应用进行处理,补全静态资源路径。

import microApp from '@micro-zoe/micro-app'

microApp.start({
  plugins: {
    modules: {
      // appName即应用的name值
      appName: [{
        loader(code) {
          if (process.env.NODE_ENV === 'development') {
            // 这里 basename 需要和子应用vite.config.js中base的配置保持一致
            code = code.replace(/(from|import)(\s*['"])(\/basename\/)/g, all => {
              return all.replace('/basename/', '子应用域名/basename/')
            })
          }

          return code
        }
      }]
    }
  }
})

👇 数据通信

沙箱关闭后,子应用默认的通信功能失效,此时可以通过手动注册通信对象实现一致的功能。

注册方式:在基座应用中为子应用初始化通信对象

import { EventCenterForMicroApp } from '@micro-zoe/micro-app'

// 注意:每个vite子应用根据appName单独分配一个通信对象
window.eventCenterForViteApp1 = new EventCenterForMicroApp(appName)

vite子应用就可以通过注册的eventCenterForViteApp1对象进行通信,其api和window.microApp一致,基座通信方式没有任何变化。

子应用通信方式:

/**
 * 绑定监听函数
 * dataListener: 绑定函数
 * autoTrigger: 在初次绑定监听函数时有缓存数据,是否需要主动触发一次,默认为false
 */
window.eventCenterForViteApp1.addDataListener(dataListener: (data: Object) => void, autoTrigger?: boolean)

// 解绑指定函数
window.eventCenterForViteApp1.removeDataListener(dataListener)

// 清空当前子应用的所有绑定函数(全局数据函数除外)
window.eventCenterForViteApp1.clearDataListener()

// 主动获取数据
window.eventCenterForViteApp1.getData()

// 子应用向基座应用发送数据
window.eventCenterForViteApp1.dispatch({type: '子应用发送的数据'})

注意

  1. 请确保vite版本>=2.5.0
  2. 适配vite本质上是适配module脚本,其它非vite构建的module脚本也可以采用相同的思路处理。

实战案例

以上介绍了vite如何接入微前端,但在实际使用中会涉及更多功能,如数据通信、路由跳转、打包部署,为此我们提供了一套案例,用于展示vite作为基座嵌入(或作为子应用被嵌入) react、vue、angular、vite、nextjs、nuxtjs等框架,在案例中我们使用尽可能少的代码实现尽可能多的功能。

案例地址:https://github.com/micro-zoe/micro-app-demo-0.x