A template for vanilla web components.
- Use the template button in github. Or clone this then
rm -rf .git && git init. Thennpm i && npm init.
- Use the template system to re-name this module and start the docs:
node ./bin/cli.jsThe CLI prompts for several variables
gh-namespace-- first path segment on githubpackage-name-- package name, including any namespace. eg,@alice/packagecomponent-name-- the name of the web component, as used in HTML, egcool-examplerepo-name-- repository name, the last segment in github URL, eg,github.com/user/repo-name-here
-
Edit the source code in
src/index.ts. -
Edit things
- edit the build-example
command in
package.jsonso that it has the right path for github pages
- edit the build-example
command in
- compile the source to both ESM and CJS format, and put compiled files in
dist. - ignore
distand*.jsin git, but don't ignore them in npm. That way we don't commit any compiled code to git, but it is available to consumers. - use npm's
prepublishOnlyhook to compile the code before publishing to npm. - use exports field in
package.jsonto make sure the right format is used by consumers. preversionnpm hook -- lintversionnpm hook -- generate a TOC for the README, and create and add a changelogpostversionnpm hook --git push --follow-tags && npm publish- eslint --
npm run lint - tests run in a real browser via
tapout -- see
npm test. Assertions come from tapzero. The test bundle needs--bundle, or bare module specifiers will not resolve in the browser. - CI via github actions
- stylelint -- see preversion npm hook
The component extends
@substrate-system/web-component,
which supplies namespaced events, qs/qsa, and attribute reflection.
The conventions are recorded as
architecture decision records; the vocabulary is
in the glossary.
Declare attributes with the static arrays. The base class generates the
getters and setters and derives observedAttributes from them, so there
is no static observedAttributes to maintain:
static reflectedStringAttributes = ['example']
static reflectedBooleanAttributes = ['disabled']
declare example:string|null
declare disabled:booleanReact to a change in a handleChange_<attribute> method.
attributeChangedCallback routes to it by name. Dispatch is by
convention, so adding an attribute means declaring it and adding the
method -- a missing handler is skipped silently.
See ADR-004.
Emit namespaced events with emit, listen with on, and build the
event name with the static event() helper:
el.emit('hello', { detail: 'some data' }) // 'cool-example:hello'
el.on('hello', ev => { /* ... */ })
el.addEventListener(Example.event('hello'), ev => { /* ... */ })See ADR-005.
render() is abstract, so every component implements it. The base
connectedCallback() calls it; if you override connectedCallback,
call super.connectedCallback() or nothing renders.
Attribute changes do not re-render. Update the DOM in place from the
handleChange_* method instead.
A custom element is inert markup until its JS defines it, which shows up as a flash of undefined custom element. This template handles it in two layers, described in ADR-006.
src/index.css hides the element until it is defined. This ships with
the component:
cool-example:not(:defined) {
display: none;
}example/ demonstrates the page-level pattern: a reduce-fouce class
on <html>, removed once customElements.whenDefined resolves for
every component on the page. Two safeguards are required, not optional
-- a Promise.race timeout, and a <noscript> override. Without them,
a component that never defines leaves the page permanently blank.