5.1 KiB
AGENTS.md
This file provides guidance to AI coding agents when working with code in this repository.
What this is
web-component-base (WCB) is a zero-dependency, ~1KB base class (WebComponent extends HTMLElement) for building reactive custom elements. Authors subclass it and define only a template and static props; any change to an observed attribute automatically re-renders the component.
Commands
pnpm test— run the vitest suite once (coverage is always on viavitest.config.mjs).pnpm test:watch— vitest in watch mode.- Run a single file / test:
pnpm vitest --run test/WebComponent.test.mjsor filter by name with-t "has a readonly template prop". pnpm lint— ESLint (flat config ineslint.config.mjs, includeseslint-plugin-jsdoc).pnpm format— Prettier over the repo.pnpm build—tscemits.d.tstypes, thencopy:sourceuses esbuild to minify/bundlesrc/*.js+src/utils/*intodist/as ESM.dist/is what gets published;src/is shipped as the readable source.pnpm size-limit— enforces the byte budgets declared inpackage.jsonsize-limit(e.g.WebComponent.js≤ 1.2 KB). Keep additions tiny; this gate is a core project value.pnpm docs— run the Astro docs site (docs/workspace) locally.
pnpm is mandatory (a preinstall only-allow pnpm guard enforces it). This is a pnpm workspace; the only sub-package is docs/.
Architecture
Everything is in src/ (entry point src/index.js re-exports WebComponent and html):
-
src/WebComponent.js— the base class and the whole reactivity engine. The flow:static props(a camelCase → default-value map) is the single source of truth.observedAttributesis derived from it by kebab-casing each key.- The constructor deep-clones
static props, records each prop'stypeofin a private#typeMap, reflects defaults to attributes, and wraps the props object in a Proxy (#handler). Writingthis.props.someProp = xserializes the value and callssetAttribute(kebab-case), which triggersattributeChangedCallback→render(). That attribute-write-driven cycle is the reactivity model — there is no separate scheduler. - The Proxy enforces type stability against
#typeMapand throwsTypeErroron a type change. - Lifecycle hooks authors may override:
onInit()(connected),afterViewInit()(after first render),onDestroy()(disconnected),onChanges({property, previousValue, currentValue})(attribute changed). NoteonChangescurrently receives the kebab-caseproperty. templateis a read-only getter (assigning to it throws).render()branches on its type: a string template is assigned toinnerHTMLdirectly; an object template (a vnode tree fromhtml) is diffed against the previous tree withJSON.stringifyand, on change, rebuilt viacreateElement+replaceChildren.render()can be called manually or overridden entirely (e.g. to delegate tolit-html).static stylesare applied as a constructableCSSStyleSheetviaadoptedStyleSheets, which only works with a shadow root — setstatic shadowRootInitto opt into shadow DOM.
-
src/html.js— thehtmltagged-template function. It is a bundled/minified copy ofhtmbound to a hyperscripth(type, props, children)that produces plain{type, props, children}vnodes. License lives invendors/htm/. -
src/utils/— the serialization layer that bridges typed JS values and string attributes:serialize/deserialize(JSON round-trip for number/boolean/object, passthrough for strings),get-camel-case/get-kebab-case(attribute ⇄ prop name conversion), andcreate-element(turns a vnode tree into real DOM nodes, resolving props to DOM properties/attributes and applyingstyleobjects).src/utils/index.jsre-exports all of them.
Testing notes
- Environment is happy-dom (set in
vitest.config.mjs), so real custom-element registration works. Any component under test must be registered withcustomElements.define(...)before instantiation, or the browser throws. - Tests live both in
test/and colocated next to source as*.test.mjs(e.g.src/utils/serialize.test.mjs). Coverage is restricted tosrc. - happy-dom does not perfectly match browser semantics (notably custom-element upgrade ordering); be skeptical of behaviors that depend on real-browser timing.
Known correctness work in flight
The README's top-of-file note flags a set of state-corruption fixes as the current priority — constructor attribute writes, empty-string/removed-attribute handling silently skipping render()/onChanges, the props Proxy's first-write type lock, and unsafe structuredClone of static props defaults. Keep these in mind when touching WebComponent.js; several current behaviors described above are the target of upcoming changes.
Docs
The public documentation site is an Astro + Starlight app in docs/ (published to webcomponent.io). Guide content is Markdown/MDX under docs/src/content/docs/guides/; it doubles as the behavioral spec for the library.