Skip to content

Commit 470f15f

Browse files
authored
Incorporate Google Custom Element Best Practices into template (#6)
* Implement Web Components best practices for attributes, properties, and shadow DOM * Add comprehensive best practices documentation
1 parent 5884b19 commit 470f15f

File tree

4 files changed

+391
-28
lines changed

4 files changed

+391
-28
lines changed

COMPONENT-NAME.js

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,82 @@ export class ComponentNameElement extends HTMLElement {
1919
constructor() {
2020
super();
2121
this.attachShadow({ mode: 'open' });
22+
this._internals = {
23+
isRendered: false,
24+
};
2225
}
2326

2427
connectedCallback() {
28+
// Upgrade properties that may have been set before the element was defined
29+
this._upgradeProperty('exampleAttribute');
30+
31+
// Check for global attributes before setting defaults
32+
// Don't override author-set attributes
33+
if (!this.hasAttribute('role')) {
34+
// this.setAttribute('role', 'group'); // Example - set appropriate role
35+
}
36+
if (!this.hasAttribute('tabindex')) {
37+
// this.setAttribute('tabindex', 0); // Example - set if focusable
38+
}
39+
2540
this.render();
2641
}
2742

2843
attributeChangedCallback(name, oldValue, newValue) {
29-
if (oldValue !== newValue) {
30-
this.render();
44+
// Only handle side effects, avoid full re-render
45+
// The property getter will read from the attribute
46+
if (oldValue === newValue) {
47+
return;
48+
}
49+
50+
switch (name) {
51+
case 'example-attribute':
52+
// Handle side effects (e.g., update ARIA attributes, dispatch events)
53+
// Don't re-render the entire component unless necessary
54+
// Example: this.setAttribute('aria-label', newValue);
55+
56+
// Dispatch events for internal state changes, not for host-set properties
57+
// Only dispatch if the change came from internal component activity
58+
if (this._internals.isRendered) {
59+
// Example pattern (commented out by default):
60+
// this.dispatchEvent(new CustomEvent('COMPONENT-NAME:change', {
61+
// detail: { exampleAttribute: newValue },
62+
// bubbles: true,
63+
// composed: true
64+
// }));
65+
}
66+
break;
67+
}
68+
}
69+
70+
/**
71+
* Upgrade a property to handle cases where it was set before the element upgraded.
72+
* This is especially important for framework compatibility.
73+
* @param {string} prop - Property name to upgrade
74+
* @private
75+
*/
76+
_upgradeProperty(prop) {
77+
if (Object.prototype.hasOwnProperty.call(this, prop)) {
78+
const value = this[prop];
79+
delete this[prop];
80+
this[prop] = value;
81+
}
82+
}
83+
84+
/**
85+
* Example attribute as a property.
86+
* Reflects between property and attribute to keep them in sync.
87+
*/
88+
get exampleAttribute() {
89+
return this.getAttribute('example-attribute');
90+
}
91+
92+
set exampleAttribute(value) {
93+
// Reflect property to attribute
94+
if (value === null || value === undefined) {
95+
this.removeAttribute('example-attribute');
96+
} else {
97+
this.setAttribute('example-attribute', value);
3198
}
3299
}
33100

@@ -37,8 +104,15 @@ export class ComponentNameElement extends HTMLElement {
37104
:host {
38105
display: block;
39106
}
107+
108+
/* Support the hidden attribute properly */
109+
:host([hidden]) {
110+
display: none;
111+
}
40112
</style>
41113
<slot></slot>
42114
`;
115+
116+
this._internals.isRendered = true;
43117
}
44118
}

README.md

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Web Component Starter Template
22

3-
A comprehensive, production-ready starter template for creating Web Components. This template is based on the architecture and best practices from my web components work.
3+
A comprehensive, production-ready starter template for creating Web Components. This template is based on the architecture and best practices from my web components work, incorporating [Google's Custom Element Best Practices](https://web.dev/articles/custom-elements-best-practices).
44

55
## ✨ Features
66

77
- **Modern Tooling**: Vitest, ESLint, Prettier, Happy DOM
8-
- **Best Practices**: Shadow DOM, Custom Elements v1, proper encapsulation
8+
- **Best Practices**: Shadow DOM, Custom Elements v1, proper encapsulation, following [Google's recommendations](https://web.dev/articles/custom-elements-best-practices)
99
- **Multiple Import Options**: Auto-define, manual definition, or both
1010
- **Testing**: Comprehensive test setup with coverage reporting
1111
- **CI/CD**: GitHub Actions workflows included
@@ -48,34 +48,35 @@ If you prefer manual setup, see [SETUP.md](SETUP.md) for detailed instructions.
4848

4949
```
5050
web-component-starter/
51-
├── COMPONENT-NAME.js # Component implementation
52-
├── index.js # Main entry (class + auto-define)
53-
├── define.js # Auto-define only
54-
├── custom-elements.json # Custom Elements Manifest
55-
├── package.json # Package config with scripts
56-
├── LICENSE # MIT License
57-
├── README.md # This file (replaced after setup)
58-
├── README.tpl # Template for your component's README
59-
├── .gitignore # Git ignore
60-
├── .npmignore # npm ignore
61-
├── .prettierrc # Prettier config
62-
├── .editorconfig # Editor config
63-
├── eslint.config.js # ESLint config
64-
├── vitest.config.js # Vitest config
51+
├── COMPONENT-NAME.js # Component implementation
52+
├── index.js # Main entry (class + auto-define)
53+
├── define.js # Auto-define only
54+
├── custom-elements.json # Custom Elements Manifest
55+
├── package.json # Package config with scripts
56+
├── LICENSE # MIT License
57+
├── README.md # This file (replaced after setup)
58+
├── README.tpl # Template for your component's README
59+
├── WEB-COMPONENTS-BEST-PRACTICES.md # Best practices documentation
60+
├── .gitignore # Git ignore
61+
├── .npmignore # npm ignore
62+
├── .prettierrc # Prettier config
63+
├── .editorconfig # Editor config
64+
├── eslint.config.js # ESLint config
65+
├── vitest.config.js # Vitest config
6566
├── .github/
6667
│ ├── workflows/
67-
│ │ ├── ci.yml # Continuous integration
68-
│ │ └── publish.yml # Auto-publish to npm
69-
│ └── ISSUE_TEMPLATE/ # Bug & feature templates
68+
│ │ ├── ci.yml # Continuous integration
69+
│ │ └── publish.yml # Auto-publish to npm
70+
│ └── ISSUE_TEMPLATE/ # Bug & feature templates
7071
├── scripts/
71-
│ └── setup.js # Interactive setup wizard (removed after setup)
72+
│ └── setup.js # Interactive setup wizard (removed after setup)
7273
├── test/
73-
│ ├── setup.js # Test configuration
74-
│ └── COMPONENT-NAME.test.js # Test suite
74+
│ ├── setup.js # Test configuration
75+
│ └── COMPONENT-NAME.test.js # Test suite
7576
├── demo/
76-
│ └── index.html # Live demo page
77-
├── SETUP.md # Manual setup guide (removed after setup)
78-
└── CONTRIBUTING.md # Contribution guidelines
77+
│ └── index.html # Live demo page
78+
├── SETUP.md # Manual setup guide (removed after setup)
79+
└── CONTRIBUTING.md # Contribution guidelines
7980
```
8081

8182
## 🛠️ Development
@@ -191,6 +192,7 @@ For legacy browsers, use polyfills.
191192

192193
## 📚 Documentation
193194

195+
- [WEB-COMPONENTS-BEST-PRACTICES.md](WEB-COMPONENTS-BEST-PRACTICES.md) - Explanation of best practices used in this template
194196
- [SETUP.md](SETUP.md) - Detailed setup instructions (removed after setup)
195197
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guidelines
196198
- [LICENSE](LICENSE) - MIT License
@@ -207,6 +209,7 @@ Perfect for:
207209
## 🙏 Credits
208210

209211
Based on best practices from:
212+
- [Google's Custom Element Best Practices](https://web.dev/articles/custom-elements-best-practices)
210213
- [form-obfuscator](https://github.com/aarongustafson/form-obfuscator) by Aaron Gustafson
211214
- [Open Web Components](https://open-wc.org/)
212215

0 commit comments

Comments
 (0)