使用Single-spa集成Vue2、Vue3、React

2023-09-02 10:13:08 浏览数 (2)

导言

引用Single-spa文档的描述:

Single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。使用 single-spa 进行前端架构设计可以带来很多好处,例如:

在同一页面上使用多个前端框架 而不用刷新页面 (React, AngularJS, Angular, Ember, 你正在使用的框架) 独立部署每一个单页面应用 新功能使用新框架,旧的单页应用不用重写可以共存 改善初始加载时间,延迟加载代码

开始使用Single-spa搭建项目

  1. 基座应用(Vue)
  2. 子应用react-app(react17)
  3. 子应用vue2-app(vue2)
  4. 子应用vue3-app(vue3)

以上应该局可以通过cli工具搭建(vue-cli、create-react-app)

基座应用

1. 添加依赖
代码语言:javascript复制
npm i single-spa

引入systemjs(用于加载依赖及子应用js)

代码语言:javascript复制
<!-- index.html -->
      <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/amd.js"></script>
      <script type="systemjs-importmap">
          {
            "imports": {
                 "@single-spa/react-app": "http://localhost:8080/react-app-react-app.js",
                  "@single-spa/vue3-app": "http://localhost:8001/js/app.js",
                  "@single-spa/vue2-app": "http://localhost:8002/js/app.js",
                  "react": "https://cdn.jsdelivr.net/npm/react@17.0.2/umd/react.production.min.js",
                  "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.2/umd/react-dom.production.min.js"
            }
          }
       </script>
2. 注册子应用(react)
  • 主应用配置

采用全局注册,路由匹配形式

代码语言:javascript复制
// main.js
import {
 registerApplication,
 start
} from 'single-spa'

registerApplication(
 'react-app',
 // eslint-disable-next-line no-undef
 () => System.import("@single-spa/react-app"),
 location => location.hash.startsWith('#/react-app'),
 {
  domElementGetter: function () {
   // 此处用于异步返回挂在街节点,如果不返回默认在body下新增节点挂载
   return document.getElementById('react-app')
  }
 }
)
start({ urlRerouteOnly: true, })
代码语言:javascript复制
<template>
<!-- 在组件内指定挂载节点 -->
	<div id="react-app"></div>
</template>
  • 子应用(microapps/react-app)配置

修改打包配置指定输出文件名

代码语言:javascript复制
// webpack.config.js
const { merge } = require("webpack-merge");
const singleSpaDefaults = require("webpack-config-single-spa-react");

module.exports = (webpackConfigEnv, argv) => {
  const defaultConfig = singleSpaDefaults({
    orgName: "react-app",
    projectName: "react-app",
    webpackConfigEnv,
    argv,
  });

  return merge(defaultConfig, {
    // modify the webpack config however you'd like to by adding to this object
  });
};

初始化子应用

代码语言:javascript复制
import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    return null;
  },
});

export const { bootstrap, mount, unmount } = lifecycles;

3. 注册子应用(vue2)

采用parcel形式接入

  • 主应用
代码语言:javascript复制
<template>
	<Parcel @parcelMounted="parcelMounted"
		@parcelUpdated="parcelUpdated"
		@parcelUnmount="parcelUnMounted"
		:config="parcelConfig"
		:mountParcel="mountRootParcel"
		wrapWith="div"
		wrapClass="vue-child-app-load"
		:parcelProps="getParcelProps()" />
</template>

<script setup>
	import Parcel from 'single-spa-vue/parcel'
	import { mountRootParcel } from 'single-spa'
	import { computed } from 'vue'
	// eslint-disable-next-line no-undef
	const parcelConfig = computed(() => System.import("@single-spa/vue2-app"))
	const getParcelProps = () => {
		// props some context to child app
		return {
			foo: { token: 'some thing' }
		}
	}
	const parcelMounted = () => {
		console.log("parcel mounted");
	}
	const parcelUpdated = () => {
		console.log("parcel updated");
	};
	const parcelUnMounted = () => {
		console.log("parcel parcelUnMounted");
	};
</script>

因为在主应用中引入了systemjs,且使用importmap映射了依赖名称,则可以使用window下的System直接通过map名称加载文件。这也是single-spa实现依赖共享的主要形式。

  • 子应用(microapps/vue2-app),修改打包配置指定输出文件名
代码语言:javascript复制
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack: {
    output: {
      libraryTarget: "system",
      filename: "js/app.js",
    },
  },
  devServer: {
    port: 8002
  }
})

初始化生命周期

代码语言:javascript复制
const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: {
    render(h) {
      return h(App, {
        props: {
          // single-spa props are available on the "this" object. Forward them to your component as needed.
          // https://single-spa.js.org/docs/building-applications#lifecycle-props
          // if you uncomment these, remember to add matching prop definitions for them in your App.vue file.
          /*
          name: this.name,
          mountParcel: this.mountParcel,
          singleSpa: this.singleSpa,
          */
        },
      });
    },
  },
});

export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;


4. 注册子应用(vue3)

(microapps/vue3-app)

因为该子应用和主应用使用相同技术栈,除了上面vue2-app的引入形式,其实可以采用通过alias、或者link(npm、yarn)的形式在编译阶段就接入

代码语言:javascript复制
// vue.config.js
module.exports = defineConfig({
configureWebpack: {
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "src"),
        "microapps": path.resolve(__dirname, "microapps"),
      }
    }
  }
})

挂载时机既可以使用single-spa-vue/parcel形式挂载

代码语言:javascript复制
<template>
	<Parcel @parcelMounted="parcelMounted"
		@parcelUpdated="parcelUpdated"
		@parcelUnmount="parcelUnMounted"
		:config="parcelConfig"
		:mountParcel="mountRootParcel"
		wrapWith="div"
		wrapClass="vue-child-app-load"
		:parcelProps="getParcelProps()" />
</template>

<script setup>
	import Parcel from 'single-spa-vue/parcel'
	import { mountRootParcel } from 'single-spa'
	import { computed } from 'vue'
	const parcelConfig = computed(() => import('microapps/vue3-app/src/singleSpaApp'))
	const getParcelProps = () => {
		// props some context to child app
		return {
			foo: { token: 'some thing' }
		}
	}
	const parcelMounted = () => {
		console.log("parcel mounted");
	}
	const parcelUpdated = () => {
		console.log("parcel updated");
	};
	const parcelUnMounted = () => {
		console.log("parcel parcelUnMounted");
	};
</script>

也可以使用import直接引入使用:

代码语言:javascript复制
<template>
	<App />
</template>

<script setup>
	import App from "microapps/vue3-app/src/App"
</script>

详细代码见 https://github.com/wenjuGao/microapp-examples

0 人点赞