微前端架构实战:qiankun模块联邦与独立部署方案

微前端(Micro Frontend)将微服务理念延伸到前端领域,将大型单体前端应用拆分为多个独立开发、独立部署、独立运行的子应用。当单体前端项目代码量超过50万行、构建时间超过10分钟、多团队协作冲突频繁时,微前端架构能有效解决这些问题。qiankun是蚂蚁集团开源的微前端框架,基于Single-SPA实现,提供JS沙箱、CSS隔离、预加载等开箱即用的能力。

qiankun微前端架构设计

qiankun采用主子应用模式:主应用(Container)负责注册子应用、管理路由切换和公共依赖;子应用独立开发部署,通过导出生命周期函数接入主应用。主应用加载子应用时动态拉取子应用的HTML入口,执行JS并挂载到指定容器:

// 主应用:注册子应用
import { registerMicroApps, start } from 'qiankun';

// 子应用配置
const microApps = [
  {
    name: 'user-center',
    entry: '//localhost:8081',
    container: '#sub-app-container',
    activeRule: '/user',
    props: {
      token: localStorage.getItem('token'),
      apiBase: '/api/v1'
    }
  },
  {
    name: 'order-system',
    entry: '//localhost:8082',
    container: '#sub-app-container',
    activeRule: '/order',
    props: {
      token: localStorage.getItem('token'),
      apiBase: '/api/v1'
    }
  },
  {
    name: 'dashboard',
    entry: '//localhost:8083',
    container: '#sub-app-container',
    activeRule: '/dashboard'
  }
];

registerMicroApps(microApps, {
  beforeLoad: [
    (app) => {
      console.log(`${app.name} 开始加载`);
      // 可在此处添加loading状态
    }
  ],
  beforeMount: [
    (app) => {
      console.log(`${app.name} 开始挂载`);
    }
  ],
  afterUnmount: [
    (app) => {
      console.log(`${app.name} 已卸载`);
      // 清理全局变量、事件监听等
    }
  ]
});

// 启动微前端,开启预加载和沙箱
start({
  prefetch: true,        // 预加载子应用资源
  sandbox: {
    strictStyleIsolation: false,  // 严格样式隔离(Shadow DOM)
    experimentalStyleIsolation: true  // 实验性样式隔离(Scoped CSS)
  },
  singular: true  // 同时只激活一个子应用
});

子应用接入与生命周期导出

子应用需导出bootstrap、mount、unmount三个生命周期函数。以Vue3子应用为例,需配置webpack的publicPath和UMD导出:

// 子应用入口文件 main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

let app = null;

// qiankun生命周期导出
export async function bootstrap(props) {
  console.log('子应用启动', props);
  // 可通过props接收主应用传入的数据
}

export async function mount(props) {
  app = createApp(App);
  app.use(router);
  app.use(store);
  
  // 将主应用传入的props挂载到全局
  if (props.token) {
    store.commit('setToken', props.token);
  }
  if (props.apiBase) {
    store.commit('setApiBase', props.apiBase);
  }
  
  app.mount(props.container 
    ? props.container.querySelector('#app') 
    : '#app');
}

export async function unmount(props) {
  app.unmount();
  app = null;
  // 清理副作用:定时器、全局事件、WebSocket等
}

// 非qiankun环境独立运行
if (!window.__POWERED_BY_QIANKUN__) {
  createApp(App).use(router).use(store).mount('#app');
}

子应用webpack配置需添加publicPath动态设置和UMD导出:

// vue.config.js 或 webpack.config.js
const { name } = require('./package.json');

module.exports = {
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`
    },
    devServer: {
      port: 8081,
      headers: {
        'Access-Control-Allow-Origin': '*'  // 允许主应用跨域加载
      }
    }
  }
};

// 入口文件头部动态设置publicPath
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

主子应用通信机制

qiankun提供initGlobalState API实现应用间通信,基于发布订阅模式。主应用创建全局状态,子应用通过props.onGlobalStateChange监听变化,通过props.setGlobalState修改状态:

// 主应用:初始化全局状态
import { initGlobalState } from 'qiankun';

const initialState = {
  user: null,
  theme: 'light',
  language: 'zh-CN'
};

const actions = initGlobalState(initialState);

// 监听子应用的状态变更
actions.onGlobalStateChange((state, prev) => {
  console.log('全局状态变更:', state);
  if (state.theme) {
    document.documentElement.setAttribute('data-theme', state.theme);
  }
});

// 主应用主动设置状态
actions.setGlobalState({
  user: { id: 1, name: 'admin', role: 'super' }
});

// 将actions传入子应用
registerMicroApps([{
  name: 'user-center',
  entry: '//localhost:8081',
  container: '#sub-app-container',
  activeRule: '/user',
  props: {
    // 通信方法通过props传入
    onGlobalStateChange: actions.onGlobalStateChange,
    setGlobalState: actions.setGlobalState
  }
}]);

// 子应用:接收并使用通信方法
export async function mount(props) {
  // 监听全局状态变化
  props.onGlobalStateChange((state) => {
    console.log('收到主应用状态:', state);
    if (state.user) {
      store.commit('setUser', state.user);
    }
  });
  
  // 子应用修改全局状态
  props.setGlobalState({ theme: 'dark' });
  
  app.mount(props.container.querySelector('#app'));
}

Webpack Module Federation方案

Webpack 5原生支持的Module Federation提供了另一种微前端方案。与qiankun不同,Module Federation在构建时声明模块暴露和消费关系,运行时动态加载远程模块,无需HTML入口文件注入:

// 模块提供方 webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'userApp',
      filename: 'remoteEntry.js',
      exposes: {
        // 暴露的组件
        './UserList': './src/components/UserList.vue',
        './UserDetail': './src/components/UserDetail.vue',
        './store': './src/store/index.js'
      },
      shared: {
        vue: { singleton: true, requiredVersion: '^3.3.0' },
        'vue-router': { singleton: true }
      }
    })
  ]
};

// 模块消费方 webpack.config.js
new ModuleFederationPlugin({
  name: 'host',
  remotes: {
    userApp: 'userApp@http://localhost:8081/remoteEntry.js',
    orderApp: 'orderApp@http://localhost:8082/remoteEntry.js'
  },
  shared: {
    vue: { singleton: true, requiredVersion: '^3.3.0' }
  }
})

// 消费方动态加载远程组件
const UserList = defineAsyncComponent(() => 
  import('userApp/UserList')
);

Module Federation的优势在于共享依赖(shared配置),多个子应用引用同一版本Vue、React等库时不会重复打包。但Module Federation要求所有应用使用Webpack 5+,且不支持运行时沙箱隔离,样式冲突需自行处理。

样式隔离与资源预加载

qiankun提供两种样式隔离策略。严格样式隔离使用Shadow DOM将子应用包裹在独立Shadow Root中,完全隔离CSS但不影响全局样式。实验性样式隔离通过给子应用CSS选择器添加scoped前缀实现近似隔离:

// start配置启用样式隔离
start({
  sandbox: {
    // 方案一:Shadow DOM完全隔离
    // strictStyleIsolation: true,
    // 问题:第三方UI库(Ant Design等)可能因Shadow DOM 
    //       无法获取全局样式而样式异常
    
    // 方案二:Scoped CSS(推荐)
    experimentalStyleIsolation: true
    // 原理:给子应用所有CSS选择器添加 div[data-qiankun="app-name"] 前缀
    // 效果:.ant-btn { } => div[data-qiankun="user-center"] .ant-btn { }
  },
  prefetch: {
    // 预加载策略:首次访问子应用时预加载其他子应用静态资源
    getPublicPath: (entry) => {
      return entry.replace(/\/[^/]*$/, '/');
    }
  }
});

// 处理全局样式冲突的实践
// 子应用在mount时注入全局样式,unmount时移除
export async function mount(props) {
  const styleEl = document.createElement('style');
  styleEl.setAttribute('data-app', 'user-center');
  styleEl.textContent = `
    body { margin: 0; font-family: 'PingFang SC', sans-serif; }
  `;
  document.head.appendChild(styleEl);
  app.mount(props.container.querySelector('#app'));
}

export async function unmount() {
  // 清理子应用注入的全局样式
  document.querySelectorAll('style[data-app="user-center"]').forEach(el => el.remove());
  app.unmount();
}

部署架构与Nginx配置

生产部署中主应用和子应用分别独立部署到不同域名或路径。Nginx需配置CORS允许主应用跨域加载子应用资源:

# Nginx配置:子应用静态资源服务
server {
    listen 8081;
    server_name user-app.internal;
    
    # 允许主应用域名跨域访问
    add_header Access-Control-Allow-Origin "https://portal.yunthe.com";
    add_header Access-Control-Allow-Methods "GET, OPTIONS";
    add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept";
    
    location / {
        root /usr/share/nginx/html/user-app;
        try_files $uri $uri/ /index.html;
        
        # 静态资源缓存
        location ~* \.(js|css|png|jpg|svg)$ {
            expires 30d;
            add_header Cache-Control "public, immutable";
        }
    }
}

# 主应用配置
server {
    listen 443 ssl;
    server_name portal.yunthe.com;
    
    location / {
        root /usr/share/nginx/html/portal;
        try_files $uri $uri/ /index.html;
    }
    
    # API代理
    location /api/ {
        proxy_pass http://backend-cluster;
    }
}

qiankun和Module Federation各有适用场景。qiankun适合技术栈异构(Vue+React混用)、独立部署要求强、样式隔离要求高的场景。Module Federation适合同技术栈(全Vue或全React)、构建时集成、共享依赖优化的场景。大型项目可采用混合方案:qiankun管理应用级隔离和路由,Module Federation处理跨应用的组件级共享。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/wei-qian-duan-jia-gou-shi-zhan-qiankun-mo-kuai-lian-bang-yu/

(0)
小编小编
上一篇 1天前
下一篇 1天前

相关推荐