@nextwebwg/html-next-converter
| Install | |
|---|---|
npm install @nextwebwg/html-next-converter |
|
| Latest Version: | 1.0.0-alpha.69 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 21, 2026 |
| Links: | GitHub · npm |
@nextwebwg/html-next-converter
Converts a Declarative Components application or library graph into Vue 3.5 or Svelte 5 single-file
components, or React 19.3 TSX components. Once converted, HTML Next is gone. Each .vue file imports Vue,
its nested components, its component module (copied beside it), and only the generated helpers its
features need. A Vue component using
$html imports vue/html.ts; that helper requires parse5 as an application dependency for
deterministic SSR. It parses inert fragments and applies one pinned safe-default allowlist in every
browser and on the server. It deliberately does not call native setHTML(), even where available:
Firefox currently parses malformed table content differently from Chromium, WebKit, and parse5,
which would make hydration browser-dependent. Props, state, computed values, bindings, structural
directives and slots map to Vue's own facilities; a declared event is a native CustomEvent on
the lowered root, and Vue emits that same CustomEvent<T> under the event's name, plus
update:<prop> for v-model:<prop> when its detail reports a prop. Styles become
<style scoped> with the live runtime's selectors and specificity, in a native scope rooted at the
root's tag class and bounded by every converted root, which carries data-hn. In a component with
slots, its own markup carries data-hn-<tag>, which each selector tests in :where(), and
:slotted() selects the rest. A Vue component that is not converted carries no data-hn, so its
markup inside projected content is projected content to :slotted(). A component the styles
select by tag carries its tag as a class where it is used, so Vue passes it to the child's root.
Every component imports the shared vue/host.ts for what each one does the same way: consumer
attributes on the root, the hydration root check, checked handler writes, and the expression and
rendering helpers its template calls. A bundler keeps only the helpers a component imports, and an
application ships each one once.
Components declaring props import vue/props.ts. Invalid incoming values remain available at
their source while the root reports validity; a later typed binding is checked separately. A
Vue property binding should supply a number or boolean as that type, not as a string. Prop-free
components do not include this helper.
html-next-convert vue 'components/**' --mode library --out-dir generated
For a check without output, use html-next-check --target vue --mode library 'components/**'
from @nextwebwg/html-next-unplugin. See the CI and diagnostic API guide
for combining compiler checks with TypeScript and framework typecheckers. Programmatic converter
users can call checkConversion with the same options as convertComponents, omitting
outDirectory; it returns the planned conversion manifest, writes no files, and throws the same
compiler diagnostics as conversion. Independent check failures are collected in
HtmlDiagnosticAggregateError.diagnostics; conversion itself continues to stop at the first error.
Pass onWarning to receive warnings, such as HT022, that do not fail the check.
Quote the glob so the converter, not your shell, expands it. It discovers every .html
component below components/, including definitions with no incoming component link, and
preserves the source directory layout beneath generated/vue/. One HTML resource may define
several <template component> carriers; each gets its own named export. Names retain their
prefix: ui-button becomes UiButton. A directory path such as
components/ is shorthand for the same recursive scan. Explicit .html files remain valid.
When a <data src> URL is relative to its component file, pass --public-root-url /app/
if the conversion root is served at /app/. The converter uses that URL to preserve
definition-relative requests in the browser. Root-relative and absolute request URLs need no
mapping; a missing mapping for a component-relative URL is a conversion error. The equivalent
JavaScript option is publicRootURL.
A component publishes a state to <context> readers only when a converted component reads it.
Pass --public-state (publicState: true) when components outside the conversion, such as another
library's, read your components' state through <context>; every state is then published.
Application conversion emits an application.ts entry for the selected graph roots. Library
conversion emits an index.ts entry whose named exports are independently consumable. Both modes
emit html-next.conversion.json, which records the input entries, output entry, every artifact,
the target version, and package-shaped dependencies and peerDependencies. Copy the relevant
fields from package into the published package's package.json. parse5 appears in
dependencies only when an emitted $html helper needs it; Vue, React, or Svelte appears in
peerDependencies for its respective output. Dependencies imported by authored component
modules remain the publisher's responsibility.
Publish the .vue files together with the .js and .d.ts your Vue build produces from them
(for example vite build with @vitejs/plugin-vue, and vue-tsc --declaration --emitDeclarationOnly).
Invalid definitions retain HTML Next's source-located HT diagnostics. A valid construct that Vue
conversion cannot map produces source-located HTC001 with the underlying code. Colliding generated
paths produce HTC002 before any output is written, and an unsupported target version produces
HTC003 before the graph is loaded.
React output accepts the same quoted globs and mirrors the same source
directories under generated/react/, emitting .tsx and adjacent plain .css files. The CSS is
imported by each component; styled-components is not used. Components import the rendering and
binding helpers their markup and handlers call from the shared react/render.tsx, which an
application ships once; the output is formatted. React conversion covers
static markup, props and bindings, default and named/scoped slots, state/computed/handlers,
context, declared data, native form controls, safe HTML, and structural templates. The shared
conformance corpus and feature-specific React fixtures compare browser behavior, server output,
hydration, and exact pixels with the live runtime in Chromium, Firefox, and WebKit. Constructs
that cannot be represented fail conversion explicitly rather than silently changing behavior.
Svelte output uses Svelte 5 runes, snippets, attachments, and public lifecycle APIs, with .svelte
components under generated/svelte/, each with its styles in its <style>. It supports props (including generic
and state-selected types), state/computed/handlers, named and scoped slots, native events, context, data reads, component modules and refs, native form controls, safe HTML, structural
flow, and keyed lists. Native-control helpers preserve authored reset defaults and dirty edits;
Svelte's normal compiler owns rendering, SSR, and hydration. Components import the rendering and
binding helpers their markup and handlers call from the shared svelte/render.svelte.ts, which an
application ships once. Scripts are formatted in sv create's style. Markup breaks lines inside tags,
because Svelte renders whitespace between elements as a space, and after a space the template keeps.
html-next-convert svelte 'components/**' --mode library --out-dir generated
The current supported Svelte baseline is 5.57.1. Compile generated files with the standard Svelte
compiler or @sveltejs/vite-plugin-svelte. Source consumers can use svelte-check; publishers
emitting declaration files can use svelte2tsx. The generated public types cover props, native and
declared event callbacks and scoped slot fields. Both graph modes export components; application and library consumers use public mount/hydrate
or their framework's usual mounting and hydration flow.
Svelte support uses the unmodified framework; consumers do not install a runtime patch.
One known upstream gap remains: reordering a keyed row can blur its focused input. Svelte uses
ordinary DOM insertion to move the row, which can clear browser focus. In Svelte 5.57.1, our
Chromium, Firefox, and WebKit checks retain edited values and selection but lose focus;
Chromium also fires blur and focusout. The native HTML Next runtime retains focus in engines
with state-preserving moveBefore; its WebKit insertion fallback has the same focus loss.
This affects both mounting and hydration. See Svelte issue #3973 and the editable Playground
reproduction. Converter tests keep
this difference explicit while continuing to check rendering, edits, events, lifecycle and hydration.
Feature helpers are emitted once per converted graph and imported only by components that need
them. Style bindings record cssstyle and css-tree in server dependencies; the generated helper's
standard browser mapping uses the live element's CSSOM in browser builds.
React applications use their usual hydrateRoot call and error options. If the server root is
incompatible, React reports the recovery through onRecoverableError (or its default reporting);
the converter does not generate an HR005 hydration entry or replace React's recovery flow.
For source-only distribution with automatic Vue, React, or Svelte conversion in the consuming app, use the Vite adapter. It also generates consumer declarations, so publishers do not need separate framework copies.
To distribute preconverted copies of one authored library in all four forms, assemble the native HTML Next package from
the .html sources, then run the Vue, React, and Svelte library converters over the same quoted glob. Give
each conversion its own output directory so each html-next.conversion.json manifest survives:
html-next-convert vue 'components/**' --mode library --out-dir package/converted/vue-target
html-next-convert react 'components/**' --mode library --out-dir package/converted/react-target
html-next-convert svelte 'components/**' --mode library --out-dir package/converted/svelte-target
The package can expose the native entry and the converted Vue, React, and Svelte entries as
separate subpaths. If publishing source output, point ./vue, ./react, and ./svelte at the generated
vue/index.ts, react/index.ts, and svelte/index.ts, include their component files and CSS, merge the conversion
inventories' package.dependencies and package.peerDependencies into the package manifest,
and mark Vue, React, and Svelte as optional peers when each subpath is independently usable. Consumers
then compile .vue, .tsx, and .svelte with their normal bundler plugins. For precompiled distribution,
emit JavaScript and declarations from those entries and point exports at the built files. The
installed-library test packs and installs the source-output
shape, then renders the framework entries from that independent consumer.
For guaranteed unused-component and CSS pruning in a preconverted package, expose individual
component subpaths from the conversion inventory, such as ./react/XCard pointing to
react/components/XCard.tsx. Consumers can then import only the components they use. Some
bundlers retain CSS side-effect imports from unused named re-exports in a shared ./react barrel,
even when they remove the unused JavaScript. The on-demand Vite adapter handles pruning for its
generated named exports.
Use module="./behavior.js" on the component template and export a named connect:
export function connect(host, { signal }) {
host.on("click", () => { host.state.open = !host.state.open; });
const observer = new ResizeObserver(() => { /* measure the connected root */ });
observer.observe(host.root);
signal.addEventListener("abort", () => observer.disconnect(), { once: true });
}
connect runs on each DOM connection. Disconnection aborts its signal and releases its
listeners, effects and returned cleanup. A reconnect keeps the instance's state and starts a
new activation. SSR renders supplied props without importing this browser module.
Compatibility controller default exports still initialize once per instance; their
host.on("connect", setup) handles repeated connections. Register other
native events with host.on(type, listener), which detaches listeners while disconnected and
reattaches them on reconnect. A handler can dispatch its native $$event or selected event fields
to that listener. <method> and automatic named-export element methods are unsupported.
host.state contains mutable state, readonly computed state, and inherited context. Resources
are read through host.data.name, including their readonly response and status fields. Invalid
authored state assignments retain the prior value and warn; native control edits retain their
ordinary validity behavior. Props use :host([prop]) styling, and state uses :host(:state(state)).
Vue, React and Svelte conversion support an editable initial value with <state from="$product">.
Initialization waits for the expression's inputs to become available; the first accepted local
write takes ownership immediately. Refreshes never overwrite the draft. $await/$error/$else
work with these states and their inherited context, as with typed props and computed values.
React renders provisional seeds during SSR and concurrent rendering, then claims only committed
inputs or accepted local writes. An abandoned render cannot initialize a connected instance. See the public
state initialization proposal
for the declaration's behavior.
The public proposal defines these contracts.
A component that declares a <request> accepts a function prop for it, named in camelCase
(mention-query is mentionQuery), and its event form (Vue's @mention-query, React's
onMentionQuery, Svelte's onmention-query). The host adds one listener per request on the root
before the controller runs and reads the prop when a request arrives; a prop or listener set later
relays to the requests that went unanswered, as a late host.on does. The generated types give
each prop the request's detail and answer types, and a request whose camelCase name equals a
prop's fails conversion (HC011). Such a component imports requests.ts, the request steps, which
every converted host shares with the live runtime and compiled output through the document. See
requests.
A component with head declarations
imports its target's head helper (vue/head.ts, react/head.ts or svelte/head.svelte.ts):
the head manager every HTML Next bundle on a page shares through the document, with the
framework's adapter. Its title, metadata, links and JSON-LD join the document head while the
component's root is connected, a declared script's status is the component's own state, and
on:load and on:error call its handlers once per connection. For server rendering, the helper's
createHeadCollector() gathers what a render reached (through Vue's render context, Svelte's
render context, or React's collectHead), and writeCollectedHead(document, collector) writes it
into the application's own head, which hydration adopts. A component installed from a package is
an imported component, so its page-wide head entries are left out. A declared script whose src
is relative to its component, such as ./vendor/sdk.js, resolves at conversion against the
component's publicRootURL URL, which it requires, as a component-relative <data src> does.
Handler <dispatch target="name"> resolves the current component instance's $ref="name", never a DOM ID. Collection refs receive separate native events in rendered order; payload expressions are sampled once. React, Vue, and Svelte outputs preserve the authored event type and flags, and a targeted request does not become a framework emit from its sender. See dispatch targets.
Version History
Pre-releases (61)
| Version | Released | License |
|---|---|---|
| 1.0.0-alpha.69 | MIT | |
| 1.0.0-alpha.68 | MIT | |
| 1.0.0-alpha.67 | MIT | |
| 1.0.0-alpha.66 | MIT | |
| 1.0.0-alpha.65 | MIT | |
| 1.0.0-alpha.64 | MIT | |
| 1.0.0-alpha.63 | MIT | |
| 1.0.0-alpha.61 | MIT | |
| 1.0.0-alpha.60 | MIT | |
| 1.0.0-alpha.59 | MIT | |
| 1.0.0-alpha.58 | MIT | |
| 1.0.0-alpha.57 | MIT | |
| 1.0.0-alpha.56 | MIT | |
| 1.0.0-alpha.55 | MIT | |
| 1.0.0-alpha.54 | MIT | |
| 1.0.0-alpha.53 | MIT | |
| 1.0.0-alpha.52 | MIT | |
| 1.0.0-alpha.51 | MIT | |
| 1.0.0-alpha.50 | MIT | |
| 1.0.0-alpha.49 | MIT | |
| 1.0.0-alpha.48 | MIT | |
| 1.0.0-alpha.47 | MIT | |
| 1.0.0-alpha.46 | MIT | |
| 1.0.0-alpha.45 | MIT | |
| 1.0.0-alpha.44 | MIT | |
| 1.0.0-alpha.43 | MIT | |
| 1.0.0-alpha.42 | MIT | |
| 1.0.0-alpha.41 | MIT | |
| 1.0.0-alpha.40 | MIT | |
| 1.0.0-alpha.39 | MIT |
Showing the latest 30 of 61. See every release on npm