Browser Compatibility Modern Implementation
1. Babel Polyfill Core-js ES6+ Support
| Tool/Feature | Purpose | Configuration | Best Practices |
|---|---|---|---|
| Babel | Transpile modern JS to ES5. Transform JSX, TypeScript. Plugin ecosystem. Source maps | .babelrc or babel.config.js. Presets: @babel/preset-env, -react, -typescript
|
Use preset-env with browserslist. Enable modules: false for tree-shaking. Source maps in dev |
| @babel/preset-env | Smart transpilation based on target browsers. Only polyfill what's needed. Auto browser detection | targets: { browsers: ['>0.5%', 'not dead'] }. useBuiltIns: 'usage' or 'entry' |
Use useBuiltIns: 'usage' for optimal size. Set browserslist in package.json. Update targets yearly |
| core-js | Polyfill library. ES6+ features, Promise, Symbol, Array methods, Object methods, 90KB minified | npm install core-js@3. Import: import 'core-js/stable' or auto with useBuiltIns
|
Use core-js@3. Automatic imports with babel useBuiltIns: 'usage'. Only polyfill used features |
| Polyfill Strategies | Entry point, Usage-based, Feature detection, Polyfill.io service, Conditional loading | Usage: smallest bundle. Entry: explicit control. Polyfill.io: CDN-based, dynamic | Usage-based for apps. Entry for libraries. Polyfill.io for multi-browser. Test without polyfills |
| Modern Features Support | async/await, Promises, Class properties, Optional chaining, Nullish coalescing, BigInt | Babel transforms syntax. core-js polyfills APIs. regenerator-runtime for async/await | Check caniuse.com. Feature detection before use. Graceful degradation. Progressive enhancement |
| Bundle Size Impact | Polyfills add 20-50KB. Target modern browsers. Use differential loading. Monitor size | webpack-bundle-analyzer. Monitor polyfill size. Exclude from modern bundle | Target last 2 versions. 20KB polyfills for legacy. 0KB for modern. Use type="module" |
Example: Babel configuration with optimal polyfilling
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
// Target browsers from browserslist
targets: {
browsers: [
'>0.5%',
'not dead',
'not op_mini all',
'not IE 11'
]
},
// Automatic polyfill injection based on usage
useBuiltIns: 'usage',
// Use core-js version 3
corejs: { version: 3, proposals: true },
// Preserve ES modules for tree-shaking
modules: false,
// Include regenerator for async/await
exclude: ['transform-regenerator'],
}
],
'@babel/preset-react',
'@babel/preset-typescript'
],
plugins: [
// Optional chaining and nullish coalescing (if not in preset-env)
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-nullish-coalescing-operator',
// Class properties
['@babel/plugin-proposal-class-properties', { loose: true }],
// Runtime helpers (reduce duplication)
['@babel/plugin-transform-runtime', {
corejs: false, // Don't polyfill (handled by useBuiltIns)
helpers: true, // Extract helpers
regenerator: true,
useESModules: true
}]
],
env: {
production: {
plugins: [
// Remove console.log in production
'transform-remove-console',
// Dead code elimination
'transform-remove-undefined'
]
}
}
};
// package.json - browserslist
{
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all",
"not IE 11"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"legacy": [
"IE 11"
]
}
}
Example: Feature detection and polyfill loading
// polyfills.ts - Conditional polyfill loading
export async function loadPolyfills() {
const polyfills: Promise<void>[] = [];
// Check for Promise support
if (!window.Promise) {
polyfills.push(import('core-js/features/promise'));
}
// Check for fetch support
if (!window.fetch) {
polyfills.push(import('whatwg-fetch'));
}
// Check for IntersectionObserver
if (!('IntersectionObserver' in window)) {
polyfills.push(import('intersection-observer'));
}
// Check for ResizeObserver
if (!('ResizeObserver' in window)) {
polyfills.push(import('@juggle/resize-observer').then(m => {
window.ResizeObserver = m.ResizeObserver;
}));
}
// Wait for all polyfills to load
await Promise.all(polyfills);
}
// main.tsx - Load polyfills before app
import { loadPolyfills } from './polyfills';
loadPolyfills().then(() => {
// Import and render app after polyfills loaded
import('./App').then(({ App }) => {
const root = document.getElementById('root');
ReactDOM.createRoot(root!).render(<App />);
});
});
// Alternative: Polyfill.io service (CDN-based)
// index.html
<script src="https://polyfill.io/v3/polyfill.min.js?features=default,fetch,IntersectionObserver,ResizeObserver"></script>
2. PostCSS Autoprefixer Vendor Prefixes
| Tool | Purpose | Configuration | Best Practices |
|---|---|---|---|
| PostCSS | CSS transformation pipeline. Plugin architecture. Autoprefixer, CSS modules, nesting | postcss.config.js. Plugins array. Integrate with webpack/Vite/Next |
Use with bundler. Enable source maps. Chain plugins efficiently. Cache in production |
| Autoprefixer | Auto vendor prefixes (-webkit, -moz, -ms). Based on browserslist. Remove outdated prefixes | autoprefixer({ grid: 'autoplace' }). Uses browserslist config |
Always use with browserslist. Enable CSS Grid support. Update regularly. No manual prefixes |
| CSS Modern Features | Grid, Flexbox, Custom Properties, calc(), clamp(), aspect-ratio, container queries | Autoprefixer handles most. Polyfills for custom properties (IE11). Progressive enhancement | Write modern CSS. Autoprefixer adds prefixes. Test in target browsers. Use @supports |
| postcss-preset-env | Use future CSS today. Stage 3 features. Nesting, custom media queries, color functions | postcss-preset-env({ stage: 3, features: { 'nesting-rules': true } }) |
Stage 3 is stable. Enable specific features. More than autoprefixer. Use with caution |
| CSS Modules | Scoped CSS, Local class names, Composition, No global conflicts, Type-safe with TS | postcss-modules. Import: import styles from './App.module.css' |
Use for component styles. Global for resets. Compose common styles. Generate types |
| PostCSS Plugins | cssnano (minify), postcss-nesting, postcss-custom-properties, postcss-import | Chain plugins. Order matters. cssnano last. Import first. Optimize for prod | Minimal plugins. cssnano in prod. Import for @import. Nesting for cleaner CSS |
Example: Complete PostCSS configuration
// postcss.config.js
module.exports = {
plugins: [
// Import support
require('postcss-import')({
path: ['src/styles']
}),
// Nesting (like Sass)
require('postcss-nesting'),
// Modern CSS features with autoprefixer included
require('postcss-preset-env')({
stage: 3,
features: {
'nesting-rules': true,
'custom-media-queries': true,
'custom-properties': false, // Use native CSS variables
},
autoprefixer: {
grid: 'autoplace',
flexbox: 'no-2009'
}
}),
// Or standalone autoprefixer
// require('autoprefixer'),
// Minification in production
process.env.NODE_ENV === 'production' ? require('cssnano')({
preset: ['default', {
discardComments: { removeAll: true },
normalizeWhitespace: true,
reduceIdents: false, // Keep animation names
zindex: false // Don't optimize z-index
}]
}) : false
].filter(Boolean)
};
// Example CSS that will be prefixed
/* Input CSS */
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
& .item {
display: flex;
align-items: center;
&:hover {
transform: scale(1.05);
}
}
}
.modern {
aspect-ratio: 16 / 9;
container-type: inline-size;
@supports (aspect-ratio: 1) {
/* Modern browsers */
}
}
/* Output CSS (with autoprefixer) */
.container {
display: -ms-grid;
display: grid;
-ms-grid-columns: (minmax(250px, 1fr))[auto-fit];
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.container .item {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.container .item:hover {
-webkit-transform: scale(1.05);
-ms-transform: scale(1.05);
transform: scale(1.05);
}
3. Can I Use Browser Feature Detection
| Tool/Approach | Purpose | Implementation | Best Practices |
|---|---|---|---|
| caniuse.com | Browser support database. Check feature support. Usage statistics. Known issues | Search feature. Check target browsers. Review known issues. Export to browserslist | Check before using new features. 95%+ support is safe. Check mobile support. Review notes |
| @supports CSS | Feature queries in CSS. Progressive enhancement. Fallbacks. Test property support | @supports (display: grid) { }. Test property:value pairs. Combine with and/or |
Provide fallbacks first. Enhance with @supports. Test one property. Use for critical features |
| JavaScript Feature Detection | Check API availability. if ('feature' in object). Modernizr library. Dynamic polyfilling | if ('IntersectionObserver' in window) { }. Check before use. Load polyfill if missing |
Always check APIs. Provide fallbacks. Load polyfills conditionally. Test in target browsers |
| Modernizr | Feature detection library. 150+ tests. Custom builds. HTML classes. JS API | Custom build at modernizr.com. Adds classes to <html>. JS: Modernizr.feature |
Custom build only (5-10KB). HTML classes for CSS. JS API for conditionals. Complement polyfills |
| browserslist | Define target browsers. Shared config. Used by Babel, Autoprefixer, ESLint | .browserslistrc or package.json. Queries: '>0.5%', 'last 2 versions', 'not dead' |
Single source of truth. Update yearly. Separate prod/dev. Use caniuse-lite data |
| User Agent Detection | Last resort. Browser/version detection. Mobile vs desktop. Known browser bugs | navigator.userAgent. Libraries: ua-parser-js, bowser. Server-side better |
Avoid if possible. Feature detection preferred. Use for specific bug workarounds. Server-side |
Example: Feature detection patterns
// CSS @supports for progressive enhancement
/* Fallback for older browsers */
.grid-container {
display: flex;
flex-wrap: wrap;
}
/* Enhanced for browsers with Grid support */
@supports (display: grid) {
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
}
/* Container queries with fallback */
.card {
padding: 1rem;
}
@supports (container-type: inline-size) {
.container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
padding: 2rem;
}
}
}
// JavaScript feature detection
class FeatureDetector {
static hasIntersectionObserver(): boolean {
return 'IntersectionObserver' in window;
}
static hasServiceWorker(): boolean {
return 'serviceWorker' in navigator;
}
static hasWebGL(): boolean {
try {
const canvas = document.createElement('canvas');
return !!(
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl')
);
} catch {
return false;
}
}
static hasLocalStorage(): boolean {
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
return true;
} catch {
return false;
}
}
static supportsWebP(): Promise<boolean> {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img.width === 1);
img.onerror = () => resolve(false);
img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=';
});
}
static getCSSSupport(): Record<string, boolean> {
return {
grid: CSS.supports('display', 'grid'),
flexbox: CSS.supports('display', 'flex'),
customProperties: CSS.supports('--custom', 'value'),
aspectRatio: CSS.supports('aspect-ratio', '16/9'),
containerQueries: CSS.supports('container-type', 'inline-size'),
backdrop: CSS.supports('backdrop-filter', 'blur(10px)'),
};
}
}
// Usage in React
export function useFeatureDetection() {
const [features, setFeatures] = useState({
intersectionObserver: false,
serviceWorker: false,
webp: false,
webgl: false,
cssGrid: false,
});
useEffect(() => {
const detect = async () => {
setFeatures({
intersectionObserver: FeatureDetector.hasIntersectionObserver(),
serviceWorker: FeatureDetector.hasServiceWorker(),
webp: await FeatureDetector.supportsWebP(),
webgl: FeatureDetector.hasWebGL(),
cssGrid: CSS.supports('display', 'grid'),
});
};
detect();
}, []);
return features;
}
// Conditional rendering based on features
function ImageGallery() {
const { webp } = useFeatureDetection();
const imageExt = webp ? 'webp' : 'jpg';
return <img src={`/images/photo.${imageExt}`} alt="Photo" />;
}
4. Progressive Enhancement Feature Support
| Strategy | Description | Implementation | Benefits |
|---|---|---|---|
| Progressive Enhancement | Start with basic HTML/CSS. Layer JavaScript. Feature detection. Graceful degradation | Core content in HTML. CSS for presentation. JS for enhancement. Works without JS | Accessible by default. SEO-friendly. Resilient. Fast baseline. Works everywhere |
| Content First | HTML semantic markup. No JS required for content. Forms work without JS. Links navigate | Use <form action="/submit">. <a href="/page">. Server-side rendering. Semantic HTML5 | Accessibility. SEO. No-JS users. Slow connections. Screen readers. Search crawlers |
| CSS Enhancement | Basic layout without modern features. @supports for enhancement. Fallback styles first | Flexbox fallback for Grid. Basic colors before gradients. Simple animations | Works on all browsers. Better with modern features. Graceful degradation. No breaking |
| JavaScript Enhancement | Check feature before use. Load polyfills conditionally. Provide no-JS alternative | <noscript> fallback. Progressive loading. Feature detection. Error boundaries | Works without JS. Better with JS. Faster for modern browsers. Resilient to failures |
| Mobile First | Design for mobile baseline. Enhance for desktop. Touch-first interactions. Responsive | Min-width media queries. Mobile layout default. Touch events. Responsive images | Better mobile UX. Faster on mobile. Progressive enhancement. Desktop gets extras |
| Network Resilience | Work offline. Queue requests. Cache content. Show stale data. Background sync | Service workers. IndexedDB. Offline indicators. Retry logic. Optimistic UI | Works on slow/offline. Better UX. Resilient. Mobile-friendly. Emerging markets |
Example: Progressive enhancement patterns
// HTML - Works without JavaScript
<!-- Form with server-side fallback -->
<form action="/api/submit" method="POST" class="contact-form">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<label for="message">Message:</label>
<textarea id="message" name="message" required></textarea>
<button type="submit">Send Message</button>
</form>
<!-- No-JS fallback -->
<noscript>
<p>This site works best with JavaScript enabled, but core functionality is available without it.</p>
</noscript>
// CSS - Progressive enhancement with @supports
/* Basic layout (works everywhere) */
.gallery {
margin: 0 auto;
max-width: 1200px;
}
.gallery-item {
margin-bottom: 20px;
}
/* Enhanced with Flexbox */
@supports (display: flex) {
.gallery {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.gallery-item {
flex: 0 0 calc(33.333% - 20px);
margin-bottom: 0;
}
}
/* Further enhanced with Grid */
@supports (display: grid) {
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.gallery-item {
flex: unset;
}
}
// JavaScript - Progressive enhancement
class ContactForm {
constructor(formElement: HTMLFormElement) {
this.form = formElement;
// Only enhance if fetch is available
if ('fetch' in window) {
this.enhanceForm();
}
// Otherwise, form submits normally to server
}
private enhanceForm() {
this.form.addEventListener('submit', async (e) => {
e.preventDefault(); // Only prevent if JS works
const formData = new FormData(this.form);
try {
const response = await fetch(this.form.action, {
method: 'POST',
body: formData,
});
if (response.ok) {
this.showSuccess();
} else {
// Fallback to normal form submit on error
this.form.submit();
}
} catch (error) {
// Network error - fallback to normal submit
this.form.submit();
}
});
}
private showSuccess() {
// Enhanced UI feedback (only with JS)
this.form.innerHTML = '<p>Thank you! Message sent.</p>;
}
}
// Initialize only if DOM is ready and JS enabled
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initForms);
} else {
initForms();
}
function initForms() {
const forms = document.querySelectorAll<HTMLFormElement>('.contact-form');
forms.forEach(form => new ContactForm(form));
}
// React - Progressive enhancement with hydration
// Server-side render first, then hydrate
import { hydrateRoot } from 'react-dom/client';
function App() {
return (
<form action="/api/submit" method="POST">
{/* Server renders working form */}
{/* Client enhances with React */}
</form>
);
}
// Hydrate only if browser supports modules
if ('noModule' in HTMLScriptElement.prototype) {
const root = document.getElementById('root')!;
hydrateRoot(root, <App />);
}
// Otherwise, server-rendered HTML works as-is
5. Cross-browser Testing BrowserStack
| Tool/Service | Features | Use Cases | Best Practices |
|---|---|---|---|
| BrowserStack NEW | 3000+ browsers, Real devices, Live testing, Automated tests, Screenshots, Network throttling | Manual testing, Selenium/Playwright integration, Visual regression, Mobile device testing | Test critical flows. Automate common paths. Test on real devices. Check latest browsers monthly |
| Sauce Labs | Browser cloud, Mobile testing, CI/CD integration, Parallel tests, Analytics, Video recording | Automated E2E tests, Cross-browser CI, Performance testing, Mobile app testing | Integrate with CI. Run tests in parallel. Video for failures. Test matrix optimization |
| LambdaTest | Live testing, Screenshot testing, Automated testing, Local tunnel, Responsive testing | Visual testing, Responsive layouts, Browser compatibility, Geolocation testing | Bulk screenshots. Responsive testing. Local tunnel for dev. Compare screenshots |
| Playwright | Chromium, Firefox, WebKit. Headless testing. Auto-wait. Network interception. Tracing | Cross-browser E2E tests. Component testing. Visual regression. CI automation | Test in 3 engines. Parallel execution. Trace for debugging. Screenshots on failure. Retry flaky |
| Testing Strategy | Test pyramid. Critical flows manual. Smoke tests automated. Visual regression. Performance | Login, checkout, forms. Core user journeys. Different screen sizes. Network conditions | Automate repetitive. Manual for UX. Test on real devices. Check accessibility. Performance budget |
| Browser DevTools | Chrome DevTools, Firefox DevTools, Safari Web Inspector. Device emulation. Network throttling | Debug issues. Performance profiling. Mobile emulation. Console logging. Network analysis | Use native DevTools first. Device emulation for quick checks. Network throttling for slow connections |
Example: Playwright cross-browser tests
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['junit', { outputFile: 'results.xml' }],
['json', { outputFile: 'results.json' }]
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
// Desktop browsers
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// Mobile browsers
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 13'] },
},
// Tablet
{
name: 'iPad',
use: { ...devices['iPad Pro'] },
},
],
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
// e2e/critical-flow.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Critical User Flow', () => {
test('should complete checkout process', async ({ page }) => {
await page.goto('/');
// Add to cart
await page.click('[data-testid="add-to-cart"]');
await expect(page.locator('.cart-badge')).toHaveText('1');
// Go to checkout
await page.click('[data-testid="cart-icon"]');
await page.click('[data-testid="checkout-button"]');
// Fill form
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="card"]', '4242424242424242');
// Submit
await page.click('[data-testid="submit-payment"]');
// Verify success
await expect(page.locator('.success-message')).toBeVisible();
});
test('should work offline', async ({ page, context }) => {
// Go offline
await context.setOffline(true);
await page.goto('/');
// Should show offline indicator
await expect(page.locator('.offline-indicator')).toBeVisible();
// Content should still be accessible
await expect(page.locator('h1')).toBeVisible();
});
});
// BrowserStack integration
// wdio.conf.js for BrowserStack
exports.config = {
user: process.env.BROWSERSTACK_USERNAME,
key: process.env.BROWSERSTACK_ACCESS_KEY,
capabilities: [
{
browserName: 'Chrome',
browserVersion: 'latest',
'bstack:options': {
os: 'Windows',
osVersion: '11',
}
},
{
browserName: 'Safari',
browserVersion: 'latest',
'bstack:options': {
os: 'OS X',
osVersion: 'Ventura',
}
},
{
browserName: 'Chrome',
'bstack:options': {
deviceName: 'Samsung Galaxy S23',
realMobile: true,
}
}
],
services: ['browserstack']
};
6. ES Module Legacy Bundle Differential Loading
| Strategy | Description | Implementation | Benefits |
|---|---|---|---|
| ES Modules (ESM) | Native browser modules. type="module". Dynamic imports. Tree-shaking. Modern browsers | <script type="module" src="app.js">. import/export. 95%+ browser support |
Smaller bundles. No transpilation. Faster. Native tree-shaking. Parallel loading |
| Legacy Bundle | ES5 transpiled. Polyfills included. nomodule attribute. IE11 and old browsers | <script nomodule src="app-legacy.js">. Babel transpilation. Full polyfills |
Support old browsers. Fallback for no-ESM. Separate bundle. Larger size acceptable |
| Differential Loading | Serve modern bundle to modern browsers. Legacy to old browsers. Automatic selection | Both module and nomodule scripts. Browser chooses. Vite/Angular CLI built-in | 20-40% smaller for modern. No polyfills for 95%. Better performance. Same codebase |
| Module/Nomodule Pattern | Modern browsers ignore nomodule. Old browsers ignore type="module". Automatic fallback | Both scripts in HTML. Modern loads module. Old loads nomodule. No JS detection needed | Zero config. Automatic. No feature detection. Works everywhere. Easy implementation |
| Build Configuration | Two build outputs. Modern target ES2020. Legacy target ES5. Vite, Angular, custom webpack | Vite: build.target = 'esnext'. Legacy plugin. Webpack: dual configs. Different browserslist | Automate builds. Single source. Optimize each. Monitor both sizes. Update targets yearly |
| Bundle Size Comparison | Modern: 100KB (no polyfills). Legacy: 150KB (polyfills). 33% savings for modern | Analyze both bundles. Modern no core-js. Legacy full polyfills. Separate chunks | Modern users get faster. Legacy users still work. Cost-effective. Better metrics |
Example: Differential loading setup
// HTML - Module/nomodule pattern
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Differential Loading</title>
<!-- Preload modern bundle for modern browsers -->
<link rel="modulepreload" href="/assets/app.js">
<!-- Preload legacy bundle for old browsers -->
<link rel="preload" href="/assets/app-legacy.js" as="script" crossorigin>
</head>
<body>
<div id="root"></div>
<!-- Modern browsers: ES2020+, no polyfills, smaller -->
<script type="module" src="/assets/app.js"></script>
<!-- Legacy browsers: ES5, full polyfills, larger -->
<script nomodule src="/assets/app-legacy.js"></script>
<!-- Safari 10.1 fix (downloads both) -->
<script>
!function(){var e=document,t=e.createElement("script");
if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;
e.addEventListener("beforeload",function(e){
if(e.target===t)n=!0;
else if(!e.target.hasAttribute("nomodule")||!n)return;
e.preventDefault()},!0),
t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();
</script>
</body>
</html>
// vite.config.ts - Differential loading with Vite
import { defineConfig } from 'vite';
import legacy from '@vitejs/plugin-legacy';
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
modernPolyfills: true,
renderLegacyChunks: true,
// Additional legacy targets
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
],
build: {
// Modern target
target: 'esnext',
// Ensure code splitting
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
}
}
}
}
});
// Webpack - Manual differential loading
// webpack.modern.config.js
module.exports = {
output: {
filename: '[name].js',
environment: {
// Modern browser features
arrowFunction: true,
const: true,
destructuring: true,
forOf: true,
dynamicImport: true,
module: true,
}
},
target: ['web', 'es2020'],
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: { esmodules: true },
bugfixes: true,
modules: false,
}]
]
}
}
}
]
}
};
// webpack.legacy.config.js
module.exports = {
output: {
filename: '[name]-legacy.js',
},
target: ['web', 'es5'],
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: { ie: 11 },
useBuiltIns: 'usage',
corejs: 3,
}]
]
}
}
}
]
}
};
// Build script to generate both bundles
// package.json
{
"scripts": {
"build": "npm run build:modern && npm run build:legacy",
"build:modern": "webpack --config webpack.modern.config.js",
"build:legacy": "webpack --config webpack.legacy.config.js"
}
}
Browser Compatibility Strategy:
- Target Browsers: Last 2 versions, >0.5% usage, not dead. Update yearly. Mobile first
- Polyfills: Use preset-env with useBuiltIns: 'usage'. Conditional loading. 20-50KB for legacy
- CSS: Autoprefixer with browserslist. @supports for enhancement. Fallbacks first
- Testing: Playwright for 3 engines. BrowserStack for real devices. Visual regression
- Differential Loading: Modern ES2020 bundle. Legacy ES5 bundle. 30% size savings
Browser Compatibility Summary
- Babel + core-js: Transpile to ES5. Polyfill APIs. useBuiltIns: 'usage'. 20KB modern, 50KB legacy
- PostCSS + Autoprefixer: Auto vendor prefixes. Based on browserslist. Grid support. CSS nesting
- Feature Detection: caniuse.com for checking. @supports in CSS. JavaScript detection. Modernizr
- Progressive Enhancement: HTML first. CSS enhancement. JS layer. Works without JS. Mobile first
- Cross-browser Testing: BrowserStack/Sauce Labs. Playwright 3 engines. Real devices. Automate CI
- Differential Loading: type="module" for modern. nomodule for legacy. 30% smaller. Auto selection
Compatibility Checklist: Define browserslist targets, enable
Babel preset-env, configure Autoprefixer,
implement feature detection, use progressive
enhancement, test in 3+ browsers, and ship differential bundles. Support 95%+ users efficiently.