主要内容
目的:为了将一个大型项目拆分为多个子项目.通俗来说,就是vue项目中,web1为导航栏,web2为门户,web3为系统管理,通过微前端将他们糅合成一个完成的项目.
qiankun.js介绍
qiankun.js是当前最出色的一款微前端实现库, 他帮我们实现了css隔离、js隔离、项目关联等功能
# 安装qiankun.js
$ npm i qiankun -S
1. 项目主目录结构
共三个vue项目,container属于项目导航部分,web1为门户部分,web2为系统管理部分,container项目中有个subapp文件夹,用来存在web1和web2项目(subapp文件夹也可以放外层)。
1.1 结构一:
1.2 结构二:
2. 安装qiankun后配置项目加载规则
2.1 在container的app.vue中
<template>
<div id="nav">
<!-- 定义导航栏,且激活地址 -->
<router-link to="/">Home</router-link> |
<router-link to="/w1">web1</router-link> |
<router-link to="/w2">web2</router-link>
<!-- 新增id为box的元素,将web1中的内容插入到box中 子容器 -->
<div id="box"></div>
<!-- container(父容器) -->
<router-view />
</div>
</template>
2.2 修改home.vue中的内容
<template>
<div class="home">我是 container 工程</div>
</template>
此时界面如下:
2.3 配置container的main.js
// 引入qiankun.js
import { registerMicroApps, start } from 'qiankun';
// 注册子应用
registerMicroApps([
{
name: 'vueApp2',
entry: '//localhost:8083',
container: '#box',
activeRule: '/w2',
},
{
name: 'vueApp1',
entry: '//localhost:8082',
container: '#box',
activeRule: '/w1',
},
]);
// 开启服务
start();
参数解析:
registerMicroApps(apps, lifeCycles?)
apps-Array- 微应用的注册信息
name-String- 微应用的名称,必须确保唯一 - 【必选】entry-String- 微应用的入口 - 【必选】container-String | HTMLElement- 微应用的容器节点的选择器或者Element实例 - 【必选】acticeRule-String | (location:Location) => boolean | Array | (location:Location) => boolean- 微应用的激活规则 - 【必选】
3. 在subapp中新建子项目
3.1 新建子项目
# 跳转到subapp目录下
$ cd subapp
# 初始化web1
$ vue init webpack web1
# 初始化web2
$ vue init webpack web2
3.2 配置web1项目,web2同理配置
3.2.1 配置web1的main.js
在main.js导出自己的生命周期函数
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
Vue.config.productionTip = false;
let instance = null;
function render() {
instance = new Vue({
router,
render: h => h(App)
}).$mount('#web1') // 框架会拿到完整的dom结构, 所以index.html里面的id也要改一下
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('bootstrap');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount() {
console.log('bootstrap mount');
render()
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log('bootstrap unmount');
instance.$destroy()
}
3.2.2 web1中的index.html中修改
index.html为打包模版,因为有多个子应用,所以id不唯一,将div中id改为web1。
3.2.3 web1的App.vue修改
<template>
<div id="web1">Welcome Web1 page!</div>
</template>
3.2.4 web1 -> config -> index.js中修改端口号
这里修改后的端口号和主应用中配置的微服务的端口号对应。
module.exports = {
dev: {
port:'8082' // web2 改为 8083
}
}
4. 运行项目
4.1 单独启动
# contianer,web1,web2顶层目录下启动
$ npm run dev
4.2 一起启动
4.2.1 安装npm-run-all
单独依次启动应用比较麻烦,可以统一启动,安装npm-run-all
$ npm i npm-run-all --save-dev
4.2.2 配置contianer下的package.json
"scripts": {
"serve": "npm-run-all --parallel serve:*",
"serve:box": "vue-cli-service serve",
"serve:web1": "cd subapp/web1 && yarn serve",
"serve:web2": "cd subapp/web2 && yarn serve",
"build": "npm-run-all --parallel build:*",
"build:box": "vue-cli-service build",
"build:web1": "cd subapp/web1 && yarn build",
"build:web2": "cd subapp/web2 && yarn build"
},
运行项目即可
4.3 运行项目后请求子应用跨域
在子应用的
build -> webpack.dev.js中修改
devServer: {
// 由于会产生跨域, 所以加上
headers: {
'Access-Control-Allow-Origin': "*"
},
}
现在的运行结果为:
# 踩坑记录
Q1: webpackJsonp_vue is not defined
A1: 路由中用到异步懒加载引入改为正常引入
// const Home = resolve => require(['@/views/Home.vue'], resolve); // const Login = resolve => require(['@/components/Login.vue'], resolve); import Home from '@/views/Home.vue'; import Login from '@/components/Login.vue';
Q2: Uncaught Error: application 'vueApp1' died in status LOADING_SOURCE_CODE: [qiankun]: You need to export lifecycle functions in vueApp1 entry
A2: 答案是缺少
libraryTarget:'umd',需要以包的形式打包, 挂载window上
Q3:application 'portal-site' died in status LOADING_SOURCE_CODE: Failed to fetch
A3: 检查子应用是否启动以及子应用的端口号是否匹配的上
在这里我的问题是:子应用的配置中的端口号为
8081,,然而在main.jsd的apps配置中出现了8001的问题,草率了...
Q4: application 'portal-site' died in status LOADING_SOURCE_CODE: [qiankun]: Target container with #portalSite not existed while portal-site loading! - 目标容器不存在
Target container with #container not existed while xxx loading!- 目标容container一开始就不存在Target container with #container not existed while xxx mounting- 说明刚开始container容器是存在的,后来因为代码改变了document,导致container容器丢失。
A4: 当前的子应用挂在容器错误
const apps = [ { name:'portal-site', entry: "//localhost:8081/portal-site", // container: "#portalSite", container: "#app", activeRule: "/portal-site" } ] export default apps;
Q5: Loading chunk {x} failed.
A5: 将路由的异步引入改为同步
【注意】:通过度娘,看到好多人的解释都是路由加载后出现的问题,所以在
router -> index.js文件中导航守卫之前加入路由的错误监听,然而并没有解决上面的问题...// router/index.js router.onError((error) => { const pattern = /Loading chunk (\d) + failed/g; const isChunkLoadFailed = error.message.match(pattern); const targetPath = router.history.pending.fullPath; if (isChunkLoadFailed) { router.replace(targetPath) } })
遗留问题
- 子应用单独开发
- 打包
- css隔离
- js隔离