Skip to content
On this page

Monorepo 与浏览器兼容性

概述

虽然 Monorepo 本身是开发工具概念,不直接涉及浏览器兼容性,但在 Monorepo 环境中管理前端项目时,浏览器兼容性仍然是一个重要考虑因素。本章将探讨如何在 Monorepo 环境中处理多个前端包的浏览器兼容性问题。

多包环境中的兼容性挑战

1. 统一兼容性标准

在 Monorepo 中,多个前端包可能需要支持不同的浏览器版本:

json
// packages/ui-components/package.json
{
  "name": "@myorg/ui-components",
  "browserslist": [
    "defaults",
    "not IE 11"
  ]
}

// packages/legacy-app/package.json
{
  "name": "@myorg/legacy-app", 
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "Firefox ESR",
    "not dead",
    "IE 11"
  ]
}

2. 共享配置管理

创建共享的浏览器兼容性配置:

javascript
// packages/browserslist-config/index.js
module.exports = {
  modern: [
    "last 2 Chrome versions",
    "last 2 Firefox versions", 
    "last 2 Safari versions",
    "last 2 Edge versions"
  ],
  broad: [
    "> 0.5%",
    "last 3 versions",
    "Firefox ESR",
    "not dead",
    "IE 11"
  ]
};

构建工具配置

1. Babel 配置

在 Monorepo 中统一 Babel 配置:

javascript
// babel.config.js (root)
module.exports = {
  // 共享配置
  presets: [
    ['@babel/preset-env', {
      targets: {
        browsers: require('./packages/browserslist-config').broad
      },
      useBuiltIns: 'usage',
      corejs: 3
    }]
  ],
  // 按包定制
  overrides: [
    {
      test: './packages/modern-app',
      presets: [
        ['@babel/preset-env', {
          targets: require('./packages/browserslist-config').modern
        }]
      ]
    }
  ]
};

2. TypeScript 配置

共享的 TypeScript 配置:

json
// tsconfig.base.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  }
}

3. PostCSS 和 CSS 处理

javascript
// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')({
      overrideBrowserslist: [
        'last 2 versions',
        '> 1%',
        'ie >= 11'
      ]
    }),
    require('postcss-preset-env')({
      stage: 3,
      features: {
        'nesting-rules': true
      }
    })
  ]
};

包级别的兼容性策略

1. 不同包的差异化处理

javascript
// packages/modern-lib/src/index.js
// 现代库,使用较新的 API
export class ModernComponent {
  constructor() {
    this.data = new Map(); // 使用现代 API
    this.events = new Set(); // 使用现代 API
  }
}

// packages/legacy-lib/src/index.js
// 传统库,兼容旧浏览器
export class LegacyComponent {
  constructor() {
    this.data = {}; // 使用传统方法
    this.events = []; // 使用传统方法
  }
}

2. 兼容性入口点

为不同兼容性需求提供不同入口点:

json
// package.json
{
  "name": "@myorg/component-lib",
  "main": "dist/legacy/index.js",
  "module": "dist/modern/index.js", 
  "browser": "dist/browser/index.js",
  "exports": {
    ".": {
      "modern": "./dist/modern/index.js",
      "import": "./dist/legacy/index.js",
      "require": "./dist/legacy.cjs"
    }
  }
}

测试策略

1. 多浏览器测试

在 Monorepo 中设置多浏览器测试:

javascript
// packages/test-utils/src/browser-test.js
const browsers = [
  { name: 'chrome', version: 'latest' },
  { name: 'firefox', version: 'latest' },
  { name: 'safari', version: 'latest' },
  { name: 'ie', version: '11' }
];

export function runBrowserTests() {
  browsers.forEach(browser => {
    // 为每个浏览器运行测试
    runTestsForBrowser(browser);
  });
}

2. 兼容性检查脚本

javascript
// scripts/check-compatibility.js
const { execSync } = require('child_process');

function checkCompatibility() {
  // 检查 ES 特性兼容性
  const esCompatibility = execSync('npx browserslist-useragent-cli "My App v1.0"');
  
  // 检查 CSS 兼容性
  const cssCompatibility = execSync('npx csscompat --config .browserslistrc');
  
  return { esCompatibility, cssCompatibility };
}

module.exports = { checkCompatibility };

CI/CD 中的兼容性检查

1. 自动化兼容性验证

yaml
# .github/workflows/compatibility.yml
name: Compatibility Check
on: [push, pull_request]

jobs:
  compatibility:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x, 18.x]
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm check:compatibility
      - run: pnpm test:browsers

2. 兼容性报告生成

json
// package.json
{
  "scripts": {
    "check:compatibility": "browserslist && npx compat-report",
    "test:browsers": "npx testcafe 'chrome,firefox,safari' packages/*/src/**/__tests__/*.test.js"
  }
}

Polyfill 管理

1. 共享 Polyfill 包

创建共享的 Polyfill 包:

javascript
// packages/polyfills/src/index.js
// 条件性加载 Polyfill
if (!window.Promise) {
  require('es6-promise/auto');
}

if (!window.fetch) {
  require('whatwg-fetch');
}

if (!window.Map || !window.Set) {
  require('core-js/es/map');
  require('core-js/es/set');
}

2. 按需加载 Polyfill

javascript
// packages/polyfills/src/modern.js
// 现代浏览器的轻量级 Polyfill
export { default as Promise } from 'es6-promise';
javascript
// packages/polyfills/src/legacy.js
// 传统浏览器的完整 Polyfill
import 'core-js/stable';
import 'regenerator-runtime/runtime';

性能考虑

1. 代码分割

根据浏览器兼容性进行代码分割:

javascript
// webpack.config.js
module.exports = {
  entry: {
    'modern': './src/modern-entry.js',
    'legacy': './src/legacy-entry.js'
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        // 根据浏览器兼容性分割代码
        modern: {
          test: /modern/,
          name: 'modern-vendor',
          chunks: 'all'
        },
        legacy: {
          test: /legacy/,
          name: 'legacy-vendor',
          chunks: 'all'
        }
      }
    }
  }
};

2. 动态导入

根据浏览器能力动态导入:

javascript
// packages/dynamic-loader/src/index.js
export async function loadModule(moduleName) {
  // 检查浏览器功能
  if (supportsESModules()) {
    return import(`./esm/${moduleName}.js`);
  } else {
    return import(`./cjs/${moduleName}.js`);
  }
}

function supportsESModules() {
  try {
    return Boolean(new Function('return import.meta')());
  } catch (e) {
    return false;
  }
}

工具和库

1. Browserslist

在根目录配置:

# .browserslistrc
# 生产环境
> 0.5%
last 2 versions
Firefox ESR
not dead

# 开发环境
last 1 chrome version
last 1 firefox version

2. Core-js 配置

json
// package.json
{
  "browserslist": [
    "last 2 versions",
    "> 1%",
    "ie >= 11"
  ],
  "corejs": {
    "version": 3,
    "proposals": true
  }
}

最佳实践

1. 渐进增强

在 Monorepo 中实现渐进增强:

javascript
// packages/progressive-enhancement/src/index.js
export class ProgressiveComponent {
  constructor(element) {
    this.element = element;
    this.initBaseFeatures();
    this.addEnhancedFeatures();
  }
  
  initBaseFeatures() {
    // 基础功能,所有浏览器支持
    this.element.addEventListener('click', this.handleClick);
  }
  
  addEnhancedFeatures() {
    // 增强功能,现代浏览器支持
    if ('IntersectionObserver' in window) {
      this.setupObserver();
    }
  }
}

2. 功能检测

javascript
// packages/feature-detection/src/index.js
export const FeatureDetection = {
  webP: () => {
    const canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = 1;
    return canvas.toDataURL('image/webp').indexOf('webp') > -1;
  },
  
  es6: () => {
    try {
      new Function('(a = 0) => a');
      return true;
    } catch (err) {
      return false;
    }
  }
};

监控和报告

1. 兼容性监控

javascript
// packages/compatibility-monitor/src/index.js
export class CompatibilityMonitor {
  static trackErrors() {
    window.addEventListener('error', (event) => {
      this.reportError({
        message: event.error.message,
        stack: event.error.stack,
        userAgent: navigator.userAgent,
        url: window.location.href
      });
    });
  }
  
  static reportError(error) {
    // 发送错误报告到监控服务
    fetch('/api/errors', {
      method: 'POST',
      body: JSON.stringify(error)
    });
  }
}

总结

在 Monorepo 环境中处理浏览器兼容性需要:

  1. 统一配置: 建立共享的兼容性配置
  2. 差异化策略: 为不同包设置不同的兼容性目标
  3. 自动化检查: 在 CI/CD 中集成兼容性验证
  4. 性能优化: 根据兼容性需求优化构建输出
  5. 持续监控: 监控实际使用中的兼容性问题

通过这些策略,可以在 Monorepo 环境中有效地管理多个前端包的浏览器兼容性需求。