// @license © 2025 Google LLC. Licensed under the Apache License, Version 2.0.
let featureDataPromise = null;
export class WasmCompat extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.featureId = this.getAttribute('wasm-feature');
this.hideHeader = this.hasAttribute('hide-header');
}
static async fetchFeatureData() {
if (!featureDataPromise) {
const url =
'https://raw.githubusercontent.com/WebAssembly/website/main/features.json';
featureDataPromise = fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch((error) => {
console.error('Could not fetch Wasm feature data:', error);
return null;
});
}
return featureDataPromise;
}
async connectedCallback() {
this.renderLoading();
const { features, browsers } = await WasmCompat.fetchFeatureData();
if (features && this.featureId) {
const feature = features[this.featureId];
if (feature) {
this.render(feature, browsers);
} else {
this.renderError(`Feature "${this.featureId}" not found.`);
}
} else {
this.renderError('Could not load WebAssembly feature data.');
}
}
renderLoading() {
this.shadowRoot.innerHTML = `
Loading feature: ${this.featureId}…
`;
}
renderError(message) {
this.shadowRoot.innerHTML = `
`;
}
render(feature, browsers) {
const browserSupport = browsers ? Object.entries(browsers) : [];
this.shadowRoot.innerHTML = `
${Object.keys(browsers).map((engineName) => {
const engine = browsers[engineName];
return `
${engineName}
| `;
}).join('')}
${browserSupport.length > 0 ? browserSupport.map(([, supportData]) => this.createCell(supportData.features[this.featureId])).join('') : this.createEmptyRow()}
`;
}
createCell(version) {
let remarks = '';
let versionNumber = version;
if (Array.isArray(version)) {
versionNumber =
version[0] === 'flag'
? ''
: version[0];
remarks = version[1];
remarks = remarks.replace(/`(.+?)`/, '$1
');
}
const isSupported = typeof versionNumber === 'string';
const versionText = isSupported
? `${versionNumber}${remarks ? ' ' + `More
${remarks}
` : ''}`
: 'N/A';
const supportClass = isSupported ? 'supported' : 'unsupported';
const supportIcon = isSupported
? ``
: ``;
return `
${supportIcon}
${versionText}
| `;
}
createEmptyRow() {
return `
No browser support information available for this feature. |
`;
}
getStyles() {
return `
:host {
display: block;
margin-bottom: 2rem;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.container {
padding: 1.5rem;
}
.feature-title {
text-align: left;
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.feature-title a {
color: currentColor;
text-decoration: none;
}
.feature-id {
font-size: 0.875rem;
margin-bottom: 1.5rem;
}
.feature-id code {
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-family: monospace;
}
.table-wrapper {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
}
th, td {
padding: 0.75rem 1rem;
}
th {
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.05rem;
}
td {
font-size: 0.95rem;
}
.engine-cell {
display: flex;
align-items: center;
gap: 0.75rem;
}
.logo {
width: auto;
height: 24px;
}
.support-cell {
white-space: pre;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
}
.version-info {
display: flex;
gap: 0.25rem;
align-items: center;
}
.support-details {
display: inline-block;
font-size: 0.8rem;
}
.support-details summary {
font-weight: bold;
cursor: pointer;
}
.support-details div {
white-space: normal;
max-width: 40ch;
}
.support-details code {
white-space: pre;
}
.supported {
color: #10b981;
}
.unsupported {
color: #ef4444;
}
.check-icon { color: #10b981; }
.x-icon { color: #ef4444; }
.empty-cell {
text-align: center;
color: #6b7280;
padding: 2rem;
}
.loading-container, .error-container {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
color: #4b5563;
gap: 0.75rem;
}
.error-container {
color: #b91c1c;
background-color: #fee2e2;
}
.spinner {
width: 24px;
height: 24px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}`;
}
}
customElements.define('wasm-compat', WasmCompat);