vue我的项目'微前端'qiankun.js的实战攻略

本篇介绍

     对于微前端的大概念大家应该听过太多了, 这里我就大白话论述一下, 比方咱们新建三个vue工程a、b、c, a负责导航模块, b负责列表页面, c负责详情页面, 而后咱们能够通过微前端技术把他们组合在一起造成一个残缺我的项目

    本篇文章不会讲述很深刻的细节操作, 但会讲述我的项目搭建到我的项目上线的全环节, 如果你这些都会了那么其余的问题就不是太大妨碍了。

     肯定要明确一点, 微前端在很多场景都是不实用的, 千万不要强行应用这门技术, 在本篇文章里我会一点点的论述什么场景不实用以及为什么不实用。
    

1. 微前端qiankun.js简简简简介

     qiankun.js是以后最出色的一款微前端实现库, 他帮咱们实现了css隔离js隔离我的项目关联等性能, 文章的前面都会有所波及的当初就让咱们开始实战吧。

2. 本次的我的项目构造一主二附

     一共三个vue我的项目, 第一个container我的项目负责导航模块, 第二个web1第三个web2, container我的项目外面有个subapp文件夹, 外面寄存着web1 & web2两个我的项目, 这样当前咱们能够轻易增加web3,web4....都放在subapp文件夹即可。

3. 装置qiankun配置我的项目加载规定

     在咱们的容器我的项目container外面装置qiankun如下命令:

$ yarn add qiankun # 或者 npm i qiankun -S

     关上container我的项目的App.vue文件咱们把导航重定义一下:
     /w1/w2路由地址别离激活web1工程与web2工程。

 <div id="nav">  <router-link to="/">Home</router-link> |  <router-link to="/w1">web1</router-link> |  <router-link to="/w2">web2</router-link> |</div>

     咱们新增一个id为"box"的元素, 接下来咱们引入的web1工程就会插入到这个元素中。

<div id="box"></div><router-view />

     把Home.vue页面代码改掉:

<template>  <div class="home">我是`container`工程</div></template><script>export default {  name: "Home",};</script><style>.home {  font-size: 23px;}</style>  

此时的页面是这个样子的:

     关上container我的项目的main.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();

参数解析:

  1. name: 微利用的名称,微利用之间必须确保惟一, 不便前期辨别我的项目起源。
  2. entry: 微利用的入口也就是当满足条件的时候, 我要激活的指标微利用的地址(也能够是其余模式比方html片段, 但本篇次要讲url地址这种模式)。
  3. container: 激活微利用的时候咱们要把这个指标微利用放在哪里, 下面代码的意思就是把激活的微利用放在id为'box'的元素外面。
  4. activeRule:微利用的激活规定(有很多种写法甚至是函数模式), 下面代码就是当路由地址为/w1时激活。

4. 配置子项目main.js

     以配置web1我的项目为例, web2与其相似, 在main.js中导出本人的生命周期函数。

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() {  render()}/** * 利用每次 切出/卸载 会调用的办法,通常在这里咱们会卸载微利用的利用实例 */export async function unmount() {  instance.$destroy()}

web1 >public >index.html中的div元素id从app改为web1, 因为要多个我的项目合成一个我的项目, 所以id最好还是不要反复。

<!DOCTYPE html><html lang=""><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"></head><body>  <div id="web1"></div></body></html>

web1vue.config.js

module.exports = {  devServer: {        port: 8082, // web2外面改成8083    },}

     当初咱们要别离进入container, web1web2外面运行yarn serve命令, 然而这样运行命令真的好麻烦, 接下来我就介绍一种更工程化的写法。

5. npm-run-all

     npm-run-all是用来通过执行一条语句来达到执行多条语句的成果的插件。

$ npm install npm-run-all --save-dev# or $ yarn add npm-run-all --dev

改装咱们的container工程中的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"  },

我解释一下:
运行: yarn serve 零碎会执行scripts 外面所有的头部为serve:的命令, 所以就会实现一个命令运行三个我的项目, 这里棘手把build命令也写了。

其余扩大玩法:

  1. serial: 多个命令按排列程序执行,例如:npm-run-all --serial clean lint build:**
  2. continue-on-error: 是否疏忽谬误,增加此参数 npm-run-all 会主动退出出错的命令,持续运行失常的
  3. race: 增加此参数之后,只有有一个命令运行出错,那么 npm-run-all 就会完结掉全副的命令
上述筹备工作都做完了, 咱们能够启动我的项目试试了。

6. 申请子项目居然跨域

     运行起来会发现报错了:

须要在web1web2两个我的项目vue.config.js里加上如下配置就不报错了:

devServer: {        port: 8082,        // 因为会产生跨域, 所以加上        headers: {            'Access-Control-Allow-Origin': "*"        }    },

之所以会有这种跨域的报错是因为qiankun外部应用fetch申请的资源, 以后毕竟是启动了三个不同的node服务, 内部html页面申请其资源还是会跨域的, 所以须要设置容许所有源。

咱们为web1web2设置一下款式, 后果如下:



  • 但这些仅仅是个开始而已, 因为各种问题马上络绎不绝。

7. 辨别在是否在主利用内

     咱们有时候须要独自开发web1, 此时咱们并不依赖container我的项目, 那么咱们就要把main.js改装一下:

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')}if (window.__POWERED_BY_QIANKUN__) {  window.__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}if (!window.__POWERED_BY_QIANKUN__) {  render()}export async function bootstrap() {  console.log('bootstrap');}export async function mount() {  render()}export async function unmount() {  instance.$destroy()}

逐句解释:

  1. window.__POWERED_BY_QIANKUN__: 以后环境是否为qiankun.js提供。
  2. window.__webpack_public_path__: 等同于 output.publicPath 配置选项, 然而他是动静的。
  3. window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__:qiankun.js注入的公共门路。

判断以后环境为独自开发的环境就间接执行render办法, 如果是qiankun的容器内, 那么须要设置publicPath, 因为qiankun须要把每个子利用都辨别开, 而后引入容器我的项目内, 这样咱们就能够独自开发web1我的项目了。

8. 子利用路由跳转与vue-router的异步组件小bug

     在配置router的时候咱们常常会将页面写成异步加载:

component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),

web2我的项目中的home页面, 我减少一个按钮跳到about页面:

<template>  <div class="home">    <button @click="jump">点击跳转到about页面</button>  </div></template><script>export default {  methods: {    jump() {      // this.$router.push("/web2/about");      window.history.pushState(null, null, "/w2/about");    },  },};</script>

     上述代码不能够间接用this.$router.push, 这样会与qiankun.js的路由调配抵触, 官网上说会呈现404这种状况, 所以倡议咱们间接用 window.history.pushState

     然而这中写法在以后版本qiankun.js外面可能会有如下谬误:

     这是因为动静设置的publicPath并不能满足加载异步组件chunk, 须要咱们如下配置一番:(web2->vue.config.js)

publicPath: `//localhost: 8083`

就能够失常加载这个页面了:

并且此时间接刷新以后url也还能够正确显示about页面。

9. 辨别开发与打包

     后面几条说的都是开发相干的设置, 这里咱们要开始介绍打包的配置了, 这里会介绍原理与做法, 不会做的很细所以具体的我的项目开发还是要好好的封装一番。

我这里先把nginx简略配置一下, 让这个包能用。

location /ccqk/web1 {    alias   /web/ccqk/web1;    index  index.html index.htm;    try_files $uri $uri/ /index.html;}location /ccqk/web2 {    alias   /web/ccqk/web2;    index  index.html index.htm;    try_files $uri $uri/ /index.html;}location /ccqk {    alias   /web/ccqk/container;    index  index.html index.htm;    try_files $uri $uri/ /index.html;}

因为我之前有我的项目在服务器上为了不便辨别, 轻易写了个ccqk前缀, 那么当初指标很明确了, 我须要打一个叫ccqk的文件夹, 外面有三个包containerweb1web2

第一步: 确立打包门路
  • container -> vue.config.js

    module.exports = {outputDir: './ccqk/container',publicPath: process.env.NODE_ENV === "production" ? `/ccqk` : '/',};
  • web1 -> vue.config.js
const packageName = require('./package.json').name;const port = 8082module.exports = {    outputDir: '../../ccqk/web1',    publicPath: process.env.NODE_ENV === "production" ? '/ccqk/web1' : `//localhost:${port}`,    devServer: {        port,        headers: {            'Access-Control-Allow-Origin': "*"        }    },    configureWebpack: {        // 须要以包的模式打包, 挂载window上        output: {            library: `${packageName}-[name]`,            libraryTarget: 'umd',            jsonpFunction: `webpackJsonp_${packageName}`,        },    },    chainWebpack: config => {        config.plugin("html").tap(args => {            args[0].minify = false;            return args;        });    }};
  • web2 -> vue.config.json

    const packageName = require('./package.json').name;const port = 8083module.exports = {outputDir: '../../ccqk/web2',publicPath: process.env.NODE_ENV === "production" ? '/ccqk/web2' : `//localhost:${port}`,devServer: {    port,    headers: {        'Access-Control-Allow-Origin': "*"    }},configureWebpack: {    output: {        library: `${packageName}-[name]`,        libraryTarget: 'umd',        jsonpFunction: `webpackJsonp_${packageName}`,    },},chainWebpack: config => {    config.plugin("html").tap(args => {        args[0].minify = false;        return args;    });}};

    知识点留神解释:

  1. output.library: 配置导出库的名称, 如果libraryTarget设置为'var'那么主利用能够间接用window拜访到。
  2. output.libraryTarget:这里设置为umd意思是在 AMD 或 CommonJS 的 require 之后可拜访。
  3. output.jsonpFunction:webpack用来异步加载chunk的JSONP 函数。
  4. chainWebpack: 用来批改webpack的配置, 配置不进行压缩。
第二步: 配置路由门路
  • web2 -> router ->index.js

    const router = new VueRouter({mode: "history",base: process.env.NODE_ENV === "development" ? '/w2' : '/ccqk/w2',routes,});

10. css隔离


     这里的隔离并不是完满的, 想要理解更具体的内容能够看看我的往期文章带你走进-\>影子元素(Shadow DOM)&浏览器原生组件开发(Web Components API ), 看完你就会齐全了解为啥不完满。

11. js隔离

     在多利用场景下,每个微利用的沙箱都是互相隔离的,也就是说每个微利用对全局的影响都会局限在微利用本人的作用域内。比方 A 利用在 window 上新增了个属性 test,这个属性只能在 A 利用本人的作用域通过 window.test 获取到,主利用或者其余微利用都无奈拿到这个变量。

     我这里就不秀源码不扯大概念, 间接来干货原理, qiankun会在子利用激活的时候为其赋予一个代理后的window对象, 用户操作这个window对象的每一步都会被记录下来, 不便在卸载子利用时还原全局window对象, 你要问如何替换的window对象, 其实它是用withevel来实现的替换, 并且比方jq在执行前为了提高效率都会把window对象传入函数里应用, 那么这里间接传入代理window就都ok了, 电脑越写越卡就不扯太多了。

     所以其实应用了微前端技术计划是要付出肯定的老本的, 代码速度必定是有所升高。

12. 康威定律

  • 第一定律 组织沟通形式会通过零碎设计表达出来。
  • 第二定律 工夫再多一件事件也不可能做的完满,但总有工夫做完一件事件。
  • 第三定律 线型零碎和线型组织架构间有潜在的异质同态个性。
  • 第四定律 大的零碎组织总是比小零碎更偏向于合成。

     只有最适宜的组织模式, 没有相对的模式, 比方一个团队想要试试微前端, 那么其实如果你是个挪动端的商城我的项目, 没什么必要应用微前端, 如果是个小中型的后盾零碎, 也不是很举荐, 除非你们是一个长期保护并且模块繁多, 或者是你想在这个我的项目的根底上另启一个我的项目做, 那么微前端将是一把神器。

end.

这次就是这样, 心愿与你一起提高。