QianKun 前端微服务技术深度解析

一、沙箱机制

1.1 原理

全局变量隔离:

  • 通过代理(Proxy)对象来拦截和管理全局变量(如 window 对象)的读写操作,实现全局变量的隔离
  • 当微应用尝试访问或修改全局变量时,沙箱会捕获这些操作并进行处理,确保不会影响其他微应用

样式隔离:

  • 乾坤使用 Shadow DOM 或 scoped CSS 来隔离微应用的样式,防止样式冲突
  • 对于不支持 Shadow DOM 的浏览器,乾坤会通过 CSS 前缀或其他方式来实现样式隔离

事件隔离:

  • 乾坤会拦截和管理全局事件(如 click、resize 等),确保事件不会跨微应用传播
  • 通过事件代理和事件委托,实现事件的精确控制和隔离

生命周期管理:

  • 乾坤为每个微应用定义了详细的生命周期钩子,包括 bootstrap、mount 和 unmount,确保微应用在不同阶段的行为可控
  • 在 unmount 阶段,乾坤会清理微应用的全局变量、事件监听器等,确保微应用卸载后不会留下残留

1.2 代码实现实例

这段代码实现了一个简单的沙箱类 Sandbox,用于隔离微应用的全局变量操作,避免对原始 window 对象产生影响。

javascript

// 沙箱类 class Sandbox { constructor() { this.originalWindow = window; // 保存原始的 window 对象 this.proxyWindow = new Proxy(window, { get: (target, key) => { // 检查是否已经存在隔离的变量 if (this[key] !== undefined) { return this[key]; } return target[key]; }, set: (target, key, value) => { // 检查是否已经存在隔离的变量 if (this[key] !== undefined) { this[key] = value; return true; } target[key] = value; return true; } }); } activate() { // 激活沙箱,将 window 替换为 proxyWindow window = this.proxyWindow; } deactivate() { // 恢复原始的 window 对象 window = this.originalWindow; } clear() { // 清理沙箱中的所有变量 for (const key in this) { if (this.hasOwnProperty(key) && key !== 'originalWindow' && key !== 'proxyWindow') { delete this[key]; } } } } // 使用沙箱 const sandbox = new Sandbox(); // 激活沙箱 sandbox.activate(); // 模拟微应用的全局变量操作 window.myVar = 'Hello, Qiankun!'; // 检查沙箱中的全局变量 console.log(sandbox.myVar); // 输出: Hello, Qiankun! // 恢复原始的 window 对象 sandbox.deactivate(); // 清理沙箱 sandbox.clear(); // 检查原始的 window 对象 console.log(window.myVar); // 输出: undefined

代码解读:

  1. 类的定义和构造函数
    • class Sandbox 定义了一个名为 Sandbox 的类,用于创建沙箱实例
    • 构造函数中保存原始的 window 对象并创建 Proxy 代理对象
  2. Proxy 拦截器
    • get 拦截器:当访问 proxyWindow 的属性时,如果沙箱中已存在该属性,则返回沙箱中的值
    • set 拦截器:当给 proxyWindow 的属性赋值时,如果沙箱中已存在该属性,则赋值给沙箱中的属性
  3. 生命周期方法
    • activate():激活沙箱,将全局 window 替换为代理对象
    • deactivate():停用沙箱,恢复原始 window 对象
    • clear():清理沙箱中所有自定义变量

二、样式隔离原理

2.1 Shadow DOM 隔离

利用浏览器原生支持的 Shadow DOM API,为子应用创建独立的 DOM 子树,其内部样式与外部完全隔离。

javascript

// 主应用配置子应用 registerMicroApps([ { name: 'subApp', entry: '//localhost:7100', container: '#subapp-container', activeRule: '/sub-app', props: { sandbox: { strictStyleIsolation: true // 启用 Shadow DOM } } }, ]);

优点:

  • 强隔离性:Shadow DOM 内部的 CSS 选择器不会影响外部,反之亦然
  • 原生支持:无需额外处理 CSS,由浏览器保证隔离性

缺点:

  • 兼容性限制:部分旧浏览器不支持(如 IE11)
  • UI 组件穿透问题:如 Ant Design 的 Modal 组件可能因挂载到 body 导致样式失效
  • 事件穿透复杂:需手动处理 Shadow DOM 内外的事件通信

2.2 Scoped CSS

乾坤通过动态重写子应用的 CSS 规则,为每个选择器添加唯一前缀,将样式限制在子应用容器内。

javascript

// 主应用配置 start({ sandbox: { experimentalStyleIsolation: true // 启用动态作用域 } });

优点:

  • 无侵入性:无需修改子应用代码,乾坤自动处理样式作用域
  • 兼容性佳:支持所有浏览器
  • 灵活可控:支持动态加载/卸载子应用样式表

缺点:

  • 性能损耗:动态重写 CSS 可能影响大型应用的加载性能
  • 动态样式失效:通过 JavaScript 插入的样式需额外处理

技术对比与选型建议

方案适用场景注意事项
Shadow DOM高隔离需求、现代浏览器环境处理全局组件(如弹窗)、事件通信
动态样式表作用域兼容旧浏览器、快速迁移现有项目监控动态插入样式、性能优化

乾坤样式隔离的实现细节

CSS 重写机制:
乾坤劫持 document.createElement 等方法,在子应用加载时解析其 CSS 文本,通过正则匹配重写选择器:

css

/* 原始CSS */ .button { color: red; } /* 重写后 */ [data-qiankun-subapp] .button { color: red; }

样式卸载策略:
子应用卸载时,乾坤自动移除其动态注入的 <style> 标签,避免残留样式影响。

第三方库适配:
针对 UI 库(如 Ant Design)的全局样式,可通过配置 excludeAssetFilter 排除特定资源,或在子应用内使用定制前缀。

常见问题与解决方案

问题1:弹窗组件样式失效

javascript

// 解决方案:将弹窗挂载到子应用容器内 Modal.config({ rootSelector: '#subapp-container' });

问题2:动态加载样式丢失
解决方案:监听 DOMNodeInserted 事件,对新增 <style> 标签自动重写。

问题3:字体图标跨域问题
解决方案:在主应用配置跨域头,或通过乾坤的 fetch 劫持重定向资源路径。

三、如何同时启动子应用

3.1 开发环境

3.1.1 主应用配置

安装依赖:

bash

npm install qiankun

主应用配置(以 React 为例):

javascript

// index.js import React from 'react'; import ReactDOM from 'react-dom/client'; import { registerMicroApps, start } from 'qiankun'; // 注册子应用 registerMicroApps([ { name: 'sub-app1', // 子应用名称 entry: '//localhost:8081', // 子应用入口地址 container: '#sub-app1-container', // 子应用挂载节点 activeRule: '/sub-app1', // 子应用激活规则 }, { name: 'sub-app2', entry: '//localhost:8082', container: '#sub-app2-container', activeRule: '/sub-app2', }, // 可以根据需要添加更多子应用配置 ]); // 启动 qiankun start(); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);

3.1.2 子应用配置

子应用的 package.json:

json

{ "scripts": { "start": "webpack-dev-server --config webpack.config.js" } }

3.1.3 同时启动多个应用

使用 concurrently 工具同时启动主应用和多个子应用:

安装 concurrently:

bash

npm install concurrently --save-dev

主应用的 package.json:

json

{ "scripts": { "start:main": "webpack-dev-server --config webpack.config.js", "start:sub-app1": "cd ../sub-app1 && npm start", "start:sub-app2": "cd ../sub-app2 && npm start", "start": "concurrently \"npm run start:main\" \"npm run start:sub-app1\" \"npm run start:sub-app2\"" } }

3.2 生产环境

  1. 打包子应用: 在每个子应用项目目录下运行打包命令
  2. 部署子应用: 将打包后的静态资源部署到服务器
  3. 打包主应用: 打包主应用并部署,确保能正确访问子应用资源地址
  4. 主应用配置与开发环境基本一致,通过 registerMicroApps 注册子应用并启动 qiankun

四、子应用之间通信

4.1 通过主应用作为中间媒介通信

主应用可以作为不同子应用之间通信的桥梁,利用 qiankun 的 props 机制传递数据和事件。

主应用配置:

javascript

import { registerMicroApps, start } from 'qiankun'; // 定义通信对象 const communication = { data: {}, setData: (key, value) => { communication.data[key] = value; }, getData: (key) => { return communication.data[key]; } }; registerMicroApps([ { name: 'subApp1', entry: '//localhost:8081', container: '#sub-app1', activeRule: '/sub-app1', props: { communication } }, { name: 'subApp2', entry: '//localhost:8082', container: '#sub-app2', activeRule: '/sub-app2', props: { communication } } ]); start();

子应用使用通信对象:

javascript

// 子应用 1 export async function mount(props) { const { communication } = props; // 设置数据 communication.setData('message', 'Hello from subApp1'); } // 子应用 2 export async function mount(props) { const { communication } = props; // 获取数据 const message = communication.getData('message'); console.log(message); }

4.2 使用全局事件总线

创建全局的事件总线,让不同的子应用都可以监听和触发事件。

创建全局事件总线:

javascript

import { EventEmitter } from 'events'; // 创建事件总线 const eventBus = new EventEmitter(); registerMicroApps([ { name: 'subApp1', entry: '//localhost:8081', container: '#sub-app1', activeRule: '/sub-app1', props: { eventBus } }, { name: 'subApp2', entry: '//localhost:8082', container: '#sub-app2', activeRule: '/sub-app2', props: { eventBus } } ]); start();

子应用监听和触发事件:

javascript

// 子应用 1 export async function mount(props) { const { eventBus } = props; // 触发事件 eventBus.emit('message', 'Hello from subApp1'); } // 子应用 2 export async function mount(props) { const { eventBus } = props; // 监听事件 eventBus.on('message', (message) => { console.log(message); }); } // 卸载时清理事件监听 export async function unmount(props) { const { eventBus } = props; eventBus.removeAllListeners('message'); }

4.3 使用浏览器的本地存储或会话存储

利用浏览器的 localStorage 或 sessionStorage 在不同子应用之间共享数据。

子应用存储数据:

javascript

// 子应用 1 function sendData() { localStorage.setItem('message', 'Hello from subApp1'); }

子应用获取数据:

javascript

// 子应用 2 function receiveData() { const message = localStorage.getItem('message'); console.log(message); }

注意事项:

  • 数据同步问题:需要注意不同子应用在不同时间点读取和写入数据的同步问题
  • 事件清理:使用事件总线时,要在子应用卸载时清理事件监听,避免内存泄漏

五、实战指南

5.1 开发环境

在 qiankun 微前端架构里,子应用以 mount 函数形式暴露其挂载逻辑。虽然代码里没有直接调用 mount 函数,但 qiankun 主应用会在合适时机自动调用它来启动子应用。

qiankun 自动调用 mount 函数机制:

  1. 加载子应用资源:根据子应用的 entry 地址,加载子应用打包后的资源
  2. 调用 mount 函数:加载完资源后,qiankun 会自动调用子应用导出的 mount 函数,并传递 props 参数

主应用代码示例:

javascript

import { registerMicroApps, start } from 'qiankun'; // 注册子应用 registerMicroApps([ { name: 'vue-sub-app', entry: '//localhost:8082', // 子应用入口地址 container: '#vue-sub-app-container', // 子应用挂载容器 activeRule: '/vue-sub-app', // 子应用激活规则 props: { // 可以传递一些数据或事件总线等 someData: '这是主应用传递给子应用的数据' } } ]); // 启动 qiankun start();

Vue 子应用代码示例:

javascript

import Vue from 'vue'; import App from './App.vue'; export async function mount(props) { const { someData } = props; // 监听事件示例 const handleMessage = (message) => { console.log('Received message in Vue sub-app:', message); }; // 触发事件示例 const sendMessage = () => { // eventBus.emit('message', 'Hello from Vue sub-app'); }; new Vue({ render: (h) => h(App, { props: { sendMessage, someData } }) }).$mount('#app'); return () => { // 子应用卸载时移除事件监听 // eventBus.removeListener('message', handleMessage); }; }

开发与测试流程:

  1. 启动子应用:在子应用项目目录下运行启动命令
  2. 启动主应用:在主应用项目目录下运行启动命令
  3. 访问子应用:在浏览器中访问主应用,当 URL 匹配子应用的 activeRule 时自动加载

5.2 打包后 mount 函数名问题

使用 Webpack 打包时,默认情况下 mount 函数名不会被修改。但如果使用了混淆压缩工具,可能会导致函数名被修改。

避免函数名被修改的配置:

javascript

// webpack.config.js const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({ terserOptions: { keep_classnames: true, keep_fnames: true // 保留函数名 } }) ] } };

5.3 应用判断 qiankun 环境

Vue 子应用入口文件示例:

javascript

import Vue from 'vue'; import App from './App.vue'; // 判断是否处于 qiankun 环境 if (!window.__POWERED_BY_QIANKUN__) { // 独立运行时,直接渲染应用 new Vue({ render: h => h(App) }).$mount('#app'); } else { // 作为 qiankun 子应用运行时,导出生命周期函数 let instance; export async function mount(props) { const { container } = props; instance = new Vue({ render: h => h(App) }).$mount(container ? container.querySelector('#app') : '#app'); } export async function unmount() { instance.$destroy(); instance.$el.innerHTML = ''; instance = null; } }

详细解释:

  1. 独立运行逻辑: 当 window.__POWERED_BY_QIANKUN__ 为 false 时,表示独立运行,直接创建并挂载 Vue 实例
  2. qiankun 子应用逻辑: 当 window.__POWERED_BY_QIANKUN__ 为 true 时,导出 mount 和 unmount 生命周期函数
    • mount 函数:接收 props 参数,创建 Vue 实例并挂载到指定节点
    • unmount 函数:销毁 Vue 实例并清空挂载节点内容

通过以上详细解析,我们可以看到 QianKun 微前端框架提供了完整的解决方案,包括沙箱隔离、样式隔离、多应用启动和通信机制。在实际项目中,需要根据具体需求选择合适的配置方案,并注意处理各种边界情况和兼容性问题。

Read more

踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录

踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录

目录 * WordPress中要点,域和托管 * 域名 * 托管 * 添加新页面 * 添加新文章 * 安装方式 * 1. 接口清单(API Design) * 2. Controller 层实现 * 3. Service 层实现 * 4. Mapper 层(MyBatis-Plus) * (1) 好友关系实体 * (2) Mapper接口 * 5. 统一返回结构 * 6. 接口测试示例 * **(1) 添加好友** * **(2) 查询好友列表** * **关键设计说明** * **扩展建议** * 为什么需要为数据库的 email 字段建立索引 * 1. 提高查询性能 * 2. 保证数据唯一性(当需要时) * 3. 支持高级查询特性 * 注意事项 * 实际应用示例 * 关于前端使用openapi报错原因 * 解决方案

Windows下载、安装并运行MinIO,访问WebUI界面

Windows下载、安装并运行MinIO,访问WebUI界面

MinIO MinIO 是一款基于 Apache License v2.0 开源协议的对象存储服务,兼容 Amazon S3 云存储服务接口,可用于存储海量非结构化数据(如图片、视频、日志文件等)。本教程针对 Windows 系统搭建本地 MinIO 服务,适合开发测试、小型项目部署场景。 下载MinIO 官网下载 访问MinIO中文官网或MinIO英文官网,根据读者的操作系统选择相应的操作系统版本点击MinIO Server/AIStor Server和MinIO Client/AIStor Client的Download按钮下载对应文件。 说明:两版官网域名不同,Server/Client 的文字标题有差异,但下载文件一致;中文官网下载速度更快,优先推荐。 网盘下载 通过网盘分享的文件:Minio 链接: https://pan.baidu.com/s/

【前端】使用Vue3过程中遇到加载无效设置点击方法提示不存在的情况,原来是少加了一个属性

【前端】使用Vue3过程中遇到加载无效设置点击方法提示不存在的情况,原来是少加了一个属性

🌹欢迎来到《小5讲堂》🌹 🌹这是《前端》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 目录 * 前言 * 提示报错 * 问题分析 * 1. **Options API vs Composition API 风格差异** * ✅ **Options API 写法(方法直接放在外面)** * ✅ **Composition API 写法(方法必须在 setup 中定义)** * ✅ **`<script setup>` 语法糖(最简洁的 Composition API)** * 2. **为什么你的代码会报错?** * 3. **解决方案** * 方案 1:改用 **Options API**(适合从 Vue

从2025看2026前端发展趋势

🎨 从2025看2026前端发展趋势 一、📌 核心前言(2025铺垫→2026展望) 2025年前端行业已完成“基础成熟化”:Vue3、React18成为主流,TypeScript全面普及,工程化流程趋于完善,AI工具开始渗透开发环节,但也暴露了痛点——开发效率不均衡、跨端体验不一致、AI与业务结合浅显、性能优化门槛高。 ✨ 核心趋势:2026年前端将从「基础成熟」走向「深度融合」,重点围绕「AI原生开发」「跨端统一」「性能极致」「工程化提效」四大方向突破,同时Node.js等底层工具的升级(如2026年Node.js新特性)将进一步推动前端向全栈化、平台化转型。 二、✍️ 五大核心趋势(手绘重点·结合2025现状) 1. AI原生开发:从“辅助工具”到“核心生产力” 🤖(最重磅) (1)2025现状 2025年,前端AI工具多为“辅助层面”