or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bundle-usage.mddynamic-loading.mdes5-adapter.mdevent-system.mdindex.md
tile.json

event-system.mddocs/

Custom Event System

The Web Components polyfills provide a custom event system to coordinate polyfill loading and custom element lifecycle management through the WebComponentsReady event.

Capabilities

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 });

Event Timing

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);
}

WebComponents.ready Property

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);
  }
}

Custom Element Batching

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 complete

Event Integration Patterns

Common 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);
    }
  });
});

Event Debugging

// 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');
}

Error Handling

// 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
  }
});