关于微前端
的大概念你们应该听过太多了, 这里我就大白话阐述一下, 好比咱们新建三个vue工程
a、b、c, a
负责导航模块, b
负责列表页面, c
负责详情页面, 而后咱们能够经过微前端
技术把他们组合在一块儿造成一个完整项目
。css
本篇文章不会讲述很深刻的细节操做, 但会讲述项目搭建到项目上线的全环节, 若是你这些都会了那么其余的问题就不是太大阻碍了。html
必定要明确一点, 微前端
在不少场景都是不适用的, 千万不要强行使用这门技术, 在本篇文章里我会一点点的阐述什么场景不适用以及为何不适用。
前端
qiankun.js
简简简简介 qiankun.js
是当前最出色的一款微前端
实现库, 他帮咱们实现了css隔离
、js隔离
、项目关联
等功能, 文章的后面都会有所涉及的如今就让咱们开始实战吧。vue
一主二附
一共三个vue项目
, 第一个container
项目负责导航模块, 第二个web1
第三个web2
, container
项目里面有个subapp
文件夹, 里面存放着web1 & web2
两个项目, 这样之后咱们能够随便添加web3,web4....
都放在subapp
文件夹便可。node
qiankun
配置项目加载规则 在咱们的容器项目container
里面安装qiankun
以下命令:webpack
$ yarn add qiankun # 或者 npm i qiankun -S
打开container
项目的App.vue
文件咱们把导航重定义一下:
/w1
与/w2
路由地址分别激活web1
工程与web2
工程。nginx
<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
工程就会插入到这个元素中。web
<div id="box"></div> <router-view />
把Home.vue
页面代码改掉:vue-router
<template> <div class="home">我是`container`工程</div> </template> <script> export default { name: "Home", }; </script> <style> .home { font-size: 23px; } </style>
此时的页面是这个样子的:vue-cli
打开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
时激活。子项目
的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
命令, 可是这样运行命令真的好麻烦, 接下来我就介绍一种更工程化的写法。
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
命令也写了。
其余扩展玩法:
子项目
居然跨域 运行起来会发现报错了:
须要在web1
与web2
两个项目vue.config.js
里加上以下配置就不报错了:
devServer: { port: 8082, // 因为会产生跨域, 因此加上 headers: { 'Access-Control-Allow-Origin': "*" } },
之因此会有这种跨域的报错是由于qiankun
内部使用fetch
请求的资源, 当前毕竟是启动了三个不一样的node服务, 外部html页面请求其资源仍是会跨域的, 因此须要设置容许全部源。
web1
与web2
设置一下样式, 结果以下: 咱们有时候须要单独开发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
项目了。
子应用
路由跳转与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`
就能够正常加载这个页面了:
about
页面。前面几条说的都是开发相关的设置, 这里咱们要开始介绍打包的配置了, 这里会介绍原理与作法, 不会作的很细因此具体的项目开发仍是要好好的封装一番。
我这里先把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, });
这里的隔离并非完美的, 想要了解更详细的内容能够看看个人往期文章带你走进-\>影子元素(Shadow DOM)&浏览器原生组件开发(Web Components API ), 看完你就会彻底理解为啥不完美。
在多应用场景下,每一个微应用的沙箱都是相互隔离的,也就是说每一个微应用对全局的影响都会局限在微应用本身的做用域内。好比 A 应用在 window 上新增了个属性 test,这个属性只能在 A 应用本身的做用域经过 window.test 获取到,主应用或者其余微应用都没法拿到这个变量。
我这里就不秀源码不扯大概念, 直接来干货原理, qiankun
会在子应用
激活的时候为其赋予一个代理后的window
对象, 用户操做这个window
对象的每一步都会被记录下来, 方便在卸载子应用
时还原全局window
对象, 你要问如何替换的window
对象, 其实它是用with
与evel
来实现的替换, 而且好比jq
在执行前为了提升效率都会把window对象传入函数里使用, 那么这里直接传入代理window
就都ok了, 电脑越写越卡就不扯太多了。
因此其实使用了微前端
技术方案是要付出必定的成本的, 代码速度确定是有所下降。
只有最适合的组织模式, 没有绝对的模式, 好比一个团队想要试试微前端, 那么其实若是你是个移动端的商城项目, 没什么必要使用微前端, 若是是个小中型的后台系统, 也不是很推荐, 除非大家是一个长期维护而且模块繁多, 或者是你想在这个项目的基础上另启一个项目作, 那么微前端
将是一把神器。
此次就是这样, 但愿与你一块儿进步。