一、介绍
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端的核心价值:
- 技术栈无关
- 独立开发、独立部署
- 增量升级
- 独立运行时
qiankun 微前端框架特性:
- 基于single-spa封装
- 技术栈无关
- HTML Entry 接入方式
- 样式隔离
- JS沙箱
- 资源预加载
- umi插件
二、快速上手
主应用
1. 安装 qiankun
yarn add qiankun
# 或
npm install qiankun -Ss
2. 在主应用中注册微应用
import { registerMicroApps, start } from 'qiankun'
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:7100',
container: '#reactContainer',
activeRule: '/reactActiveRule'
},
{
name: 'vueApp',
entry: {
scripts: ['//localhost:7100/main.js']
},
container: '#vueContainer',
activeRule: '/vueActiveRule'
},
])
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式:
import { loadMicroApp } from 'qiankun'
loadMicroApp({
name: 'app',
entry: '//localhost:7100',
container: '#yourContainer'
})
微应用
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
1. 导出相应的生命周期钩子
微应用需要在自己的入口js( 通常是你配置的webpack的entry.js )导出 bootstrap、mount、unmount三个生命周期钩子,以供主引用在适当的时机调用。
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发
* bootstrap。
*/
export async function bootstrap() {
console.log('bootstrap')
}
/**
* 应用每次进入都会调用 mount 方法, 通常我们再这里触发应用的渲染方法。
*/
export async function mount (props) {
ReactDOM.render(
<App />,
props.container ? props.container.querySelector('#root') : document.getElementById('root')
)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount (props) {
ReactDOM.unmountComponentAtNode(
props.container ? props.container.querySelector('#root') : document.getElementById('root')
)
}
/**
* 可选,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update (props) {
console.log(props)
}
2. 配置微应用的打包工具
除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
webpack:
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packgeName}`
}
}
项目实践
主应用
主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start 即可。
// 安装qiankun
yarn add qiankun # 或者 npm i qiankun -S
// 注册微应用并启动:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:3000',
container: '#container',
activeRule: '/app-react',
},
{
name: 'vueApp',
entry: '//localhost:8080',
container: '#container',
activeRule: '/app-vue',
},
{
name: 'angularApp',
entry: '//localhost:4200',
container: '#container',
activeRule: '/app-angular',
},
]);
// 启动 qiankun
start();
微应用
微应用分为 有 webpack 构建和 无 webpack 构建项目。
有 webpack 构建项目 【Vue | React | Angular】
-
新增 public-path.js 文件,用于修改运行时的 publicPath; (运行时的publicPath 和 构建时的publicPath 是不同的,两者不能等价替代)。
-
微应用建议使用 history 模式的路由,需要设置路由 base, 值和它的 activeRule 是一样的。
-
在入口文件最顶部引入 public-path.js, 修改并导出三个生命周期函数。
-
修改webpack打包,允许开发环境跨域和 umd 打包。
你的项目是
index.html和其他的所有文件分开部署的,说明你们已经将构建时的publicPath设置为了完整路径,则不用修改运行时的publicPath(第一步操作可省)。
无webpack构建项目
无webpack构建的微应用直接将 生命周期 lifecycles 挂载到 window 上即可。
React 微应用
以 create react app 生成的 React 16 项目为例,搭配 react-router-dom 5.x 。
-
在 src 目录下新增 public-path.js :
if (window._POWERED_BY_QIANKUN) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } -
设置 history 模式路由的 base :
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}> -
入口文件 index.js 修改, 为了避免根id
#root与其他的DOM冲突,需要限制查找范围。import 'public-path' import React from 'react' import ReactDOM from 'react-dom' import App from './App' function render (props) { const { container } = props ReactDOM.render( <App />, container ? container.querySelector('#root') : document.querySelect('#root') ) } if (!window.__POWERED_BY_QIANKUN__) { render({}) } export async function bootstrap() { console.log('[react16] react app bootstraped') } export async function mount(props) { console.log('[react16] props from main framework', props) render(props) } export async function unmount(props) { const { container } = props ReactDOM.unmountComponentAtNode( container ? container.querySelector('#root') : document.querySelector('#root') ) } -
修改webpack配置
安装插件
@rescripts/cli,当然也可以选择其他的插件,如react-app-rewired。npm i -D @rescripts/cli根目录新增
.rescriptsrc.js:const { name } = require('./package'); module.exports = { webpack: (config) => { config.output.library = `${name}-[name]`; config.output.libraryTarget = 'umd'; config.output.jsonpFunction = `webpackJsonp_${name}`; config.output.globalObject = 'window'; return config; }, devServer: (_) => { const config = _; config.headers = { 'Access-Control-Allow-Origin': '*', }; config.historyApiFallback = true; config.hot = false; config.watchContentBase = false; config.liveReload = false; return config; }, };修改 package.json
- "start": "react-scripts start", + "start": "rescripts start", - "build": "react-scripts build", + "build": "rescripts build", - "test": "react-scripts test", + "test": "rescripts test", - "eject": "react-scripts eject"Vue 微应用
以
vue-cli 3+生成的vue 2.x项目为例,vue 3版本等稳定后再补充。-
在 src 目录下新增 public-path.js
if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } -
入口文件 main.js 修改,为了避免根id
#app与其他的DOM冲突,需要限制查找范围。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({ base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/', 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(props) { instance.$destroy() instance.$el.innerHTML = '' instance = null router = null } -
打包配置修改(vue.config.js)
const { name } = require('./package') module.exports = { devServer: { headers: { 'Access-Control-Allow-Origin': '*', }, }, configureWebpack: { output: { library: `${name}-[name]`, libraryTarget: 'umd', // 把微应用打包成 umd 库格式 jsonpFunction: `webpackJsonp_${name}`, }, }, }
非 webpack 构建的微应用
一些非
webpack构建的项目,例如jQuery项目、jsp项目,都可以按照这个处理。接入之前请确保你的项目里的图片、音视频等资源能正常加载,如果这些资源的地址都是完整路径(例如
https://qiankun.umijs.org/logo.png),则没问题。如果都是相对路径,需要先将这些资源上传到服务器,使用完整路径。接入非常简单,只需要额外声明一个
script,用于export相对应的lifecycles。例如:-
声明 entry 入口
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Purehtml Example</title> </head> <body> <div> Purehtml Example </div> </body> + <script src="//yourhost/entry.js" entry></script> </html> -
在 entry.js 里声明生命周期 lifecycles
const render = ($) => { $('#puerhtml-container').html('Hello, render with jQuery'); return Promise.resolve(); } ((global) => { global['purehtml'] = { bootstrap: () => { console.log('purehtml bootstrap') return Promise.resolve() }, mount: () => { console.log('purehtml mount') return render($) }, unmount: () => { console.log('purehtml unmount') return Promise.resolve() } } })(window)
-
【参考】1. qiankun 官网: 介绍 - qiankun (umijs.org)
2. qiankun的github: umijs/qiankun: 📦 🚀 Blazing fast, simple and complete solution for micro frontends. (github.com)