The Web Components polyfills provide a custom event system to coordinate polyfill loading and custom element lifecycle management through the WebComponentsReady event.
Main event that signals when all polyfills are loaded and custom elements are ready for use.
/**
* WebComponentsReady event fired on document when polyfills complete loading
* Fired after custom elements are upgraded and ready for interaction
*/
interface WebComponentsReadyEvent extends CustomEvent {
/** Event type is always 'WebComponentsReady' */
type: 'WebComponentsReady';
/** Event bubbles up through the document */
bubbles: true;
/** No custom detail data */
detail: null;
}
// Event listener interface
document.addEventListener('WebComponentsReady', (event: WebComponentsReadyEvent) => void): void;Usage Examples:
// Basic event listener
document.addEventListener('WebComponentsReady', () => {
console.log('Web Components are ready!');
// Safe to use custom elements here
});
// With event object
document.addEventListener('WebComponentsReady', (event) => {
console.log('Event type:', event.type); // 'WebComponentsReady'
console.log('Bubbles:', event.bubbles); // true
// Initialize application
initializeApp();
});
// One-time listener
document.addEventListener('WebComponentsReady', function handler() {
document.removeEventListener('WebComponentsReady', handler);
// Initialization code here
}, { once: true });Understanding when the WebComponentsReady event fires in different loading scenarios.
/**
* Event timing varies by loading method:
*
* Synchronous loading (webcomponents-loader.js):
* 1. Polyfills load via document.write()
* 2. DOMContentLoaded event waits
* 3. Custom elements are upgraded
* 4. WebComponentsReady fires
*
* Asynchronous loading (webcomponents-loader.js defer):
* 1. Polyfills load asynchronously
* 2. waitFor callbacks execute
* 3. Custom elements are upgraded
* 4. WebComponentsReady fires
*
* Bundle loading (webcomponents-bundle.js):
* 1. All polyfills load immediately
* 2. DOMContentLoaded event waits (if document.readyState !== 'complete')
* 3. Custom elements are upgraded
* 4. WebComponentsReady fires
*/
// Check if already ready
if (window.WebComponents && window.WebComponents.ready) {
// Event already fired
handleReady();
} else {
// Wait for event
document.addEventListener('WebComponentsReady', handleReady);
}Programmatic way to check if polyfills are loaded and ready.
/**
* Boolean property indicating polyfill readiness state
*/
interface WebComponents {
/** True after polyfills load and custom elements are upgraded */
ready: boolean;
}
declare global {
interface Window {
WebComponents: WebComponents;
}
}Usage Examples:
// Check readiness state
function checkWebComponentsReady() {
if (window.WebComponents && window.WebComponents.ready) {
return true;
}
return false;
}
// Conditional initialization
if (checkWebComponentsReady()) {
// Initialize immediately
initializeCustomElements();
} else {
// Wait for ready event
document.addEventListener('WebComponentsReady', initializeCustomElements);
}
// Polling approach (not recommended, use events instead)
function waitForReady() {
if (window.WebComponents && window.WebComponents.ready) {
initializeCustomElements();
} else {
setTimeout(waitForReady, 10);
}
}Internal system for batching custom element upgrades for better performance.
/**
* Internal batching system coordinates custom element upgrades
* Automatically called by polyfill loading system
* Generally not needed in application code
*/
interface WebComponents {
/** Internal method for batching custom element upgrades */
_batchCustomElements(): void;
}
// Internal batching behavior:
// 1. Custom element definitions are queued during loading
// 2. Upgrades are batched until polyfills are ready
// 3. All upgrades happen at once for better performance
// 4. WebComponentsReady fires after batched upgrades completeCommon patterns for integrating with the WebComponentsReady event system.
Framework Integration:
// React/Vue/Angular application bootstrap
document.addEventListener('WebComponentsReady', () => {
// Safe to mount React components that use custom elements
ReactDOM.render(<App />, document.getElementById('root'));
});
// Lit application
document.addEventListener('WebComponentsReady', () => {
// Custom elements are ready, import and use Lit components
import('./lit-components.js').then(() => {
document.body.innerHTML = '<my-lit-app></my-lit-app>';
});
});Testing Integration:
// Jest/Mocha test setup
beforeEach((done) => {
if (window.WebComponents && window.WebComponents.ready) {
done();
} else {
document.addEventListener('WebComponentsReady', done, { once: true });
}
});
// Cypress integration
cy.window().should((win) => {
expect(win.WebComponents).to.exist;
expect(win.WebComponents.ready).to.be.true;
});Module Loading:
// Dynamic import after ready
document.addEventListener('WebComponentsReady', async () => {
const { MyElement } = await import('./my-element.js');
customElements.define('my-element', MyElement);
});
// Progressive enhancement
document.addEventListener('WebComponentsReady', () => {
// Upgrade existing elements in the page
document.querySelectorAll('[data-component]').forEach(el => {
const componentName = el.dataset.component;
if (customElements.get(componentName)) {
// Replace with custom element
const customEl = document.createElement(componentName);
customEl.innerHTML = el.innerHTML;
el.parentNode.replaceChild(customEl, el);
}
});
});// Debug event timing
let loadStart = performance.now();
document.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded at', performance.now() - loadStart);
});
document.addEventListener('WebComponentsReady', () => {
console.log('WebComponentsReady at', performance.now() - loadStart);
// Debug WebComponents state
console.log('WebComponents.ready:', window.WebComponents.ready);
console.log('Custom elements defined:', customElements.whenDefined('my-element'));
});
// Debug polyfill loading
if (window.WebComponents) {
console.log('WebComponents object available immediately');
} else {
console.log('WebComponents object not yet available');
}// Handle cases where WebComponentsReady never fires
const readyTimeout = setTimeout(() => {
console.warn('WebComponentsReady event did not fire within 5 seconds');
// Fallback initialization
if (typeof customElements !== 'undefined') {
initializeCustomElements();
}
}, 5000);
document.addEventListener('WebComponentsReady', () => {
clearTimeout(readyTimeout);
initializeCustomElements();
});
// Handle polyfill loading errors
window.addEventListener('error', (event) => {
if (event.filename && event.filename.includes('webcomponents')) {
console.error('Web Components polyfill error:', event.error);
// Fallback to non-custom-element implementation
}
});