JavaScript API and JS components
JavaScript API
Section titled “JavaScript API”@johnhenry/html-modules/browser defines the elements and exposes the shared instance as HTMLModules (exported,
and on globalThis). <html-import> is a thin layer over it, so both share one cache and one set of rules. The
side-effect-free root entry point, @johnhenry/html-modules, exports everything else.
import { HTMLModules } from '@johnhenry/html-modules/browser';
const ui = await HTMLModules.load('./ui.html');// { card, button, theme, config, default, components }: shaped like a compiled ES module
await HTMLModules.import('./ui.html', { as: 'ui' }); // = <html-import as="ui">await HTMLModules.import('./ui.html', { as: 'ui', delimiter: '-' }); // = <html-import as="ui" delimiter="-">await HTMLModules.import('./ui.html', { as: 'ui', conflict: 'reuse', errors: 'throw' });const lazy = HTMLModules.import('./ui.html', { as: 'ui', load: 'lazy' }); // a handle: { ready, load(), cancel(), state }await HTMLModules.import('./ui.html', { bindings: [{ export: 'card', element: 'x-card' }] });HTMLModules.bind(ui, { as: 'admin' }); // bind a loaded moduleHTMLModules.resolve('./ui.html'); // → absolute URLHTMLModules.cache; // Map<`<kind>:<URL>`, Promise<namespace>>HTMLModules.unload('./ui.html'); // evict it from the cache: the next load fetches againA loaded namespace is frozen and has a null prototype. Named exports are camelCase (fancy-button →
fancyButton), default is present when the module has one, and components maps export names to component
definitions.
The root entry point exports exactly 56 names (the full list is on the API reference). The API at a glance; every entry links to its reference:
| Area | Exports | Reference |
|---|---|---|
| Instances | createHTMLModules(options), the instance’s load, unload, import, bind, resolve, cache, options, base; defineHTMLModuleElements() |
JavaScript API |
| Elements | HTMLImport, HTMLBinding, HTMLExport, HTMLImportSettings, HTMLModuleSettings (from /browser) |
Elements |
| Definitions | defineHTMLComponent, HTMLComponent, defineHTMLStylesheet, HTMLStylesheet, isHTMLComponent, isHTMLStylesheet, isStylesheet, isElementLike |
Runtime |
| Binding and registration | bindModule, applyBinding, registerComponents, defineElement, toComponent, lookupExport, componentsOf, manifest, adoptStylesheet, unadoptStylesheet, configureRuntime, renderDeclarative |
Runtime |
| Module records | readHTMLModule, scanHTMLModule, recordFromRaw, moduleImportOptions |
Records |
| Settings | IMPORT_DEFAULTS, EXPORT_DEFAULTS, readImportSettings, readModuleSettings, readImportOptions, resolveImportOptions |
Records |
| Names | DELIMITER, bindingName, parseBindingName, isValidDelimiter, isValidElementName, elementNameProblem, isKebabName, camelCase, kebabCase |
Names |
| Lazy loading | lazyTargets, watchLazy, componentRoot |
JavaScript API |
| Loader | createLoader, linkHTMLModule, createNamespace |
JavaScript API |
| Hot replacement | hotReplaceComponent, hotReplaceStylesheet, hotReplaceModule, and HTMLModules.hotReload(src) |
Dev server, hot reload and Vite |
| Scoped registries | supportsScopedRegistries |
HTML syntax |
| Compiler | compileHTMLModule, compileRecord, rewriteSpecifier, rebaseSpecifier, the html-module CLI |
Compiler |
Definitions (src/runtime.js, also @johnhenry/html-modules/runtime) are the shared representation. The loader
and the compiler both produce them with defineHTMLComponent():
import { defineHTMLComponent } from '@johnhenry/html-modules/runtime';
const card = defineHTMLComponent({ name: 'card', // identity template: '<article><slot></slot></article>', shadow: 'open', // or 'closed' delegatesFocus: false, styles: [':host { display: block }'], imports: [], // [{ module, from, as?, bindings? }] bound before registration});card.define('my-card'); // → the registered classcard.element; // the base class, to extendJavaScript-authored components
Section titled “JavaScript-authored components”<html-import> takes JS modules too, loaded with native import(). A JS module says which exports are components;
nothing else is ever registered, so export const VERSION = "2.1" never becomes <ui--version>.
// ui.js: either a components manifest …export const components = { 'custom-card': CustomCard, 'fancy-button': FancyButton };
// … or exports made with defineHTMLComponent()export const customCard = defineHTMLComponent(CustomCard);<html-import src="./ui.js" as="ui"></html-import> <!-- <ui--custom-card>, <ui--fancy-button> -->A plain class needs no manifest when the page names it: <html-binding export="Counter" element="x-counter">. To
give an HTML template JavaScript behaviour, extend its definition’s element and export the result:
const { likeView } = await HTMLModules.load(new URL('./like-view.html', import.meta.url).href);export class LikeButton extends likeView.element { connectedCallback() { /* … */ } }export const components = { 'like-button': defineHTMLComponent({ element: LikeButton, imports: likeView.imports }) };HTML modules can re-export JS components too: <html-export src="./widgets.js" name="counter" import="Counter">.
TypeScript
Section titled “TypeScript”Declarations ship for every entry point (., ./browser, ./runtime, ./compiler, ./dev, ./vite), generated from the
source’s JSDoc, with a types condition in package.json’s exports. They include the record types
(ModuleRecord, ImportRecord, ExportRecord), module namespaces, the HTMLModules instance and the element classes:
import { scanHTMLModule, type ModuleRecord } from '@johnhenry/html-modules';import { HTMLModules, type HTMLImportElement } from '@johnhenry/html-modules/browser';A strict typed consumer of every entry point is compiled by npm run types:check (in npm test and CI); npm run types
regenerates types/. See TypeScript in the API reference.