Appearance
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 环境中处理浏览器兼容性需要:
- 统一配置: 建立共享的兼容性配置
- 差异化策略: 为不同包设置不同的兼容性目标
- 自动化检查: 在 CI/CD 中集成兼容性验证
- 性能优化: 根据兼容性需求优化构建输出
- 持续监控: 监控实际使用中的兼容性问题
通过这些策略,可以在 Monorepo 环境中有效地管理多个前端包的浏览器兼容性需求。