Web Component and Shadow DOM Analysis: Modern Web Architecture
A practical Fusebox guide to web component and shadow dom analysis.
Web Component and Shadow DOM Analysis: Modern Web Architecture
Web Components represent a suite of technologies allowing you to create reusable custom elements with encapsulated functionality. This guide explores how to detect, analyze, and understand Web Components and Shadow DOM implementations in modern web applications.
Run This Web Component Analyzer Now
// Copy and paste this into your browser console to analyze Web Components
class WebComponentAnalyzer {
constructor() {
this.customElements = [];
this.shadowRoots = [];
this.slots = [];
this.templates = [];
this.adoptedStylesheets = [];
this.stats = {
totalCustomElements: 0,
totalShadowRoots: 0,
openShadowRoots: 0,
closedShadowRoots: 0,
slotsUsed: 0,
templatesFound: 0
};
}
analyzeWebComponents() {
console.log('=
Analyzing Web Components and Shadow DOM...\n');
// Detect custom elements
this.detectCustomElements();
// Find shadow roots
this.findShadowRoots();
// Analyze templates
this.analyzeTemplates();
// Check for adopted stylesheets
this.checkAdoptedStylesheets();
// Analyze component communication
this.analyzeComponentCommunication();
// Generate report
this.generateReport();
}
detectCustomElements() {
// Get all registered custom elements
const registry = window.customElements;
// Check for custom element definitions
if (registry && registry.get) {
// Common custom element names to check
const commonPrefixes = ['app-', 'x-', 'my-', 'custom-', 'wc-'];
const allElements = document.querySelectorAll('*');
allElements.forEach(element => {
const tagName = element.tagName.toLowerCase();
// Check if it's a custom element (contains hyphen)
if (tagName.includes('-')) {
const definition = registry.get(tagName);
this.customElements.push({
tagName,
element,
isDefined: !!definition,
hasSlots: element.querySelector('slot') !== null,
hasShadowRoot: !!element.shadowRoot,
attributes: Array.from(element.attributes).map(attr => ({
name: attr.name,
value: attr.value
}))
});
this.stats.totalCustomElements++;
}
});
}
console.log(`Found ${this.stats.totalCustomElements} custom elements`);
}
findShadowRoots() {
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
{
acceptNode: () => NodeFilter.FILTER_ACCEPT
}
);
let node;
while (node = walker.nextNode()) {
if (node.shadowRoot) {
const shadowInfo = {
host: node,
tagName: node.tagName.toLowerCase(),
mode: node.shadowRoot.mode,
childrenCount: node.shadowRoot.children.length,
hasStyles: node.shadowRoot.styleSheets.length > 0,
slots: Array.from(node.shadowRoot.querySelectorAll('slot')).map(slot => ({
name: slot.name || 'default',
assignedNodes: slot.assignedNodes().length
}))
};
this.shadowRoots.push(shadowInfo);
this.stats.totalShadowRoots++;
if (shadowInfo.mode === 'open') {
this.stats.openShadowRoots++;
// Analyze shadow DOM content
this.analyzeShadowContent(node.shadowRoot);
} else {
this.stats.closedShadowRoots++;
}
}
}
}
analyzeShadowContent(shadowRoot) {
// Check for slots
const slots = shadowRoot.querySelectorAll('slot');
slots.forEach(slot => {
this.slots.push({
name: slot.name || 'default',
assignedNodes: slot.assignedNodes(),
assignedElements: slot.assignedElements()
});
this.stats.slotsUsed++;
});
// Check for styles
const styles = shadowRoot.querySelectorAll('style');
if (styles.length > 0) {
console.log(` Shadow DOM contains ${styles.length} style tags`);
}
// Check for adopted stylesheets
if (shadowRoot.adoptedStyleSheets && shadowRoot.adoptedStyleSheets.length > 0) {
this.adoptedStylesheets.push({
host: shadowRoot.host,
count: shadowRoot.adoptedStyleSheets.length
});
}
}
analyzeTemplates() {
const templates = document.querySelectorAll('template');
templates.forEach(template => {
const templateInfo = {
id: template.id,
hasContent: template.content.children.length > 0,
childrenCount: template.content.children.length,
usesSlots: template.content.querySelector('slot') !== null,
parent: template.parentElement?.tagName.toLowerCase()
};
this.templates.push(templateInfo);
this.stats.templatesFound++;
});
}
checkAdoptedStylesheets() {
// Check if Constructable Stylesheets are supported
if ('adoptedStyleSheets' in document) {
console.log(' Constructable Stylesheets supported');
// Check document-level adopted stylesheets
if (document.adoptedStyleSheets.length > 0) {
console.log(`=� Document has ${document.adoptedStyleSheets.length} adopted stylesheets`);
}
}
}
analyzeComponentCommunication() {
console.log('\n= Component Communication Analysis:');
// Check for custom events
const customEventPatterns = [
'custom-',
'component-',
'app-',
'change',
'update'
];
// Monitor for custom events (sampling approach)
const eventListener = (e) => {
if (e.type.includes('-') || customEventPatterns.some(p => e.type.includes(p))) {
console.log(` Custom event detected: ${e.type} from ${e.target.tagName}`);
}
};
// Add temporary listener
document.addEventListener('click', eventListener, true);
setTimeout(() => {
document.removeEventListener('click', eventListener, true);
}, 100);
// Check for properties/attributes that suggest communication
this.customElements.forEach(comp => {
const commProps = comp.attributes.filter(attr =>
attr.name.startsWith('on') ||
attr.name.includes('callback') ||
attr.name.includes('handler')
);
if (commProps.length > 0) {
console.log(` ${comp.tagName} has event handlers:`, commProps.map(p => p.name));
}
});
}
generateReport() {
console.log('\n=� Web Components Analysis Report\n');
// Summary statistics
console.log('=� Summary:');
console.log(` Custom Elements: ${this.stats.totalCustomElements}`);
console.log(` Shadow Roots: ${this.stats.totalShadowRoots} (${this.stats.openShadowRoots} open, ${this.stats.closedShadowRoots} closed)`);
console.log(` Slots Used: ${this.stats.slotsUsed}`);
console.log(` Templates: ${this.stats.templatesFound}`);
// Custom elements details
if (this.customElements.length > 0) {
console.log('\n<� Custom Elements:');
// Group by prefix
const grouped = this.groupByPrefix(this.customElements);
Object.entries(grouped).forEach(([prefix, elements]) => {
console.log(`\n ${prefix}* (${elements.length} elements)`);
elements.slice(0, 3).forEach(el => {
const features = [];
if (el.hasShadowRoot) features.push('Shadow DOM');
if (el.hasSlots) features.push('Slots');
if (!el.isDefined) features.push('� Not defined');
console.log(` - ${el.tagName} ${features.length > 0 ? `[${features.join(', ')}]` : ''}`);
});
});
}
// Shadow DOM details
if (this.shadowRoots.length > 0) {
console.log('\n=e Shadow DOM Usage:');
this.shadowRoots.slice(0, 5).forEach(shadow => {
console.log(` ${shadow.tagName}:`);
console.log(` Mode: ${shadow.mode}`);
console.log(` Children: ${shadow.childrenCount}`);
if (shadow.slots.length > 0) {
console.log(` Slots: ${shadow.slots.map(s => s.name).join(', ')}`);
}
});
}
// Templates
if (this.templates.length > 0) {
console.log('\n=� Templates:');
this.templates.forEach(template => {
console.log(` ${template.id || 'unnamed'}: ${template.childrenCount} children`);
});
}
// Generate best practices
this.generateBestPractices();
}
groupByPrefix(elements) {
const grouped = {};
elements.forEach(el => {
const prefix = el.tagName.split('-')[0];
if (!grouped[prefix]) {
grouped[prefix] = [];
}
grouped[prefix].push(el);
});
return grouped;
}
generateBestPractices() {
console.log('\n=� Recommendations:');
if (this.stats.closedShadowRoots > 0) {
console.log(' � Closed shadow roots detected - consider using open mode for better tooling support');
}
if (this.customElements.some(el => !el.isDefined)) {
console.log(' � Undefined custom elements found - ensure all custom elements are registered');
}
if (this.stats.totalCustomElements > 0 && this.adoptedStylesheets.length === 0) {
console.log(' =� Consider using Constructable Stylesheets for better performance');
}
if (this.slots.some(slot => slot.assignedNodes.length === 0)) {
console.log(' 9 Empty slots detected - provide default content for better UX');
}
}
}
// Run the analyzer
const analyzer = new WebComponentAnalyzer();
analyzer.analyzeWebComponents();
Understanding Web Components
Web Components consist of three main technologies:
- Custom Elements: Define new HTML elements
- Shadow DOM: Encapsulate styles and markup
- HTML Templates: Define markup templates
Advanced Shadow DOM Inspector
// Enhanced Shadow DOM exploration tool
class ShadowDOMInspector {
constructor() {
this.shadowHosts = new Map();
this.styleIsolation = [];
this.eventRetargeting = [];
}
inspectShadowDOM() {
console.log('=& Deep Shadow DOM Inspection...\n');
// Find all shadow hosts
this.findAllShadowHosts();
// Analyze style isolation
this.analyzeStyleIsolation();
// Check event retargeting
this.checkEventRetargeting();
// Analyze slot distribution
this.analyzeSlotDistribution();
// Check for CSS custom properties
this.analyzeCSSCustomProperties();
// Generate detailed report
this.generateDetailedReport();
}
findAllShadowHosts(root = document.body, depth = 0) {
const walker = document.createTreeWalker(
root,
NodeFilter.SHOW_ELEMENT
);
let node;
while (node = walker.nextNode()) {
if (node.shadowRoot) {
const shadowData = {
host: node,
depth,
mode: node.shadowRoot.mode,
styles: this.extractStyles(node.shadowRoot),
customProperties: this.extractCustomProperties(node.shadowRoot),
eventListeners: this.detectEventListeners(node)
};
this.shadowHosts.set(node, shadowData);
// Recursively search within shadow roots
if (node.shadowRoot.mode === 'open') {
this.findAllShadowHosts(node.shadowRoot, depth + 1);
}
}
}
}
extractStyles(shadowRoot) {
const styles = {
inline: [],
adopted: [],
external: []
};
// Inline styles
shadowRoot.querySelectorAll('style').forEach(style => {
styles.inline.push({
content: style.textContent.substring(0, 100) + '...',
rules: style.sheet ? style.sheet.cssRules.length : 0
});
});
// Adopted stylesheets
if (shadowRoot.adoptedStyleSheets) {
shadowRoot.adoptedStyleSheets.forEach(sheet => {
styles.adopted.push({
rules: sheet.cssRules.length
});
});
}
// External stylesheets
shadowRoot.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
styles.external.push({
href: link.href
});
});
return styles;
}
extractCustomProperties(shadowRoot) {
const customProps = new Set();
// Check inline styles for custom properties
shadowRoot.querySelectorAll('style').forEach(style => {
if (style.sheet) {
Array.from(style.sheet.cssRules).forEach(rule => {
if (rule.style) {
const cssText = rule.style.cssText;
const matches = cssText.match(/--[\w-]+/g);
if (matches) {
matches.forEach(prop => customProps.add(prop));
}
}
});
}
});
return Array.from(customProps);
}
detectEventListeners(element) {
// Get event listeners (Chrome DevTools API if available)
if (window.getEventListeners) {
return Object.keys(window.getEventListeners(element));
}
// Fallback: check for common event attributes
const eventAttrs = Array.from(element.attributes)
.filter(attr => attr.name.startsWith('on'))
.map(attr => attr.name.substring(2));
return eventAttrs;
}
analyzeStyleIsolation() {
console.log('<� Analyzing Style Isolation...\n');
this.shadowHosts.forEach((data, host) => {
// Check if styles leak out
const hostStyles = window.getComputedStyle(host);
// Test style isolation
const testElement = document.createElement('div');
testElement.className = 'shadow-test-element';
// Add to shadow and light DOM to compare
if (data.mode === 'open') {
const shadowTest = testElement.cloneNode();
host.shadowRoot.appendChild(shadowTest);
const lightTest = testElement.cloneNode();
document.body.appendChild(lightTest);
// Compare computed styles
const shadowStyles = window.getComputedStyle(shadowTest);
const lightStyles = window.getComputedStyle(lightTest);
this.styleIsolation.push({
host: host.tagName,
isolated: shadowStyles.color !== lightStyles.color
});
// Cleanup
shadowTest.remove();
lightTest.remove();
}
});
}
checkEventRetargeting() {
console.log('<� Checking Event Retargeting...\n');
// Set up event listener to check retargeting
const checkRetargeting = (e) => {
if (e.target !== e.composedPath()[0]) {
this.eventRetargeting.push({
type: e.type,
target: e.target.tagName,
originalTarget: e.composedPath()[0].tagName,
retargeted: true
});
}
};
// Temporary listener
document.addEventListener('click', checkRetargeting, true);
// Simulate clicks on shadow elements
this.shadowHosts.forEach((data, host) => {
if (data.mode === 'open' && host.shadowRoot.firstElementChild) {
const event = new MouseEvent('click', { bubbles: true });
host.shadowRoot.firstElementChild.dispatchEvent(event);
}
});
document.removeEventListener('click', checkRetargeting, true);
}
analyzeSlotDistribution() {
console.log('<� Analyzing Slot Distribution...\n');
const slotAnalysis = [];
this.shadowHosts.forEach((data, host) => {
if (data.mode === 'open') {
const slots = host.shadowRoot.querySelectorAll('slot');
slots.forEach(slot => {
const slotInfo = {
name: slot.name || 'default',
assignedNodes: slot.assignedNodes(),
assignedElements: slot.assignedElements(),
fallback: slot.childNodes.length > 0
};
slotAnalysis.push({
host: host.tagName,
slot: slotInfo
});
});
}
});
return slotAnalysis;
}
analyzeCSSCustomProperties() {
const customPropertyUsage = new Map();
this.shadowHosts.forEach((data, host) => {
data.customProperties.forEach(prop => {
if (!customPropertyUsage.has(prop)) {
customPropertyUsage.set(prop, []);
}
customPropertyUsage.set(prop, [...customPropertyUsage.get(prop), host.tagName]);
});
});
return customPropertyUsage;
}
generateDetailedReport() {
console.log('\n=� Detailed Shadow DOM Report\n');
// Shadow host hierarchy
console.log('<3 Shadow Host Hierarchy:');
const hostsByDepth = new Map();
this.shadowHosts.forEach((data, host) => {
if (!hostsByDepth.has(data.depth)) {
hostsByDepth.set(data.depth, []);
}
hostsByDepth.get(data.depth).push({
tag: host.tagName.toLowerCase(),
mode: data.mode
});
});
hostsByDepth.forEach((hosts, depth) => {
console.log(` Level ${depth}: ${hosts.map(h => `${h.tag}(${h.mode})`).join(', ')}`);
});
// Style analysis
console.log('\n<� Style Encapsulation:');
let totalStyles = 0;
this.shadowHosts.forEach((data, host) => {
const styleCount = data.styles.inline.length +
data.styles.adopted.length +
data.styles.external.length;
if (styleCount > 0) {
console.log(` ${host.tagName}: ${styleCount} stylesheets`);
totalStyles += styleCount;
}
});
// Custom properties
const customProps = this.analyzeCSSCustomProperties();
if (customProps.size > 0) {
console.log('\n<� CSS Custom Properties:');
customProps.forEach((components, prop) => {
console.log(` ${prop}: used by ${components.join(', ')}`);
});
}
// Performance considerations
console.log('\n� Performance Considerations:');
console.log(` Total shadow roots: ${this.shadowHosts.size}`);
console.log(` Total encapsulated styles: ${totalStyles}`);
if (this.shadowHosts.size > 50) {
console.log(' � Large number of shadow roots may impact performance');
}
}
}
// Run the inspector
const inspector = new ShadowDOMInspector();
inspector.inspectShadowDOM();
Web Component Lifecycle Monitor
// Monitor Web Component lifecycle events
class ComponentLifecycleMonitor {
constructor() {
this.lifecycleEvents = [];
this.performanceMetrics = new Map();
}
monitorComponent(ComponentClass) {
const monitor = this;
return class extends ComponentClass {
constructor() {
const start = performance.now();
super();
monitor.recordEvent(this.tagName, 'constructor', performance.now() - start);
}
connectedCallback() {
const start = performance.now();
if (super.connectedCallback) {
super.connectedCallback();
}
monitor.recordEvent(this.tagName, 'connected', performance.now() - start);
monitor.analyzeRenderPerformance(this);
}
disconnectedCallback() {
const start = performance.now();
if (super.disconnectedCallback) {
super.disconnectedCallback();
}
monitor.recordEvent(this.tagName, 'disconnected', performance.now() - start);
}
attributeChangedCallback(name, oldValue, newValue) {
const start = performance.now();
if (super.attributeChangedCallback) {
super.attributeChangedCallback(name, oldValue, newValue);
}
monitor.recordEvent(this.tagName, 'attributeChanged', performance.now() - start, {
attribute: name,
oldValue,
newValue
});
}
adoptedCallback() {
const start = performance.now();
if (super.adoptedCallback) {
super.adoptedCallback();
}
monitor.recordEvent(this.tagName, 'adopted', performance.now() - start);
}
};
}
recordEvent(tagName, event, duration, details = {}) {
this.lifecycleEvents.push({
tagName,
event,
duration,
timestamp: performance.now(),
details
});
// Update performance metrics
if (!this.performanceMetrics.has(tagName)) {
this.performanceMetrics.set(tagName, {
totalTime: 0,
eventCount: 0,
events: {}
});
}
const metrics = this.performanceMetrics.get(tagName);
metrics.totalTime += duration;
metrics.eventCount++;
if (!metrics.events[event]) {
metrics.events[event] = {
count: 0,
totalTime: 0,
avgTime: 0
};
}
metrics.events[event].count++;
metrics.events[event].totalTime += duration;
metrics.events[event].avgTime = metrics.events[event].totalTime / metrics.events[event].count;
}
analyzeRenderPerformance(element) {
// Measure time to first paint after connection
requestAnimationFrame(() => {
const renderTime = performance.now();
// Check if element has shadow DOM
if (element.shadowRoot) {
const shadowElements = element.shadowRoot.querySelectorAll('*').length;
this.recordEvent(element.tagName, 'render', 0, {
shadowElements,
hasStyles: element.shadowRoot.styleSheets.length > 0
});
}
});
}
generateReport() {
console.log('\n� Component Lifecycle Performance Report\n');
this.performanceMetrics.forEach((metrics, tagName) => {
console.log(`=� ${tagName}:`);
console.log(` Total time: ${metrics.totalTime.toFixed(2)}ms`);
console.log(` Events fired: ${metrics.eventCount}`);
Object.entries(metrics.events).forEach(([event, data]) => {
console.log(` ${event}: ${data.count}x (avg ${data.avgTime.toFixed(2)}ms)`);
});
console.log('');
});
// Find slow components
const slowComponents = Array.from(this.performanceMetrics.entries())
.filter(([_, metrics]) => metrics.totalTime > 50)
.sort((a, b) => b[1].totalTime - a[1].totalTime);
if (slowComponents.length > 0) {
console.log('� Slow Components:');
slowComponents.forEach(([tagName, metrics]) => {
console.log(` ${tagName}: ${metrics.totalTime.toFixed(2)}ms total`);
});
}
}
}
// Example usage:
// const monitor = new ComponentLifecycleMonitor();
// customElements.define('my-element', monitor.monitorComponent(MyElement));
Component Communication Analyzer
// Analyze how Web Components communicate
class ComponentCommunicationAnalyzer {
constructor() {
this.communications = [];
this.propertyBindings = new Map();
this.eventFlows = [];
this.setupListeners();
}
setupListeners() {
// Override dispatchEvent to track custom events
const originalDispatch = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function(event) {
if (event.type.includes('-') || event.constructor.name !== 'Event') {
this.communications.push({
type: 'event',
eventType: event.type,
source: this.tagName || this.constructor.name,
timestamp: performance.now(),
detail: event.detail,
bubbles: event.bubbles,
composed: event.composed
});
}
return originalDispatch.call(this, event);
}.bind(this);
}
analyzePropertyBindings() {
const components = document.querySelectorAll('*');
components.forEach(component => {
if (component.tagName.includes('-')) {
// Check for property setters
const proto = Object.getPrototypeOf(component);
const descriptors = Object.getOwnPropertyDescriptors(proto);
Object.entries(descriptors).forEach(([prop, descriptor]) => {
if (descriptor.set && !prop.startsWith('_')) {
this.propertyBindings.set(`${component.tagName}.${prop}`, {
hasGetter: !!descriptor.get,
hasSetter: !!descriptor.set,
configurable: descriptor.configurable
});
}
});
}
});
}
traceEventFlow(eventType, duration = 1000) {
const trace = [];
const captureHandler = (e) => {
trace.push({
phase: 'capture',
target: e.currentTarget.tagName,
timestamp: performance.now()
});
};
const bubbleHandler = (e) => {
trace.push({
phase: 'bubble',
target: e.currentTarget.tagName,
timestamp: performance.now()
});
};
// Add listeners to all elements
document.querySelectorAll('*').forEach(el => {
el.addEventListener(eventType, captureHandler, true);
el.addEventListener(eventType, bubbleHandler, false);
});
// Remove after duration
setTimeout(() => {
document.querySelectorAll('*').forEach(el => {
el.removeEventListener(eventType, captureHandler, true);
el.removeEventListener(eventType, bubbleHandler, false);
});
if (trace.length > 0) {
this.eventFlows.push({
eventType,
flow: trace
});
}
}, duration);
}
generateCommunicationMap() {
console.log('\n=� Component Communication Map\n');
// Event communication
if (this.communications.length > 0) {
console.log('=� Event Communications:');
const eventsByType = {};
this.communications.forEach(comm => {
if (!eventsByType[comm.eventType]) {
eventsByType[comm.eventType] = [];
}
eventsByType[comm.eventType].push(comm);
});
Object.entries(eventsByType).forEach(([type, events]) => {
console.log(` ${type}: ${events.length} dispatches`);
const sources = [...new Set(events.map(e => e.source))];
console.log(` Sources: ${sources.join(', ')}`);
});
}
// Property bindings
if (this.propertyBindings.size > 0) {
console.log('\n= Property Bindings:');
this.propertyBindings.forEach((binding, prop) => {
console.log(` ${prop}:`);
console.log(` Getter: ${binding.hasGetter ? '' : 'L'}`);
console.log(` Setter: ${binding.hasSetter ? '' : 'L'}`);
});
}
}
}
Security Considerations for Web Components
// Security analyzer for Web Components
class WebComponentSecurityAnalyzer {
constructor() {
this.vulnerabilities = [];
this.trustedTypes = 'trustedTypes' in window;
}
analyzeSecurityRisks() {
console.log('= Web Component Security Analysis\n');
// Check for innerHTML usage
this.checkInnerHTMLUsage();
// Check for eval or Function constructor
this.checkDynamicCodeExecution();
// Check Shadow DOM boundaries
this.checkShadowDOMSecurity();
// Check for XSS vulnerabilities
this.checkXSSVulnerabilities();
// Generate security report
this.generateSecurityReport();
}
checkInnerHTMLUsage() {
const components = document.querySelectorAll('*');
components.forEach(component => {
if (component.tagName.includes('-')) {
// Check component definition
const proto = Object.getPrototypeOf(component);
const methods = Object.getOwnPropertyNames(proto);
methods.forEach(method => {
if (typeof component[method] === 'function') {
const fnString = component[method].toString();
if (fnString.includes('innerHTML')) {
this.vulnerabilities.push({
type: 'innerHTML',
component: component.tagName,
method: method,
severity: 'high',
recommendation: 'Use textContent or createElement instead'
});
}
}
});
}
});
}
checkDynamicCodeExecution() {
const dangerous = ['eval', 'Function', 'setTimeout', 'setInterval'];
document.querySelectorAll('script').forEach(script => {
const content = script.textContent;
dangerous.forEach(fn => {
if (content.includes(fn + '(')) {
this.vulnerabilities.push({
type: 'dynamic-code',
function: fn,
severity: 'critical',
recommendation: 'Avoid dynamic code execution'
});
}
});
});
}
checkShadowDOMSecurity() {
const shadowHosts = [];
document.querySelectorAll('*').forEach(el => {
if (el.shadowRoot) {
shadowHosts.push({
element: el,
mode: el.shadowRoot.mode
});
// Check for form elements in shadow DOM
const forms = el.shadowRoot.querySelectorAll('form, input, textarea');
if (forms.length > 0) {
this.vulnerabilities.push({
type: 'shadow-form',
component: el.tagName,
severity: 'medium',
recommendation: 'Ensure proper form validation in Shadow DOM'
});
}
}
});
}
checkXSSVulnerabilities() {
// Check for attribute reflection
const components = document.querySelectorAll('*');
components.forEach(component => {
if (component.tagName.includes('-')) {
// Check for dangerous attribute names
const dangerousAttrs = ['href', 'src', 'action', 'formaction'];
dangerousAttrs.forEach(attr => {
if (component.hasAttribute(attr)) {
const value = component.getAttribute(attr);
if (value.startsWith('javascript:')) {
this.vulnerabilities.push({
type: 'xss-attribute',
component: component.tagName,
attribute: attr,
severity: 'critical',
recommendation: 'Sanitize attribute values'
});
}
}
});
}
});
}
generateSecurityReport() {
console.log('=� Security Report:\n');
if (this.vulnerabilities.length === 0) {
console.log(' No major security vulnerabilities detected');
} else {
console.log(`� Found ${this.vulnerabilities.length} potential vulnerabilities:\n`);
// Group by severity
const bySeverity = {
critical: [],
high: [],
medium: [],
low: []
};
this.vulnerabilities.forEach(vuln => {
bySeverity[vuln.severity].push(vuln);
});
Object.entries(bySeverity).forEach(([severity, vulns]) => {
if (vulns.length > 0) {
console.log(`${severity.toUpperCase()} (${vulns.length}):`);
vulns.forEach(vuln => {
console.log(` - ${vuln.type}: ${vuln.component || vuln.function}`);
console.log(` ${vuln.recommendation}`);
});
}
});
}
// Best practices
console.log('\n= Security Best Practices:');
console.log(' 1. Use Trusted Types API when available');
console.log(' 2. Sanitize all user inputs');
console.log(' 3. Avoid innerHTML, use textContent');
console.log(' 4. Use closed Shadow DOM for sensitive components');
console.log(' 5. Implement Content Security Policy');
}
}
// Run security analyzer
const securityAnalyzer = new WebComponentSecurityAnalyzer();
securityAnalyzer.analyzeSecurityRisks();
Performance Optimization Strategies
// Web Component performance optimizer
class WebComponentOptimizer {
static optimizationStrategies = {
lazyDefine() {
// Lazy define components only when needed
const lazyDefine = (tagName, componentModule) => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !customElements.get(tagName)) {
import(componentModule).then(module => {
customElements.define(tagName, module.default);
});
observer.disconnect();
}
});
});
document.querySelectorAll(tagName).forEach(el => {
observer.observe(el);
});
};
return lazyDefine;
},
shadowDOMCache() {
// Cache shadow DOM templates
const templateCache = new Map();
return {
get(templateId) {
if (!templateCache.has(templateId)) {
const template = document.getElementById(templateId);
if (template) {
templateCache.set(templateId, template.content.cloneNode(true));
}
}
return templateCache.get(templateId)?.cloneNode(true);
}
};
},
adoptedStylesheetsOptimization() {
// Share stylesheets across components
const sharedStyles = new Map();
return {
getStylesheet(css) {
if (!sharedStyles.has(css)) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
sharedStyles.set(css, sheet);
}
return sharedStyles.get(css);
}
};
},
propertyChangeDebounce() {
// Debounce property changes
return function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
};
}
};
}
// Example optimized component
class OptimizedComponent extends HTMLElement {
static template = WebComponentOptimizer.optimizationStrategies.shadowDOMCache();
static styles = WebComponentOptimizer.optimizationStrategies.adoptedStylesheetsOptimization();
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Use cached template
const template = OptimizedComponent.template.get('optimized-template');
if (template) {
this.shadowRoot.appendChild(template);
}
// Use shared stylesheet
const styles = OptimizedComponent.styles.getStylesheet(`
:host {
display: block;
padding: 1rem;
}
`);
this.shadowRoot.adoptedStyleSheets = [styles];
}
// Debounced property setter
set value(val) {
this._value = val;
this.debouncedUpdate();
}
debouncedUpdate = WebComponentOptimizer.optimizationStrategies
.propertyChangeDebounce()(this.update.bind(this), 100);
update() {
// Expensive update operation
this.render();
}
}
Real-World Examples
1. YouTube (Polymer Elements)
YouTube uses Web Components extensively for video players and UI components, achieving consistent behavior across different pages.
2. GitHub (Custom Elements)
GitHub's interface uses Web Components for complex UI elements like file trees and code editors.
3. Material Design Components
Google's Material Design Web Components provide reusable, accessible components following Material Design guidelines.
Best Practices Summary
// Web Components best practices checklist
const webComponentsBestPractices = {
naming: [
'Use lowercase with hyphens (kebab-case)',
'Include a namespace prefix (e.g., "app-button")',
'Be descriptive but concise'
],
shadowDOM: [
'Use "open" mode unless security requires "closed"',
'Provide ::part() for styling flexibility',
'Use slots for content projection',
'Include default slot content'
],
performance: [
'Lazy load component definitions',
'Use adoptedStyleSheets for shared styles',
'Minimize shadow DOM rebuilds',
'Debounce property updates'
],
accessibility: [
'Implement proper ARIA attributes',
'Ensure keyboard navigation',
'Provide focus management',
'Test with screen readers'
],
security: [
'Avoid innerHTML with user content',
'Sanitize attribute values',
'Use Trusted Types when available',
'Validate all inputs'
]
};
console.log('=� Web Components Best Practices:', webComponentsBestPractices);
Conclusion
Web Components and Shadow DOM provide powerful encapsulation and reusability features for modern web development. By understanding how to detect, analyze, and optimize these technologies, developers can build more maintainable and performant applications. Regular analysis helps ensure components follow best practices for security, performance, and accessibility.