vue 子应用
本节我们将详细介绍 vue 框架的应用作为子应用的接入步骤。v2 demo、v3 demo
vue 子应用接入步骤
1. bridge 依赖安装
提示
- 请注意,桥接函数的安装不是必须的,你可以自定义导出函数。
- 我们提供桥接函数是为了进一步降低用户接入成本并降低用户出错概率,桥接函数中将会内置一些默认行为,可以避免由于接入不规范导致的错误,所以这也是我们推荐的接入方式。
- 我们分别为 vue 2、3 应用提供不同的 bridge 包,目的是为了更好的类型提示及精简参数。
- vue2 应用
- vue3 应用
- npm
- Yarn
npm install @garfish/bridge-vue-v2 --save
yarn add @garfish/bridge-vue-v2
- npm
- Yarn
npm install @garfish/bridge-vue-v3 --save
yarn add @garfish/bridge-vue-v3
2. 入口文件处导出 provider 函数
更多 bridge 函数参数介绍请参考 这里
vue2 导出
- 使用 @garfish/bridge-vue-v2 导出
- 自定义导出
import Vue from 'vue';
import VueRouter from 'vue-router';
import store from './store';
import App from './App.vue';
import Home from './components/Home.vue';
import { vueBridge } from '@garfish/bridge-vue-v2';
Vue.use(VueRouter);
Vue.config.productionTip = false;
function newRouter(basename) {
const router = new VueRouter({
mode: 'history',
base: basename,
routes: [
{ path: '/home', component: Home },
],
});
return router;
}
export const provider = vueBridge({
// 根组件
rootComponent: App,
// 可选,注册 vue-router或状态管理对象
appOptions: ({ basename, dom, appName, props, appInfo }) => {
// pass the options to Vue Constructor. check https://vuejs.bootcss.com/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE
return {
el: '#app',
router: newRouter(basename),
store,
};
},
});
import Vue from 'vue';
import App from './App.vue';
import store from './store';
import VueRouter from 'vue-router';
import HelloWorld from './components/HelloWorld.vue';
Vue.use(VueRouter);
Vue.config.productionTip = false;
const render = ({ dom, basename = '/' }) => {
const router = new VueRouter({
mode: 'history',
base: basename,
router,
routes: [
{ path: '/', component: HelloWorld },
],
});
const vm = new Vue({
store,
render: (h) => h(App, { props: { basename } }),
}).$mount();
(dom || document).querySelector('#app').appendChild(vm.$el);
};
vue3 导出
- 使用 @garfish/bridge-vue-v3 导出
- 自定义导出
import { h, createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import { stateSymbol, createState } from './store.js';
import App from './App.vue';
import Home from './components/Home.vue';
import { vueBridge } from '@garfish/bridge-vue-v3';
const routes = [
{ path: '/home', component: Home },
];
function newRouter(basename) {
const router = createRouter({
history: createWebHistory(basename),
routes,
});
return router;
}
export const provider = vueBridge({
rootComponent: App,
// 可选,注册 vue-router或状态管理对象
handleInstance: (vueInstance, { basename, dom, appName, props, appInfo}) => {
vueInstance.use(newRouter(basename));
vueInstance.provide(stateSymbol, createState());
},
});
import { h, createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import { stateSymbol, createState } from './store.js';
import App from './App.vue';
import HelloGarfish from './components/HelloGarfish.vue';
export function provider({ dom, basename }) {
let app = null;
return {
render() {
app = createApp(App);
app.provide(stateSymbol, createState());
const router = createRouter({
history: createWebHistory(basename),
base: basename,
routes: [{ path: '/home', component: HelloGarfish }]
});
app.use(router);
app.mount(
dom ? dom.querySelector('#app') : document.querySelector('#app'),
);
},
destroy() {
if (app) {
app.unmount(
dom ? dom.querySelector('#app') : document.querySelector('#app'),
);
}
},
};
}
3. 根组件设置路由的 basename
信息
- vue2
- vue3
import Vue from 'vue';
import VueRouter from 'vue-router';
import store from './store';
import App from './App.vue';
import Home from './components/Home.vue';
import { vueBridge } from '@garfish/bridge-vue-v2';
Vue.use(VueRouter);
Vue.config.productionTip = false;
function newRouter(basename) {
const router = new VueRouter({
mode: 'history',
base: basename,
routes: [
{ path: '/home', component: Home },
],
});
return router;
}
import { h, createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import { stateSymbol, createState } from './store.js';
import App from './App.vue';
import Home from './components/Home.vue';
import { vueBridge } from '@garfish/bridge-vue-v3';
const routes = [
{ path: '/home', component: Home },
];
function newRouter(basename) {
const router = createRouter({
history: createWebHistory(basename),
base: basename,
routes,
});
return router;
}
4. 更改 webpack 配置
// webpack.config.js
const webpack = require('webpack');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
output: {
// 开发环境设置 true 将会导致热更新失效
clean: isDevelopment ? false : true,
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
// 需要配置成 umd 规范
libraryTarget: 'umd',
// 修改不规范的代码格式,避免逃逸沙箱
globalObject: 'window',
// webpack5 使用 chunkLoadingGlobal 代替,或不填保证 package.json name 唯一即可
jsonpFunction: 'garfish-demo-react',
// 保证子应用的资源路径变为绝对路径
publicPath: 'http://localhost:8080',
},
plugin: [
// 保证错误堆栈信息及 sourcemap 行列信息正确
new webpack.BannerPlugin({
banner: 'Micro front-end',
}),
],
devServer: {
// 保证在开发模式下应用端口不一样
port: '8000',
headers: {
// 保证子应用的资源支持跨域,在上线后需要保证子应用的资源在主应用的环境中加载不会存在跨域问题(**也需要限制范围注意安全问题**)
'Access-Control-Allow-Origin': '*',
},
},
};
【重要】注意:
- libraryTarget 需要配置成 umd 规范;
- globalObject 需要设置为 'window',以避免由于不规范的代码格式导致的逃逸沙箱;
- 如果你的 webpack 为 v4 版本,需要设置 jsonpFunction 并保证该值唯一(否则可能出现 webpack chunk 互相影响的可能)。若为 webpack5 将会直接使用 package.json name 作为唯一值,请确保应用间的 name 各不相同;
- publicPath 设置为子应用资源的绝对地址,避免由于子应用的相对资源导致资源变为了主应用上的相对资源。这是因为主、子应用处于同一个文档流中,相对路径是相对于主应用而言的
- 'Access-Control-Allow-Origin': '*' 允许开发环境跨域,保证子应用的资源支持跨域。另外也需要保证在上线后子应用的资源在主应用的环境中加载不会存在跨域问题(也需要限制范围注意安全问题); :::
5. 增加子应用独立运行兼容逻辑
提示
last but not least, 别忘了添加子应用独立运行逻辑,这能够让你的子应用脱离主应用独立运行,便于后续开发和部署。
- vue2
- vue3
// src/main.js
import Vue from 'vue';
import VueRouter from 'vue-router';
// 这能够让子应用独立运行起来,以保证后续子应用能脱离主应用独立运行,方便调试、开发
if (!window.__GARFISH__) {
const router = new VueRouter({
mode: 'history',
base: '/',
routes: [
{ path: '/home', component: Home },
],
});
new Vue({
store,
router,
render: (h) => h(App),
}).$mount('#app');
}
// src/main.js
import { h, createApp } from 'vue';
import VueRouter from 'vue-router';
// 这能够让子应用独立运行起来,以保证后续子应用能脱离主应用独立运行,方便调试、开发
if (!window.__GARFISH__) {
const router = new VueRouter({
mode: 'history',
base: '/',
routes: [
{ path: '/home', component: Home },
],
});
const app = createApp(App);
app.provide(stateSymbol, createState());
app.use(router);
app.mount('#app');
}