Web Components 2025 : Enfin mainstream après 10 ans
Lancés en 2013, les Web Components ont longtemps été perçus comme "le futur du web qui n'arrive jamais". 2025 marque le tournant : support universel navigateurs sans polyfills, frameworks matures (Lit 5.0, Stencil 4.0), et adoption massive par Google, Salesforce, Adobe.
Market snapshot octobre 2025 :
- 78% sites Top 10k utilisent Web Components (HTTP Archive)
- 2,8 millions packages npm basés sur Web Components
- 42% entreprises Fortune 500 ont un design system Web Components
Catalyseurs 2025 :
- Support complet Safari 18 (septembre 2025, dernier holdout)
- Lit 5.0 (Google) et Stencil 4.0 (Ionic) releases majeures
- Abandon progressif React/Vue dans design systems (isolation préférée)
Les 4 standards Web Components
1. Custom Elements
Définir balises HTML custom :
// Définir un composant
class MyButton extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<button>
<slot></slot>
</button>
`;
}
}
// Enregistrer dans le navigateur
customElements.define('my-button', MyButton);
<!-- Utiliser comme balise HTML native -->
<my-button>Click me</my-button>
Lifecycle callbacks :
connectedCallback(): Composant ajouté au DOMdisconnectedCallback(): Composant retiré du DOMattributeChangedCallback(): Attribut modifiéadoptedCallback(): Composant déplacé vers nouveau document
2. Shadow DOM
Encapsulation CSS/JS complète :
class IsolatedComponent extends HTMLElement {
connectedCallback() {
// Créer Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
/* CSS isolé, n'affecte PAS le reste de la page */
p { color: red; }
</style>
<p>This text is red</p>
`;
}
}
customElements.define('isolated-component', IsolatedComponent);
<style>
/* Ce style n'affecte PAS le composant */
p { color: blue; }
</style>
<p>This is blue</p>
<isolated-component></isolated-component> <!-- Text red inside -->
Avantage : Zero conflits CSS (plus de BEM, CSS Modules, ou CSS-in-JS requis).
3. HTML Templates
Templates réutilisables :
<template id="card-template">
<style>
.card { border: 1px solid black; padding: 1rem; }
</style>
<div class="card">
<h2><slot name="title"></slot></h2>
<p><slot name="content"></slot></p>
</div>
</template>
<script>
class MyCard extends HTMLElement {
connectedCallback() {
const template = document.getElementById('card-template');
const clone = template.content.cloneNode(true);
this.attachShadow({ mode: 'open' }).appendChild(clone);
}
}
customElements.define('my-card', MyCard);
</script>
<my-card>
<span slot="title">Card Title</span>
<span slot="content">Card content here</span>
</my-card>
4. ES Modules
Import natif navigateur :
<script type="module">
import { MyComponent } from './my-component.js';
customElements.define('my-component', MyComponent);
</script>
Support : 100% navigateurs modernes (Chrome 61+, Safari 11+, Firefox 60+).
Lit 5.0 vs Stencil 4.0 : Bataille des frameworks
Philosophies opposées
Lit 5.0 (Google) :
- Librairie légère (5 KB gzipped)
- Approche runtime (pas de compilation)
- Syntaxe proche vanilla Web Components
- Optimisé performance pure
Stencil 4.0 (Ionic) :
- Framework complet (tooling intégré)
- Compilateur ahead-of-time (AOT)
- Génération automatique wrappers React/Vue/Angular
- Optimisé developer experience enterprise
Lit 5.0 : Performance maximale
Installation :
npm install lit@5
Composant Lit 5.0 :
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('user-profile')
export class UserProfile extends LitElement {
// CSS scoped automatiquement (Shadow DOM)
static styles = css`
:host {
display: block;
padding: 1rem;
border: 2px solid var(--primary-color, blue);
}
h2 { margin: 0; }
`;
// Propriétés réactives (re-render auto)
@property({ type: String }) name = '';
@property({ type: Number }) age = 0;
render() {
return html`
<h2>${this.name}</h2>
<p>Age: ${this.age}</p>
<button @click=${this._handleClick}>Increment</button>
`;
}
private _handleClick() {
this.age++;
}
}
Usage :
<user-profile name="Alice" age="30"></user-profile>
Features Lit 5.0 :
- Reactive Properties automatiques **
@property({ type: String }) name = '';
// Changement name → Re-render automatique
- Template literals HTML **
html`<div>${expression}</div>`
// Parsing optimisé, XSS protection native
- Directives puissantes **
import { classMap, styleMap, repeat } from 'lit/directives';
render() {
return html`
<div class=${classMap({ active: this.isActive })}>
${repeat(this.items, item => item.id, (item) => html`<p>${item.name}</p>`)}
</div>
`;
}
Benchmarks Lit 5.0 (vs Lit 3.x) :
First render : 18% plus rapide
Update render : 32% plus rapide
Bundle size : moins 12% (4,8 KB vs 5,5 KB)
Stencil 4.0 : Enterprise-first
Installation :
npm init stencil component my-library
Composant Stencil 4.0 :
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'user-profile',
styleUrl: 'user-profile.css',
shadow: true
})
export class UserProfile {
@Prop() name: string = '';
@Prop() age: number = 0;
private handleClick = () => {
this.age++;
}
render() {
return (
<div>
<h2>{this.name}</h2>
<p>Age: {this.age}</p>
<button onClick={this.handleClick}>Increment</button>
</div>
);
}
}
Syntaxe JSX (familière pour devs React).
Features Stencil 4.0 :
- Output targets multiples **
// stencil.config.ts
export const config: Config = {
outputTargets: [
{ type: 'dist' }, // Vanilla JS
{ type: 'dist-custom-elements' }, // Custom Elements standalone
{
type: 'dist-react', // Wrappers React automatiques
dir: 'react-library'
},
{
type: 'dist-vue', // Wrappers Vue
dir: 'vue-library'
}
]
};
Résultat : 1 codebase → 4 distributions (Vanilla, React, Vue, Angular).
- Lazy loading natif **
// Stencil génère bundles optimisés automatiquement
// Composants chargés on-demand (pas de bundle monolithique)
- Pre-rendering SSR **
// stencil.config.ts
{
type: 'www',
serviceWorker: null,
prerender: {
crawlUrls: true // Pre-render toutes pages
}
}
- Testing intégré **
import { newSpecPage } from '@stencil/core/testing';
import { UserProfile } from './user-profile';
it('renders name', async () => {
const page = await newSpecPage({
components: [UserProfile],
html: `<user-profile name="Alice"></user-profile>`
});
expect(page.root).toEqualHtml(`
<user-profile name="Alice">
<h2>Alice</h2>
</user-profile>
`);
});
Comparaison chiffrée
| Critère | Lit 5.0 | Stencil 4.0 | Winner |
|---|---|---|---|
| Bundle size | 4,8 KB | 12 KB (runtime) | Lit |
| First render | 8ms | 12ms | Lit |
| Update render | 2ms | 3ms | Lit |
| Tooling | Basique | Complet (compiler, test runner) | Stencil |
| Framework wrappers | Manuel | Automatique (React, Vue, Angular) | Stencil |
| TypeScript | Support | First-class (decorators) | Stencil |
| Learning curve | Moyenne | Facile (si React background) | Stencil |
| SSR | Manuel | Intégré | Stencil |
Recommandations :
Choisir Lit 5.0 si :
- Performance critique (dashboards temps réel, trading apps)
- Bundle size prioritaire (progressive web apps)
- Flexibilité tooling (intégration custom)
Choisir Stencil 4.0 si :
- Design system multi-frameworks (React + Vue + Angular)
- Équipes grandes (outils standardisés requis)
- SSR/Pre-rendering nécessaire
- Migration depuis React (JSX familier)
Adoption enterprise : Cas d'usage 2025
Google Material Web Components
Projet : Material Design 3 en Web Components (Lit 5.0).
URL : https://material-web.dev
Composants : 40+ (buttons, cards, dialogs, data tables, etc.)
Usage :
<script type="module">
import '@material/web/button/filled-button.js';
</script>
<md-filled-button>Click me</md-filled-button>
Adoption :
- 12 000+ projets Google internes (octobre 2025)
- YouTube redesign 2025 utilise Material Web (70% UI)
- Google Search console migration en cours
Avantages Google :
- Cohérence design cross-products
- Performance (Lit 5.0 ultra-rapide)
- Pas de dépendances React/Vue (isolation complète)
Salesforce Lightning Web Components (LWC)
Basé sur : Web Components standards (compilateur custom).
Contexte : Salesforce a migré TOUT son UI vers LWC (2019-2025).
Chiffres :
- 8 000+ composants LWC dans Salesforce ecosystem
- 150 000+ développeurs Salesforce utilisent LWC quotidiennement
- Performance améliorée de 40% vs ancien framework (Aura)
Code exemple :
import { LightningElement, api } from 'lwc';
export default class Hello extends LightningElement {
@api name;
get greeting() {
return `Hello, ${this.name}!`;
}
}
<template>
<div>{greeting}</div>
</template>
Adobe Spectrum Web Components
Projet : Design system Adobe (Lit 2.x, migration Lit 5.0 en cours).
Produits : Adobe XD, Adobe Express, Creative Cloud web apps.
Approche : Hybrid (Web Components core + React wrappers optionnels).
Résultat :
- Réutilisation code augmentée de 65%
- Temps développement nouvelles features réduit de 35%
- Consistency bugs réduits de 82%
Intégration avec frameworks modernes
Web Components dans React
Problème historique : React passait props comme strings (pas objets).
Solution 2025 : React 19 support natif Web Components.
// React 19
import './my-component.js'; // Import Web Component
function App() {
const user = { name: 'Alice', age: 30 };
// React 19 passe objets correctement
return <user-profile user={user}></user-profile>;
}
Avant (React 18) :
// Workaround manuel requis
useEffect(() => {
const el = ref.current;
el.user = user; // Set property via DOM API
}, [user]);
return <user-profile ref={ref}></user-profile>;
Web Components dans Vue
Support natif excellent (depuis Vue 3) :
<script setup>
import './my-component.js';
import { ref } from 'vue';
const count = ref(0);
</script>
<template>
<!-- Vue passe props correctement automatiquement -->
<my-counter :count="count" @increment="count++"></my-counter>
</template>
Configuration (vite.config.js) :
export default {
plugins: [
vue({
template: {
compilerOptions: {
// Indiquer à Vue que my-* sont custom elements
isCustomElement: tag => tag.startsWith('my-')
}
}
})
]
};
Web Components dans Angular
Support first-class (Angular 15+) :
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA] // Activer custom elements
})
export class AppModule { }
<!-- app.component.html -->
<my-component [data]="userData" (event)="handleEvent($event)"></my-component>
Angular Elements (composants Angular → Web Components) :
import { createCustomElement } from '@angular/elements';
const MyComponentElement = createCustomElement(MyComponent, { injector });
customElements.define('my-angular-component', MyComponentElement);
Design Systems : L'avenir est framework-agnostic
Problème des design systems couplés
Approche classique (2015-2023) :
Design System React → Utilisable uniquement dans React
Design System Vue → Utilisable uniquement dans Vue
→ Duplication travail (même composants codés 3x)
Cas réel : Entreprise avec React (frontend public) + Angular (admin interne) + Vue (tool interne).
Solution avant :
- 3 design systems séparés
- Maintenance x3
- Inconsistencies visuelles
Solution 2025 : Web Components :
Design System Web Components →
├─ Wrappers React (auto-générés)
├─ Wrappers Vue (auto-générés)
├─ Wrappers Angular (auto-générés)
└─ Vanilla JS (natif)
→ 1 codebase, 4 distributions
Workflow Stencil Design System
- Développer composants Stencil **
// button.tsx
@Component({
tag: 'ds-button',
styleUrl: 'button.css',
shadow: true
})
export class Button {
@Prop() variant: 'primary' | 'secondary' = 'primary';
render() {
return <button class={this.variant}><slot></slot></button>;
}
}
- Build avec output targets multiples **
npm run build
# Génère :
# - dist/ (vanilla JS)
# - react/ (wrappers React)
# - vue/ (wrappers Vue)
# - angular/ (wrappers Angular)
- Publier packages séparés **
{
"name": "@company/design-system",
"name": "@company/design-system-react",
"name": "@company/design-system-vue",
"name": "@company/design-system-angular"
}
- Utiliser dans projets **
# Projet React
npm install @company/design-system-react
# Projet Vue
npm install @company/design-system-vue
Résultat :
- 1 équipe design system (au lieu de 3)
- Consistency garantie (même source code)
- Updates propagées automatiquement (update package → toutes apps)
Performance : Web Components vs Frameworks
Benchmarks (Octobre 2025, Chrome 128)
Test : Liste 1 000 items avec mise à jour fréquente.
Framework/Library | First Render | Update | Memory | Bundle
------------------|--------------|--------|--------|--------
Vanilla WC | 42ms | 8ms | 3,2MB | 0KB
Lit 5.0 | 48ms | 9ms | 3,4MB | 4,8KB
Stencil 4.0 | 54ms | 11ms | 3,8MB | 12KB
React 19 | 68ms | 14ms | 5,1MB | 45KB
Vue 3.5 | 58ms | 12ms | 4,2MB | 34KB
Svelte 5 | 44ms | 7ms | 2,9MB | 8KB
Winner : Svelte 5 (compile-time framework, pas runtime).
Web Components advantages :
- Zéro runtime framework (Vanilla WC)
- Bundles minuscules (Lit 5.0 seulement 4,8 KB)
- Interopérabilité (utilisable partout)
Frameworks advantages :
- Developer Experience (React hooks, Vue Composition API)
- Ecosystèmes riches (libraries, outils)
Tooling écosystème 2025
Open Web Components (OWC)
URL : https://open-wc.org
Features :
- Testing (Web Test Runner, Playwright)
- Linting (ESLint configs Web Components)
- Dev server (es-dev-server)
- Building (Rollup configs optimisés)
Installation :
npm init @open-wc
# Wizard interactif : Lit / Vanilla / Haunted
Web Components Analyzer
Génération documentation automatique :
npm install -D web-component-analyzer
wca analyze src/**/*.ts --format json --outFile custom-elements.json
Output : JSON décrivant tous composants (props, events, slots).
Utilisation :
- Storybook integration
- IDE autocompletion (VS Code Custom Data)
- Documentation sites automatiques
VS Code Extensions
lit-plugin :
- Syntax highlighting templates Lit
- Type-checking dans html`` tagged templates
- Autocomplete CSS variables
Custom Elements Manifest :
- Autocomplete balises custom
- Validation attributs/propriétés
Limites et challenges 2025
1. SEO Server-Side Rendering
Problème : Web Components nécessitent JavaScript pour render.
Impact SEO :
- Googlebot execute JavaScript (OK)
- Mais TTFB (Time to First Byte) plus lent qu'HTML pur
Solutions :
- Declarative Shadow DOM (standard 2023, support universel 2025)
- Stencil pre-rendering (génère HTML statique)
Declarative Shadow DOM :
<!-- Serveur envoie HTML pré-rendu -->
<user-profile>
<template shadowrootmode="open">
<style>p { color: red; }</style>
<p>Server-rendered content</p>
</template>
</user-profile>
<!-- JavaScript hydrate (pas re-render) -->
<script type="module" src="user-profile.js"></script>
2. Form participation
Problème : Custom elements ne participent pas aux forms nativement.
Exemple :
<form>
<my-input name="username"></my-input> <!-- Value pas soumise -->
<button>Submit</button>
</form>
Solution : ElementInternals API (support Chrome 77+, Safari 16.4+, Firefox 93+) :
class MyInput extends HTMLElement {
static formAssociated = true; // Activer form participation
constructor() {
super();
this._internals = this.attachInternals();
}
connectedCallback() {
this.innerHTML = `<input type="text">`;
this.querySelector('input').addEventListener('input', e => {
// Envoyer value au form
this._internals.setFormValue(e.target.value);
});
}
}
Résultat : Value soumise correctement dans FormData.
3. TypeScript types
Problème : TypeScript ne connaît pas vos custom elements.
Workaround : Déclarer types globalement.
// global.d.ts
declare global {
namespace JSX {
interface IntrinsicElements {
'my-component': {
name?: string;
onEvent?: (e: CustomEvent) => void;
};
}
}
}
export {};
Mieux : Génération automatique types via web-component-analyzer.
Futur Web Components : 2026-2030
Scoped Custom Element Registries
Problème actuel : Registry global (conflits noms possibles).
// App 1
customElements.define('my-button', Button1);
// App 2 (micro-frontend)
customElements.define('my-button', Button2); // ❌ Erreur : déjà défini
Solution future : Registries scopés.
const registry1 = new CustomElementRegistry();
registry1.define('my-button', Button1);
const registry2 = new CustomElementRegistry();
registry2.define('my-button', Button2); // ✓ OK, registry différent
// Utiliser registry spécifique
shadowRoot.adoptStyleSheets = [];
shadowRoot.customElements = registry1;
Status : Spec en discussion (WICG), implementation 2026-2027 estimée.
CSS Module Scripts
Import CSS dans JavaScript :
import sheet from './button.css' assert { type: 'css' };
shadowRoot.adoptedStyleSheets = [sheet];
Support : Chrome 93+, Safari 16.4+, Firefox en cours.
Articles connexes
Pour approfondir le sujet, consultez également ces articles :
- Angular v19 : Signals GA et performances +60% en octobre 2025
- React 19 stable : Server Components et Actions révolutionnent le développement web
- React 19 stable : Server Actions et optimisations révolutionnent le développement web
Conclusion : Web Components désormais production-ready
Après 10 ans de maturation, Web Components sont enfin mainstream en 2025 :
✓ Support universel (zéro polyfills requis) ✓ Frameworks matures (Lit 5.0, Stencil 4.0) ✓ Adoption enterprise (Google, Salesforce, Adobe) ✓ Interopérabilité (React, Vue, Angular, Vanilla)
Quand utiliser Web Components :
- Design systems multi-frameworks
- Composants réutilisables (embeds, widgets)
- Performance critique (bundles minuscules)
- Isolation CSS/JS requise
Quand rester sur frameworks :
- Apps complètes (React/Vue/Svelte meilleurs pour routing, state management)
- Prototypage rapide (ecosystèmes plus riches)
- Équipes déjà expertes framework spécifique
Ressources :
- Lit : https://lit.dev
- Stencil : https://stenciljs.com
- Open Web Components : https://open-wc.org
- Web Components community : https://webcomponents.org
Les Web Components ne remplaceront pas React/Vue/Angular, mais ils complètent l'écosystème en offrant une couche d'interopérabilité standard. L'avenir du web est pluraliste : chaque outil pour son use case optimal.




