Polyfill Loading Strategies (CDN, Differential Serving)
1. Differential Serving (Modern vs Legacy Bundles)
| Approach | Modern Bundle | Legacy Bundle | Detection Method |
|---|---|---|---|
| Module/Nomodule | <script type="module"> |
<script nomodule> |
Browser feature detection |
| User Agent | ES2015+ syntax | ES5 + polyfills | Server-side UA parsing |
| Feature Test | Native features | Polyfilled features | Client-side detection |
| Bundle Size Savings | 30-50% smaller | Full compatibility | Varies by project |
| Browser Support | Modern (ES2015+) | IE11, old Safari | Graceful degradation |
Example: Module/nomodule pattern
<!-- Modern browsers load ES modules -->
<script type="module" src="app.modern.js"></script>
<!-- Legacy browsers load ES5 + polyfills -->
<script nomodule src="app.legacy.js"></script>
<!--
Modern bundle (app.modern.js):
- ES2015+ syntax (arrow functions, classes, etc.)
- No polyfills for Promise, fetch, etc.
- Native modules, async/await
- ~30-40% smaller
Legacy bundle (app.legacy.js):
- Transpiled to ES5
- Includes core-js polyfills
- Promise, fetch, Symbol polyfills
- Full compatibility
-->
Example: Dynamic differential serving
// Client-side differential serving
(function() {
// Feature detection for modern bundle
var isModern = (
'fetch' in window &&
'Promise' in window &&
'assign' in Object &&
'keys' in Object &&
'symbol' in window.Symbol
);
function loadBundle(modern) {
var script = document.createElement('script');
if (modern) {
script.type = 'module';
script.src = '/dist/app.modern.js';
} else {
script.src = '/dist/app.legacy.js';
script.defer = true;
}
document.head.appendChild(script);
}
// Load appropriate bundle
loadBundle(isModern);
})();
// Webpack configuration for differential builds
// webpack.config.js
module.exports = [
{
// Modern build
name: 'modern',
output: {
filename: 'app.modern.js',
module: true
},
target: ['web', 'es2015'],
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: { esmodules: true },
bugfixes: true,
shippedProposals: true
}]
]
}
}
}
]
}
},
{
// Legacy build
name: 'legacy',
output: {
filename: 'app.legacy.js'
},
target: ['web', 'es5'],
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: '> 0.5%, last 2 versions, Firefox ESR, not dead, IE 11',
useBuiltIns: 'usage',
corejs: 3
}]
]
}
}
}
]
}
}
];
Note: Differential serving can reduce bundle sizes by 30-50% for modern
browsers while maintaining backward compatibility. Use module/nomodule for automatic detection.
2. Dynamic import() and Code Splitting
| Strategy | Use Case | Loading Time | Implementation |
|---|---|---|---|
| Route-based | Load code per page/route | On navigation | import('./page.js') |
| Component-based | Lazy load components | On visibility/interaction | React.lazy, Vue async |
| Vendor chunks | Separate node_modules | Initial load | splitChunks config |
| Polyfill chunks | Load only if needed | Conditional | Feature detection + import() |
| Prefetch/Preload | Anticipate future needs | Idle time | <link rel="prefetch"> |
Example: Conditional polyfill loading
// Load polyfills only when needed
async function loadPolyfills() {
var polyfills = [];
// Check what's needed
if (!('Promise' in window)) {
polyfills.push(import('core-js/features/promise'));
}
if (!('fetch' in window)) {
polyfills.push(import('whatwg-fetch'));
}
if (!('IntersectionObserver' in window)) {
polyfills.push(import('intersection-observer'));
}
if (!window.customElements) {
polyfills.push(import('@webcomponents/webcomponentsjs/webcomponents-bundle'));
}
// Wait for all polyfills to load
if (polyfills.length > 0) {
await Promise.all(polyfills);
console.log('Polyfills loaded:', polyfills.length);
}
}
// Initialize app after polyfills
loadPolyfills()
.then(() => {
return import('./app.js');
})
.then((app) => {
app.init();
})
.catch((error) => {
console.error('Failed to load:', error);
});
// Webpack magic comments for chunk naming
async function loadFeature() {
const module = await import(
/* webpackChunkName: "heavy-feature" */
/* webpackPrefetch: true */
'./heavy-feature.js'
);
return module;
}
Example: Progressive polyfill loader
// Progressive polyfill loading system
var PolyfillLoader = {
required: [],
loaded: new Set(),
register: function(name, test, loader) {
this.required.push({
name: name,
test: test,
loader: loader
});
},
loadAll: function() {
var self = this;
var promises = [];
this.required.forEach(function(polyfill) {
if (!polyfill.test()) {
console.log('Loading polyfill:', polyfill.name);
var promise = polyfill.loader()
.then(function() {
self.loaded.add(polyfill.name);
console.log('Loaded:', polyfill.name);
})
.catch(function(error) {
console.error('Failed to load:', polyfill.name, error);
});
promises.push(promise);
} else {
console.log('Native support:', polyfill.name);
}
});
return Promise.all(promises);
}
};
// Register polyfills
PolyfillLoader.register(
'Promise',
function() { return 'Promise' in window; },
function() { return import('es6-promise/auto'); }
);
PolyfillLoader.register(
'fetch',
function() { return 'fetch' in window; },
function() { return import('whatwg-fetch'); }
);
PolyfillLoader.register(
'IntersectionObserver',
function() { return 'IntersectionObserver' in window; },
function() { return import('intersection-observer'); }
);
PolyfillLoader.register(
'ResizeObserver',
function() { return 'ResizeObserver' in window; },
function() { return import('resize-observer-polyfill'); }
);
// Load and initialize
PolyfillLoader.loadAll().then(function() {
console.log('All required polyfills loaded');
console.log('Loaded polyfills:', Array.from(PolyfillLoader.loaded));
// Initialize application
initApp();
});
Note: Dynamic imports enable on-demand polyfill loading,
reducing initial bundle size. Use feature detection to load only what's needed.
3. Polyfill.io Service Integration and Configuration
| Feature | Configuration | Benefits | Considerations |
|---|---|---|---|
| Auto Detection | UA-based polyfill selection | Optimal bundle per browser | CDN dependency |
| Custom Features | ?features=Promise,fetch | Request specific polyfills | Manual configuration |
| Flags | ?flags=gated,always | Control loading behavior | Advanced usage |
| Self-hosted | Run own polyfill service | Full control, no third-party | Maintenance overhead |
| Security Warning | Service compromised 2024 | - | USE WITH CAUTION |
Example: Self-hosted polyfill service alternative
// Self-hosted polyfill service (safer alternative)
// polyfill-service.js
var PolyfillService = {
polyfills: {
'Promise': {
detect: function() { return 'Promise' in window; },
url: '/polyfills/promise.min.js'
},
'fetch': {
detect: function() { return 'fetch' in window; },
url: '/polyfills/fetch.min.js'
},
'IntersectionObserver': {
detect: function() { return 'IntersectionObserver' in window; },
url: '/polyfills/intersection-observer.min.js'
},
'Array.prototype.includes': {
detect: function() { return Array.prototype.includes; },
url: '/polyfills/array-includes.min.js'
},
'Object.assign': {
detect: function() { return Object.assign; },
url: '/polyfills/object-assign.min.js'
}
},
load: function(features) {
var self = this;
var scripts = [];
features.forEach(function(feature) {
var polyfill = self.polyfills[feature];
if (!polyfill) {
console.warn('Unknown polyfill:', feature);
return;
}
if (!polyfill.detect()) {
scripts.push(self._loadScript(polyfill.url));
}
});
return Promise.all(scripts);
},
_loadScript: function(url) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = url;
script.async = true;
script.onload = function() {
console.log('Loaded:', url);
resolve();
};
script.onerror = function() {
console.error('Failed to load:', url);
reject(new Error('Script load failed: ' + url));
};
document.head.appendChild(script);
});
},
loadAll: function() {
var features = Object.keys(this.polyfills);
return this.load(features);
},
// Parse URL parameters to load specific features
loadFromURL: function() {
var params = new URLSearchParams(window.location.search);
var features = params.get('features');
if (features) {
var featureList = features.split(',');
return this.load(featureList);
}
return this.loadAll();
}
};
// Usage - load specific polyfills
PolyfillService.load(['Promise', 'fetch', 'IntersectionObserver'])
.then(function() {
console.log('Polyfills ready');
initApp();
})
.catch(function(error) {
console.error('Polyfill loading failed:', error);
});
// Or load based on URL: /app.html?features=Promise,fetch
// PolyfillService.loadFromURL();
Warning: The public polyfill.io CDN was compromised in 2024.
Self-host polyfills or use trusted alternatives like cdnjs or jsDelivr with SRI hashes.
4. CDN-based Polyfill Loading (unpkg, jsdelivr)
| CDN Provider | Reliability | Features | Security |
|---|---|---|---|
| jsDelivr | High (multi-CDN) | NPM packages, GitHub releases | SRI support |
| cdnjs | High (Cloudflare) | Curated library collection | SRI support |
| unpkg | Medium | Direct NPM access | SRI recommended |
| Self-hosted | Depends on infrastructure | Full control | Your responsibility |
| Hybrid | Best (fallback chain) | CDN + local fallback | Multiple layers |
Example: CDN loading with fallback
// CDN loader with fallback chain
var CDNLoader = {
sources: [
{
name: 'jsDelivr',
url: 'https://cdn.jsdelivr.net/npm/core-js-bundle@3.27.2/minified.js',
integrity: 'sha384-...'
},
{
name: 'cdnjs',
url: 'https://cdnjs.cloudflare.com/ajax/libs/core-js/3.27.2/minified.js',
integrity: 'sha384-...'
},
{
name: 'unpkg',
url: 'https://unpkg.com/core-js-bundle@3.27.2/minified.js',
integrity: 'sha384-...'
},
{
name: 'local',
url: '/js/polyfills/core-js.min.js',
integrity: null
}
],
currentIndex: 0,
load: function(callback) {
this._tryLoad(callback);
},
_tryLoad: function(callback) {
if (this.currentIndex >= this.sources.length) {
callback(new Error('All sources failed'));
return;
}
var source = this.sources[this.currentIndex];
console.log('Trying:', source.name);
var script = document.createElement('script');
script.src = source.url;
script.async = true;
if (source.integrity) {
script.integrity = source.integrity;
script.crossOrigin = 'anonymous';
}
var self = this;
script.onload = function() {
console.log('Loaded from:', source.name);
callback(null, source.name);
};
script.onerror = function() {
console.warn('Failed from:', source.name);
self.currentIndex++;
self._tryLoad(callback);
};
document.head.appendChild(script);
}
};
// Usage
CDNLoader.load(function(err, source) {
if (err) {
console.error('All CDN sources failed:', err);
return;
}
console.log('Successfully loaded from:', source);
initApp();
});
Example: Optimized CDN selection
// Smart CDN selector based on geography and performance
var SmartCDN = {
cdns: [
{
name: 'jsDelivr',
baseUrl: 'https://cdn.jsdelivr.net/npm/',
regions: ['global']
},
{
name: 'cdnjs',
baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/',
regions: ['global']
}
],
measureLatency: function(url) {
return new Promise(function(resolve) {
var start = performance.now();
var img = new Image();
img.onload = img.onerror = function() {
var latency = performance.now() - start;
resolve(latency);
};
// Append timestamp to prevent caching
img.src = url + '?t=' + Date.now();
});
},
selectFastest: function() {
var self = this;
var tests = this.cdns.map(function(cdn) {
var testUrl = cdn.baseUrl.replace(/\/[^\/]*$/, '/favicon.ico');
return self.measureLatency(testUrl).then(function(latency) {
return { cdn: cdn, latency: latency };
});
});
return Promise.all(tests).then(function(results) {
results.sort(function(a, b) {
return a.latency - b.latency;
});
console.log('CDN latencies:', results);
return results[0].cdn;
});
},
loadFrom: function(cdn, packagePath) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = cdn.baseUrl + packagePath;
script.async = true;
script.crossOrigin = 'anonymous';
script.onload = function() {
resolve(cdn.name);
};
script.onerror = function() {
reject(new Error('Failed from ' + cdn.name));
};
document.head.appendChild(script);
});
}
};
// Usage
SmartCDN.selectFastest()
.then(function(fastestCDN) {
console.log('Using fastest CDN:', fastestCDN.name);
return SmartCDN.loadFrom(fastestCDN, 'core-js-bundle@3.27.2/minified.js');
})
.then(function(source) {
console.log('Loaded from:', source);
})
.catch(function(error) {
console.error('CDN loading failed:', error);
});
Note: Use multiple CDN fallbacks for reliability. Always include
local fallback and use SRI hashes for security.
5. Service Worker Caching for Offline Polyfills
| Strategy | Description | Use Case | Cache Priority |
|---|---|---|---|
| Cache First | Serve from cache, fallback to network | Static polyfills | High |
| Network First | Try network, fallback to cache | Frequently updated polyfills | Medium |
| Stale While Revalidate | Serve cache, update in background | Best of both worlds | Balanced |
| Network Only | Always fetch from network | Development | None |
| Cache Only | Only serve cached content | Offline-first apps | Required |
Example: Service Worker for polyfill caching
// service-worker.js
var CACHE_NAME = 'polyfills-v1';
var POLYFILL_URLS = [
'/polyfills/core-js.min.js',
'/polyfills/fetch.min.js',
'/polyfills/intersection-observer.min.js',
'/polyfills/resize-observer.min.js'
];
// Install - cache polyfills
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Caching polyfills');
return cache.addAll(POLYFILL_URLS);
})
.then(function() {
return self.skipWaiting();
})
);
});
// Activate - cleanup old caches
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys()
.then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
.then(function() {
return self.clients.claim();
})
);
});
// Fetch - serve from cache with network fallback
self.addEventListener('fetch', function(event) {
var url = new URL(event.request.url);
// Only handle polyfill requests
if (url.pathname.indexOf('/polyfills/') !== 0) {
return;
}
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
console.log('Serving from cache:', url.pathname);
return response;
}
console.log('Fetching from network:', url.pathname);
return fetch(event.request)
.then(function(response) {
// Cache the new response
if (response.status === 200) {
var responseClone = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseClone);
});
}
return response;
});
})
.catch(function() {
console.error('Failed to load:', url.pathname);
})
);
});
// Register service worker (in main app)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered:', registration);
})
.catch(function(error) {
console.error('Service Worker registration failed:', error);
});
}
Example: Stale-while-revalidate strategy
// Advanced caching with stale-while-revalidate
self.addEventListener('fetch', function(event) {
var url = new URL(event.request.url);
if (url.pathname.indexOf('/polyfills/') !== 0) {
return;
}
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
return cache.match(event.request).then(function(cachedResponse) {
// Fetch from network in background
var fetchPromise = fetch(event.request).then(function(networkResponse) {
// Update cache with fresh response
if (networkResponse.status === 200) {
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
});
// Return cached response immediately if available
// Otherwise wait for network
return cachedResponse || fetchPromise;
});
})
);
});
Note: Service Workers enable offline-first polyfill delivery.
Use cache-first strategy for stable polyfills, network-first for frequently updated ones.
6. HTTP/2 Push and <link rel="preload">
| Technique | Syntax | Timing | Use Case |
|---|---|---|---|
| HTTP/2 Server Push | Server-side configuration | Before HTML parsed | Critical polyfills |
| Preload | <link rel="preload"> |
High priority, immediate | Required resources |
| Prefetch | <link rel="prefetch"> |
Low priority, future use | Next page resources |
| Preconnect | <link rel="preconnect"> |
DNS + TLS handshake | CDN connections |
| DNS Prefetch | <link rel="dns-prefetch"> |
DNS resolution only | External domains |
Example: Resource hints for polyfills
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Optimized Polyfill Loading</title>
<!-- Preconnect to CDNs -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<!-- DNS prefetch for backup CDN -->
<link rel="dns-prefetch" href="https://unpkg.com">
<!-- Preload critical polyfills -->
<link rel="preload"
href="/polyfills/core-js.min.js"
as="script"
crossorigin>
<link rel="preload"
href="/polyfills/fetch.min.js"
as="script"
crossorigin>
<!-- Prefetch polyfills for next page -->
<link rel="prefetch"
href="/polyfills/intersection-observer.min.js"
as="script">
<!-- Load critical polyfills with high priority -->
<script src="/polyfills/core-js.min.js" defer></script>
<script src="/polyfills/fetch.min.js" defer></script>
</head>
<body>
<!-- App content -->
</body>
</html>
Example: Dynamic preload injection
// Dynamic resource hint injection
var ResourceHints = {
preload: function(url, as, crossorigin) {
var link = document.createElement('link');
link.rel = 'preload';
link.href = url;
link.as = as || 'script';
if (crossorigin) {
link.crossOrigin = crossorigin;
}
document.head.appendChild(link);
},
prefetch: function(url, as) {
var link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
if (as) {
link.as = as;
}
document.head.appendChild(link);
},
preconnect: function(url, crossorigin) {
var link = document.createElement('link');
link.rel = 'preconnect';
link.href = url;
if (crossorigin) {
link.crossOrigin = crossorigin;
}
document.head.appendChild(link);
},
// Preload polyfills based on feature detection
preloadIfNeeded: function() {
if (!('IntersectionObserver' in window)) {
this.preload('/polyfills/intersection-observer.min.js', 'script');
}
if (!('ResizeObserver' in window)) {
this.preload('/polyfills/resize-observer.min.js', 'script');
}
if (!window.customElements) {
this.preload('/polyfills/custom-elements.min.js', 'script');
}
}
};
// Setup preconnections early
ResourceHints.preconnect('https://cdn.jsdelivr.net', 'anonymous');
ResourceHints.preconnect('https://cdnjs.cloudflare.com', 'anonymous');
// Preload needed polyfills
ResourceHints.preloadIfNeeded();
// Prefetch polyfills for next page
document.addEventListener('DOMContentLoaded', function() {
// Prefetch during idle time
if ('requestIdleCallback' in window) {
requestIdleCallback(function() {
ResourceHints.prefetch('/next-page/polyfills.js', 'script');
});
}
});
Key Takeaways - Modern Loading & Distribution
- Differential Serving: 30-50% smaller bundles for modern browsers with module/nomodule
- Dynamic Import: Load polyfills on-demand based on feature detection
- Self-hosted: Safer than public CDNs, avoid compromised polyfill.io
- CDN Fallbacks: Multiple sources with SRI hashes, local backup required
- Service Workers: Cache-first strategy for offline reliability
- Resource Hints: Preload critical, prefetch future, preconnect to CDNs