qiankun 微前端 demo(Vue2)

1,205 阅读3分钟

前言

  • 这是我最近刚开始学微前端(qiankun框架)做的一个小demo,做的时候还是遇到很多问题的,在网上也是看了很多别人的Blog,最后也是磨出来了😂😂😂;
  • 这篇文章总统分为分为两部分:
    • 第一部分:是从0到1整个demo的搭建到启动的过程;
    • 第二部分:在整个过程中遇到的各种问题解决和解决办法(后面会陆续补充)。
  • 后面会出一个完整的微前端项目(一个基座 + 两个子应用(B站上面一些培训机构的项目))。
  • 给孩子点点关注吧😭 image.png

一、demo 搭建 到 启动

1.1 创建 基座 和 微应用

  • Vue创建项目的命令:vue create 项目名称
  • 各项目名称:
    • 基座:app1
    • 子应用:app2 + app3
  • 我是为方便,自己在每个项目下建立了 .env 文件,指定端口:
    • PORT = 指定端口号
    • image.png
    • 各项目端口号:
      • 基座:3001
      • 子应用:
        • app1:3002
        • app2:3003
  • 如果项目中有 Eslint,可以先关闭:
    • 在 vue.config.js 中:image.png

1.2 qiankun 框架下载

  • 只需要 基座 进行 qiankun的下载即可;
  • 命令:
    • npm:npm i qiankun -S
    • yarn: yarn add qiankun

1.3 基座(主应用)配置

1.3.1 从 qiankun 引入两个函数

    // registerMicroApps => 注册微应用
    // start => 启动 qiankun
    import { registerMicroApps, start } from 'qiankun'

1.3.2 注册微应用并启动

    registerMicroApps([
        {
            // 子应用名称
            name: 'App2',
            // 默认会加载这个路径下的html,解析里面的js
            entry: '//localhost:3002',
            // 加载的容器(微应用会显示到这个容器里面,一定要保证主应用中有这个容器)
            container: '#container',
            // 匹配的路由
            activeRule: '/app2'
        },
        {
            name: 'App3',
            entry: '//localhost:3003',
            container: '#container',
            activeRule: '/app3'
        }
    ])
    start()
  • 基座(主应用)全部代码展示:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import { start, registerMicroApps } from 'qiankun'

Vue.config.productionTip = false

// TODO 注册子应用
registerMicroApps([
  {
    // 子应用的名称
    name: 'App2',
    // 默认会加载这个路径下的html,解析里面的js
    entry: '//localhost:3002',
    // 加载的容器
    container: '#container',
    // 匹配的路由
    activeRule: '/app2'
  },
  {
    name: 'App3',
    entry: '//localhost:3003',
    container: '#container',
    activeRule: '/app3'
  },
  {
    name: 'App4',
    entry: '//localhost:8080',
    container: '#container',
    activeRule: '/login?redirect=%2F'
  }
])

// TODO 启动 qiankun
start()

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

1.3.3 基座(主应用)App.vue页面完整代码

<template>
  <div id="app">
    <nav>
      <router-link to="/app2" style="color: blue; fontSize: 25px">App2 - Home</router-link> |
      <router-link to="/app3" style="color: blue; fontSize: 25px">App3 - Home</router-link> |
    </nav>
    <div id="container"></div>
    <router-view/>
  </div>
</template>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

1.4 微应用配置

1.4.1 在 src下 新增 public-path.js 文件

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

1.4.2 微应用 入口文件(main.js) 配置

  • 注意: 很多报错都是出在微应用入口文件的配置
  • router/index.js 中,有 new VueRouter() 这一步,注释掉 new VueRouter()export default router,新增 export default routes(routes就是路由规则数组),就可以直接复制 qiankun官方 关于main.js代码直接使用, qiankun官方文档
  • image.png
  • 微应用 router/index,js 完整代码:
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'

Vue.use(VueRouter)

const routes = [
    {
        path: '/',
        name: 'home',
        component: HomeView
    },
    {
        path: '/about',
        name: 'about',
        component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
     }
]

// const router = new VueRouter({
//   mode: 'history',
//   base: process.env.BASE_URL,
//   routes
// })

// export default router
export default routes
  • 入口文件(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({
    // app3中配置只需将此处的 app2 换成 app3即可,其他的不变
    // 此处的路径,是根据主应用中注册微应用的时候,activeRule字段设置的
    base: window.__POWERED_BY_QIANKUN__ ? '/app2' : '/',
    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;
}

1.4.3 微应用webpack配置

  • 也是直接复制 qiankun官方的代码
  • vue.config.js 中配置
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package.json');

module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      /**
      * 此处,qiankun官方是 jsonpFunction: `webpackJsonp_${name}`
      * 具体原因见地位部分 - 
      */ 
      chunkLoadingGlobal: `webpackJsonp_${name}`,
    },
  },
})

1.4.3. 微应用App.vue代码

  • app2:
<template>
  <div id="app">
    <nav>
      <h1 style="color: red; fontSize: 50px">App2 - 我是 3002 端口</h1>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </nav>
    <router-view/>
  </div>
</template>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

  • app3:
<template>
  <div id="app">
    <nav>
      <h1 style="color: red; fontSize: 50px">App3 - 我是 3003 端口</h1>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </nav>
    <router-view/>
  </div>
</template>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

二、过程中遇到的问题及解决方法

2.1 需要从微应用中导出生命周期函数

  • 错误:application 'misthin-admin-element' died in status LOADING_SOURCE_CODE: [qiankun]: You need to export lifecycle functions in misthin-admin-element entry
  • 解决办法:
    • 首先去官方看看:qiankun官方常见问题
    • 如果配置什么的都没有问题,还是报这个错误,我自己这边还是 webpack的问题:
      // 在项目中 configureWebpack 属性 是由两个形式的:对象 + 函数
      // 1. 对象形式
      module.exports = defineConfig({
          configureWebpack: {
              output: {
                  library: `${name}-[name]`,
                  libraryTarget: 'umd', // 把微应用打包成 umd 库格式
                  // 需将 jsonpFunction 字段改成 chunkLoadingGlobal
                  // 此处问题见 2.2:webpack配置问题
                  chunkLoadingGlobal: `webpackJsonp_${name}`,
              },
          },
      })
      
      // 2. 函数形式
      /**
      * 如果依然采用上述写法,可能会报错:
      * Avoid modifying webpack output. path directly.  Use the "outputDir" option instead.
      * 避免修改webpack输出。直接路径。请使用“outputDir”选项。
      */
      // 需要改写成下面的形式:
      module.exports = {
          configureWebpack: (config) => {
              config.output.library = `${name}-[name]`
              config.output.libraryTarget = 'umd'
              config.output.jsonpFunction  = `webpackJsonp_${name}`
              config.output.globalObject = 'window'
          }
      };
      

2.2 关于webpack的报错

  • 在 webpack4 中,多个 webpack 运行时可能会在同一个 HTML 页面上发生冲突,因为它们使用同一个全局变量进行代码块加载。为了解决这个问题,需要为 output.jsonpFunction 配置提供一个自定义的名称。
  • Webpack5 确实会从 package.json name 中自动推断出一个唯一的构建名称,并将其作为 output.uniqueName 的默认值。
  • 这个值用于使所有潜在的冲突的全局变量成为唯一。
  • 迁移: 由于 package.json中有唯一的名称,可将output.jsonpFunction 删除。
  • 报错原因: 在2020-10-10发布的webpack5中已将 output.jsonpFunction 更名为 output.chunkLoadingGlobal
  • image.png

2.3 关于项目静态资源加载404问题

  • image.png
  • 问题出现原因:
    • 微应用(子应用)放入到基座(主应用)中后,静态资源会默认走主应用地址去访问,但是主应用中又没有这些静态资源文件,其结果显而易见,肯定就是404了。
  • 解决方法:
    • 原理:publicPath字段设置静态资源路径,默认是走的相对路径,将该字段配置成绝对地址的url即可(子应用部署之后的地址,本地调试的时候,写本地服务地址即可)。
    • 解决方法:
      • 目标文件: vue.config.js
      module.exports = {
          // 设置静态资源访问路径为绝对路径,启动项目的时候,终端显示的地址
          publicPath: 'http://localhost:8080',
          // 修改打包配置
          configureWebpack: {
              // webpack 配置
              output: {
                  library: `${name}-[name]`,
                  libraryTarget: 'umd',
                  jsonpFunction: `webpackJsonp_${name}`
              }
          },
          devServer: {
              // 允许跨域
              headers: {
                  'Access-Control-Allow-Origin': '*',
              },
          },
      }
      
  • publicPath配置选项在各种场景中都非常有用,可以通过它来指定应用程序中所有资源的基础路径。

2.4 其他错误

  • 我主要遇到的就是这个问题,其他的错误可以移步 qiankun官方常见问题
  • 还有一种可能就是 eSlint导致的。