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();
参数解析:
name
: 微利用的名称,微利用之间必须确保惟一, 不便前期辨别我的项目起源。entry
: 微利用的入口也就是当满足条件的时候, 我要激活的指标微利用的地址(也能够是其余模式比方html
片段, 但本篇次要讲url地址这种模式)。container
: 激活微利用的时候咱们要把这个指标微利用放在哪里, 下面代码的意思就是把激活的微利用放在id为'box'
的元素外面。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>
web1
的vue.config.js
module.exports = { devServer: { port: 8082, // web2外面改成8083 },}
当初咱们要别离进入container
, web1
与web2
外面运行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
命令也写了。
其余扩大玩法:
- serial: 多个命令按排列程序执行,例如:npm-run-all --serial clean lint build:**
- continue-on-error: 是否疏忽谬误,增加此参数 npm-run-all 会主动退出出错的命令,持续运行失常的
- race: 增加此参数之后,只有有一个命令运行出错,那么 npm-run-all 就会完结掉全副的命令
上述筹备工作都做完了, 咱们能够启动我的项目试试了。
6. 申请子项目
居然跨域
运行起来会发现报错了:
须要在web1
与web2
两个我的项目vue.config.js
里加上如下配置就不报错了:
devServer: { port: 8082, // 因为会产生跨域, 所以加上 headers: { 'Access-Control-Allow-Origin': "*" } },
之所以会有这种跨域的报错是因为qiankun
外部应用fetch
申请的资源, 以后毕竟是启动了三个不同的node服务, 内部html页面申请其资源还是会跨域的, 所以须要设置容许所有源。
咱们为web1
与web2
设置一下款式, 后果如下:
- 但这些仅仅是个开始而已, 因为各种问题马上络绎不绝。
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()}
逐句解释:
window.__POWERED_BY_QIANKUN__
: 以后环境是否为qiankun.js
提供。window.__webpack_public_path__
: 等同于output.publicPath
配置选项, 然而他是动静的。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
的文件夹, 外面有三个包container
、web1
、web2
。
第一步: 确立打包门路
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; });}};
知识点留神解释:
output.library
: 配置导出库的名称, 如果libraryTarget
设置为'var'那么主利用能够间接用window拜访到。output.libraryTarget
:这里设置为umd
意思是在 AMD 或 CommonJS 的 require 之后可拜访。output.jsonpFunction
:webpack用来异步加载chunk的JSONP 函数。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
对象, 其实它是用with
与evel
来实现的替换, 并且比方jq
在执行前为了提高效率都会把window对象传入函数里应用, 那么这里间接传入代理window
就都ok了, 电脑越写越卡就不扯太多了。
所以其实应用了微前端
技术计划是要付出肯定的老本的, 代码速度必定是有所升高。
12. 康威定律
- 第一定律 组织沟通形式会通过零碎设计表达出来。
- 第二定律 工夫再多一件事件也不可能做的完满,但总有工夫做完一件事件。
- 第三定律 线型零碎和线型组织架构间有潜在的异质同态个性。
- 第四定律 大的零碎组织总是比小零碎更偏向于合成。
只有最适宜的组织模式, 没有相对的模式, 比方一个团队想要试试微前端, 那么其实如果你是个挪动端的商城我的项目, 没什么必要应用微前端, 如果是个小中型的后盾零碎, 也不是很举荐, 除非你们是一个长期保护并且模块繁多, 或者是你想在这个我的项目的根底上另启一个我的项目做, 那么微前端
将是一把神器。
end.
这次就是这样, 心愿与你一起提高。