主项目的搭建
一、创建主项目
vue create hello-main
二、安装qiankun
npm install qiankun --save
(注意:子项目无需安装qiankun)
其他常用组件的安装:
npm install jsencrypt --save
npm install element-ui --save
npm install axios --save
三、创建vue.config.js配置对外访问的端口号
module.exports = {
devServer: {
host: '0.0.0.0', // 配置localhost会无法使用IP访问
port: 8090,
open: true
}
}
然后可以启动项目了npm run serve,如果发现报错有些依赖包找不到需要安装,可以直接执行npm install即可。
四、修改主项目文件
4.1、main.js文件:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps, start } from 'qiankun'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app') // 主应用的入口 对应index.html文件中的id
registerMicroApps([
/* name: 微应用的名称,微应用之间必须确保唯一
entry: 表示微应用的访问地址
container: 微应用挂在到哪个容器下
activeRule: 微应用的激活规则 */
{
name: 'vueApp',
entry: '//localhost:8091',
container: '#subContainer', // 子应用挂在id为subContainer的div下
activeRule: '/app-vue'
}
])
// 启动 qiankun
start()
注意:
1)、$mount('#app')是主应用的入口,对应index.html文件中的id。
2)、注册子应用配置中
- name: 属性不一定要和子项目保持一致,仅是一个name标识,保持唯一即可。
- entry:是子项目的访问地址和端口。
- container:子应用应该挂在到主应用的哪个div下!!!这个不要忽视。
- activeRule:中的url标识,一定要和子项目中配置的拦截到对应子项目的标识保持一致。
4.2、修改index.html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
注意:
没有改动,只是<div id="app"></div>对应main.js文件中的.$mount('#app')
4.3、App.vue文件:
<template>
<div id="app-base">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/app-vue">AppVue</router-link>
</div>
<div id="subContainer"></div>
<router-view />
</div>
</template>
<style lang="scss">
html,
body {
widows: 100%;
height: 100%;
margin: 0px;
padding: 0px;
border: 1px solid red;
box-sizing: border-box;
}
#app-base {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
widows: 100%;
height: 100%;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
注意:
对应main.js文件中的配置,将子应用挂在到id="subContainer"的div中。
4.4、路由修改成history模式:
修改文件router/index.js
改:
const router = new VueRouter({
routes
})
为:
const router = new VueRouter({
routes,
mode: 'history'
})
4.5、访问主应用:
Home页和About页能正常访问,AppVue页面报错如下。
因为还没有子项目访问不到,接下来就是创建子项目并挂载了。
子项目的搭建
一、创建子项目
vue create app-vue
其他安装:
npm install jsencrypt --save
npm install element-ui --save
npm install axios --save
启动子项目npm run serve,直接访问。
如果发现报错有些依赖包找不到需要安装,如下图所示:
可以直接执行npm install即可。
二、src下添加public-path.js文件
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
三、修改子项目文件
3.1、main.js文件:
import './public-path'
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import routes from './router'
import store from './store'
Vue.config.productionTip = false
let router = null
let instance = null
function render (props = {}) {
const { container } = props
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
mode: 'history',
routes
})
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
export async function bootstrap () {
console.log('[vue] vue app bootstraped')
}
export async function mount (props) {
console.log('[vue] props from main framework', props)
render(props)
}
export async function unmount () {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
router = null
}
注意:
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
此处/app-vue/的意思是,凡是url上有app-vue的会被拦截至当前子项目。
.$mount(container ? container.querySelector('#app') : '#app')
此处的#app对应子项目index.html文件中的id。
3.2、创建vue.config.js文件:
const { name } = require('./package')
module.exports = {
devServer: {
port: 8091,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`
}
}
}
注意:
port: 8091,当前子项目访问的端口号配置,注意不要和主项目冲突。
3.3、修改router下的index.js:
参考文章里没有提到这个,以至于直接启动项目后,报错:
died in status SKIP_BECAUSE_BROKEN: routes.forEach is not a function
注意到main.js中有:
import routes from './router'
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
mode: 'history',
routes
})
routes不是已经是VueRouter对象了吗,这里又把它放到VueRouter中去???
所以需要修改router下的index.js:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
// const router = new VueRouter({
// routes
// })
export default routes
3.4、启动子应用
确保主应用和子应用都启动无误后。
直接访问子应用:http://localhost:8091/,正常访问子应用。
访问主应用:http://localhost:8090/,正常访问主应用。
然后点击AppVue跳转至http://localhost:8090/app-vue/,可以了!!!
参考文章
参考官方文档:qiankun.umijs.org/zh/guide/tu…
其他参考:www.jianshu.com/p/278635338…
注意:
最开始按照官方文档操作怎么也不成功,后来发现是自己理解有误,所以没事还是多看书,熟读官方文档。
其他注意点
1、报错LOADING_SOURCE_CODE
Uncaught TypeError: application 'app-vue' died in status LOADING_SOURCE_CODE
首先检查子项目启动了没有!!!
2、样式隔离问题
子应用的样式一定要加上scoped
<style lang="scss" scoped>
否则会修改到主应用的样式,不信你随便拿一个标签试试。
官方的解决方案:
若子应用的样式影响到了我主应用的样式。
只需启动qiankun时配置参数即可:
// 启动 qiankun
start({
sandbox: {
strictStyleIsolation: true,
experimentalStyleIsolation: true
}
})
官方文档:
qiankun.umijs.org/zh/api#regi…
3、主应用和子应用全局变量的思考:
onGlobalStateChange函数监听全局变量的变化,offGlobalStateChange移除监听。
我的第一反应是,我在子应用好几个页面调用onGlobalStateChange监听全局变量,那我切换页面的时候不得调用offGlobalStateChange移除监听呀?
结果发现老是报错没有这个函数之类的,啥玩意???
后来想了想,我是傻吗,熟读官方例子啊!!!官方还能骗我不成?(虽然有时候能)
只需要在子应用初始化完成,对外(主应用)暴露的mount函数中监听就行了,然后子应用销毁的时候umount函数会默认调用offGlobalStateChange。
至于各个子应用的界面要用到的话,那就是子应用内部的全局变量的问题了,和原来单个前端一样呀,把这个onGlobalStateChange的参数直接赋值给vuex不就行啦。
总之initGlobalState这玩意为的就是解决,多个应用之间的共享变量的问题,而单个应用内部早就已经解决了。
此处还需要注意:
刷新页面变量又回到默认值了,和vuex里的一毛一样。所以如果需要刷新不变,那就放localStorage吧,还是和以前单个应用一个样。
此问题遗留吧,总感觉这样很麻烦,但是每个页面监听了,跳转后又不移除这不是浪费资源吗。
衍生:
听说不能直接修改state要通过mutation去提交,但是我还没碰到这个坑,碰到了再说。
4、store全局变量的监听问题:
在子应用的onGlobalStateChange监听中修改了store.state.test的值,同时在about页面去监听store.state.test的变化,竟然没反应。如下图所示:
纠结了半天,结果很简单。是没有给初始值。
5、图片等资源文件的引入
配置:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
__webpack_public_path__决定了output.publicPath的值,用于来指定资源的路径前缀。 但是此资源仅仅是打包的资源,排除static资源文件。
很明显可以看到凡是放在assets下的资源文件就能正常显示,查看图片src前面追加上了__webpack_public_path__的值,但是static前面就没有。
配置完后,相对路径的资源文件前都会追加__webpack_public_path__,绝对路径webpack不进行处理。
比如项目中有如下图片引入:
<img src="../../../assets/img/test.png">
<img src="/static/img/test.png">
<img src="../../../../public/static/img/test.png">
项目启动后查看页面元素路径
<img src="http:/localhost:8083/static/img/test.17474019.png">
<img src="/static/img/test.png">
<img src="http:/localhost:8083/static/img/test.17474019.png">
或者配置vue.config.js:
module.exports = {
assetsDir: 'http://localhost:8083/static'
}
同样的效果,相对路径前webpack会加上assetsDir前缀,绝对路径不处理。
但是该配置只适用于static文件夹下的图片等,不适用css、js,你会发现直接访问没问题,通过微前端访问是找不到js路径的。