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 = 8082
module.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 = 8083 module.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.
这次就是这样, 心愿与你一起提高。