微前端 - 搭建记录

562 阅读5分钟

主项目的搭建

一、创建主项目

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、访问主应用:

http://localhost:8090/

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路径的。