微前端技术(微前端 – qiankun)
前言
qiankun 是一个基于 single-spa 的微前端实现库 ,旨在帮助大家能更简单 、无痛的构建一个生产可用微前端架构系统 。
本文主要记录下如何接入 qiankun 微前端 。主应用使用 vue2 ,子应用使用 vue3 、react 。
一 、主应用
主应用不限技术栈 ,只需要提供一个容器 DOM ,然后注册微应用并 start 即可 。
1⃣️ 、创建项目
// @vue/cli 5.0.4 npm install @vue/cli -g vue create main-vue主应用选择 vue2.x 版本 。 具体创建步骤 ,便不在此一一叙述 。
项目创建之后 ,配置路由 ,页面布局等 。整体效果如下图 。
2⃣️ 、安装 qiankun
npm i qiankun -S3⃣️ 、 注册微应用并启动
新建微应用子列表文件 micros/app.js
// src/micros/app.js // 子应用列表 const apps = [ { name: vue2-app, // 子应用app name 推荐与子应用的package的name一致 entry: //localhost:8081/, // 子应用的入口地址 ,就是你子应用运行起来的地址 container: #micro-container, // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】 activeRule: /vue2App // 子应用的路由前缀 }, { name: vue3-app, entry: //localhost:8082/, container: #micro-container, activeRule: /vue3App }, { name: react-app, entry: //localhost:8083/, container: #micro-container, activeRule: /react } ] export default apps注册微应用
// src/micros/index.js import { addGlobalUncaughtErrorHandler, registerMicroApps, start} from qiankun // 微应用的信息 import apps from ./app /** * 注册微应用 * 第一个参数 - 微应用的注册信息 * 第二个参数 - 全局生命周期钩子 */ registerMicroApps(apps, { // qiankun 生命周期钩子 - 微应用加载前 beforeLoad: (app) => { // 加载微应用前 ,加载进度条 console.log(before load=====, app.name) return Promise.resolve() }, // qiankun 生命周期钩子 - 微应用挂载后 afterMount: (app) => { // 加载微应用前 ,进度条加载完成 console.log(after mount=====, app.name) return Promise.resolve() } } ) /** * 添加全局的未捕获异常处理器 */ addGlobalUncaughtErrorHandler((event) => { console.error(event) const { message: msg } = event if (msg && msg.includes(died in status LOADING_SOURCE_CODE)) { console.error(微应用加载失败 ,请检查应用是否可运行) } }) // 导出 qiankun 的启动函数 export default start配置主应用路由
// src/router/index.js import Vue from vue import VueRouter from vue-router Vue.use(VueRouter) const routes = [ { path: /, name: home, component: () => import(@/components/Home.vue), children: [{ path: /, name: hello, component: () => import(@/views/HomeView.vue) },{ path: /vue2App, name: vue2App }, { path: /vue3App, name: vue3App }, { path: /vue3App/list, name: vueAppList }, { path: /react, name: react }] } ] const router = new VueRouter({ mode: history, base: process.env.BASE_URL, routes }) export default router页面设置子应用的挂载节点
<template> <div class="wrapper"> <MyHeader></MyHeader> <el-container class="content"> <el-aside width="200px"> <MySider></MySider> </el-aside> <el-main> <router-view></router-view> <!-- 挂载子应用节点 --> <div id="micro-container"></div> </el-main> </el-container> </div> </template> <script> import MyHeader from ./Header.vue import MySider from ./Sider.vue export default { name: MyHome, components: { MyHeader, MySider }, data() { return { } } } </script> <style lang="less"> .content { height: calc(100% - 50px); } </style>在 main.js 中引入并启动 qiankun
// src/main.js import Vue from vue import App from ./App.vue import router from ./router import store from ./store import ../src/assets/style/reset.less import ./plugins/element.js import start from @/micros // 启动 start() Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount(#app)二 、微应用
微应用分为有 webpack 构建和无 webpack 构建项目 ,有 webpack 的微应用(主要是指 Vue 、React 、Angular)需要做的事情有:
新增 public-path.js 文件,用于修改运行时的 publicPath 。 微应用建议使用 history 模式的路由 ,需要设置路由 base ,值和它的 activeRule 是一样的 。 在入口文件最顶部引入 public-path.js,修改并导出三个生命周期函数 。 修改 webpack 打包 ,允许开发环境跨域和 umd 打包 。无 webpack 构建的微应用直接将 lifecycles 挂载到 window 上即可。
微应用无需安装 qiankun 。
三 、vue2-app 微应用
1⃣️ 、创建项目
// vue-cli 2.9.6 npm install vue-cli -g npm install webpack-cli -g npm init webpack vue-app2⃣️ 、在 src 目录新增 public-path.js
// src/public-path.js if(window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }修改路由文件 ,建议使用history 模式的路由 ,并设置路由 base ,值和它的 activeRule 是一样的 。
// src/router/index.js import Vue from vue import Router from vue-router import HelloWorld from @/components/HelloWorld Vue.use(Router) export default new Router({ mode: history, base: window.__POWERED_BY_QIANKUN__ ? "/vue2App" : "/", routes: [ { path: /, name: HelloWorld, component: HelloWorld } ] })入口文件 main.js 修改 ,为了避免根 id #app 与其他的 DOM 冲突 ,需要限制查找范围。并导出三个生命周期函数 。
// src/main.js import Vue from vue import App from ./App import router from ./router import "./public-path"; Vue.config.productionTip = false // 定义一个Vue实例 let instance = null // 渲染方法 function render(props = {}) { const { container } = props instance = new Vue({ router, render: (h) => h(App) }).$mount(container ? container.querySelector(#app): #app) } // 独立运行时 if(!window.__POWERED_BY_QIANKUN__) { render() } //暴露主应用生命周期钩子 /** * bootstrap : 在微应用初始化的时候调用一次 ,之后的生命周期里不再调用 */ export async function bootstrap() { console.log(vue2-app bootstraped); } /** * mount : 在应用每次进入时调用 */ export async function mount(props) { console.log(vue2-app mount, props); render(props); } /** * unmount :应用每次 切出/卸载 均会调用 */ export async function unmount() { console.log("vue2-app unmount") instance.$destroy(); instance.$el.innerHTML = ; instance = null; }修改 webpack 打包 ,允许开发环境跨域和 umd 打包 。
// build/webpack.base.conf.js use strict const config = require(../config) const APP_NAME = require(../package.json).name module.exports = { output: { path: config.build.assetsRoot, filename: [name].js, publicPath: process.env.NODE_ENV === production ? config.build.assetsPublicPath : config.dev.assetsPublicPath, // 微应用的包名 ,这里与主应用中注册的微应用名称一致 library: APP_NAME, // 将你的 library 暴露为所有的模块定义下都可运行的方式 libraryTarget: "umd", // 按需加载相关 ,设置为 webpackJsonp_VueMicroApp 即可 jsonpFunction: `webpackJsonp_${APP_NAME}`, }, ... } // build/webpack.dev.conf.js const devWebpackConfig = merge(baseWebpackConfig, { ... devServer: { ... // 关闭主机检查 ,使微应用可以被 fetch disableHostCheck: true, // 配置跨域请求头 ,解决开发环境的跨域问题 headers: { "Access-Control-Allow-Origin": "*", } } })运行效果如下:
四、vue3-app 微应用
1⃣️ 、创建项目
// @vue/cli 5.0.4 npm install @vue/cli -g vue create vue3-app2⃣️ 、在 src 目录新增 public-path.ts
// src/public-path.ts if ((window as any).__POWERED_BY_QIANKUN__) { __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }修改路由文件 。
// src/router/index.ts import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; const routes: Array<RouteRecordRaw> = [ { path: "/", name: "home", component: () => import("@/components/Home.vue"), children: [ { path: "/", name: "index", component: () => import("../views/HomeView.vue"), }, { path: "/list", name: "list", component: () => import("../views/AboutView.vue"), }, ], }, ]; const router = createRouter({ history: createWebHistory( window.__POWERED_BY_QIANKUN__ ? "/vue3App" : process.env.BASE_URL ), routes, }); export default router;入口文件 main.ts 修改
// src/main.ts import Vue, { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; import "./public-path.ts"; // 定义一个Vue实例 let instance: Vue.App<Element>; // 需要定义该接口,否则`/src/router/index.ts`无法使用`Window.__POWERED_BY_QIANKUN__` declare global { interface Window { __POWERED_BY_QIANKUN__?: string; } } interface IRenderProps { container: Element | string; } // 渲染方法 function render(props: IRenderProps) { const { container } = props; instance = createApp(App); instance .use(store) .use(router) .mount( container instanceof Element ? (container.querySelector("#app") as Element) : (container as string) ); } // 独立运行时 if (!window.__POWERED_BY_QIANKUN__) { render({ container: "#app" }); } //暴露主应用生命周期钩子 /** * bootstrap : 在微应用初始化的时候调用一次 ,之后的生命周期里不再调用 */ export async function bootstrap() { console.log("vue3-app bootstraped"); } /** * mount : 在应用每次进入时调用 */ export async function mount(props: any) { console.log("mount vue3-app", props); render(props); } /** * unmount :应用每次 切出/卸载 均会调用 */ export async function unmount() { console.log("unmount vue3-app app"); instance.unmount(); }修改 webpack 打包 ,允许开发环境跨域和 umd 打包 。
注意:webpack5 中 jsonpFunction 修改为 chunkLoadingGlobal
// vue.config.js const path = require("path"); const APP_NAME = require("./package.json").name; function resolve(dir) { return path.join(__dirname, dir); } module.exports = { outputDir: "dist", assetsDir: "static", filenameHashing: true, devServer: { host: "localhost", hot: true, port: 8082, client: { overlay: { errors: true, warnings: false, }, }, // 配置跨域请求头,解决开发环境的跨域问题 headers: { "Access-Control-Allow-Origin": "*", }, }, // 自定义webpack配置 configureWebpack: { resolve: { alias: { "@": resolve("src"), }, }, output: { // 把子应用打包成 umd 库格式 // // 微应用的包名 ,这里与主应用中注册的微应用名称一致 library: APP_NAME, // 将你的 library 暴露为所有的模块定义下都可运行的方式 libraryTarget: "umd", // 按需加载相关 ,设置为 webpackJsonp_微应用名称 即可 chunkLoadingGlobal: `webpackJsonp_${APP_NAME}`, }, }, };运行效果如下:
五、react-app 微应用
1⃣️ 、创建项目 ,以 create-react-app 生成的 react 17 项目为例 ,搭配 react-router-dom 6.x 。
npx create-react-app react-app --template typescript npm i react-router-dom在根目录下添加 .env 文件 ,设置项目监听的端口
// react-app/.env PORT=8083 BROWSER=none2⃣️ 、新建 public-path.ts
// src/public-path.ts if ((window as any).__POWERED_BY_QIANKUN__) { __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }如上面代码报错 ,可以通过补充定义进行修复 。以下代码最好放到全局引入的 TypeScript 定义文件中 。
interface Window { __POWERED_BY_QIANKUN__?: string __INJECTED_PUBLIC_PATH_BY_QIANKUN__?: string } declare let __webpack_public_path__: string | undefined设置 history 模式路由的 base
// src/App.tsx import React from react; import { Routes, Route, BrowserRouter} from react-router-dom import ./App.css; import Home from ./components/home; function App() { return ( <div> {/* 设置路由命名空间 */} <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? /react : /}> <Routes> <Route path="/" element={<Home />} /> </Routes> </BrowserRouter> </div> ); } export default App;修改入口文件 index.tsx
// src/index.tsx import React from react; import ReactDOM from react-dom; import ./index.css; import App from ./App; import reportWebVitals from ./reportWebVitals; import ./types.d.ts import "./public-path"; /** * bootstrap 只会在微应用初始化的时候调用一次 ,下次微应用重新进入时会直接调用 mount 钩子 ,不会再重复触发 bootstrap 。 * 通常我们可以在这里做一些全局变量的初始化 ,比如不会在 unmount 阶段被销毁的应用级别的缓存等 。 */ export async function bootstrap() { console.log(react-app bootstraped); } /** * 应用每次进入都会调用 mount 方法 ,通常我们在这里触发应用的渲染方法 */ export async function mount(props: any) { console.log(react-app mount); ReactDOM.render(<App />, props.container ? props.container.querySelector(#root) : document.getElementById(root)); } /** * 应用每次 切出/卸载 会调用的方法 ,通常在这里我们会卸载微应用的应用实例 */ export async function unmount(props: any) { console.log(react-app unmount); ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector(#root) : document.getElementById(root)); } /** * 可选生命周期钩子 ,仅使用 loadMicroApp 方式加载微应用时生效 */ export async function update(props: any) { console.log(react-app update props, props); } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById(root) ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();修改 webpack 配置,安装插件 react-app-rewired
npm install react-app-rewired -D修改 package.json
// react-app/package.json "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" }在 react-app-rewired 配置完成后 ,新建 config-overrides.js 文件来配置 webpack 。
// react-app/config-overrides.js const path = require("path"); const APP_NAME = require("./package.json").name; module.exports = { webpack: (config) => { // 微应用的包名 ,这里与主应用中注册的微应用名称一致 config.output.library = APP_NAME; // 将你的 library 暴露为所有的模块定义下都可运行的方式 config.output.libraryTarget = "umd"; // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可 config.output.chunkLoadingGlobal = `webpackJsonp_${APP_NAME}`; config.output.globalObject = window; config.output.publicPath = `//localhost:${process.env.PORT}/`; config.resolve.alias = { ...config.resolve.alias, "@": path.resolve(__dirname, "src"), }; return config; }, devServer: function (configFunction) { return function (proxy, allowedHost) { const config = configFunction(proxy, allowedHost); // 关闭主机检查 ,使微应用可以被 fetch // config.disableHostCheck = true; config.allowedHosts = "all"; // 配置跨域请求头 ,解决开发环境的跨域问题 config.headers = { "Access-Control-Allow-Origin": "*", }; // 配置 history 模式 config.historyApiFallback = true; return config; }; }, };3⃣️ 、运行效果如下
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!