CycleWire v1.1.0
Added
-
registered(): the names of the registered action modules, imported or not. -
defineAction(handler): returns the handler, typed with its props and element:
defineAction<{ sku: string }, HTMLFormElement>(…). -
A
trace(event)plugin hook, called by the development build only, that reports what
the core schedules, fetches, skips and runs. The production build carries none of it. -
Generic types:
Context<Props, Element>andAction<Props, Element>. Once a global
CycleWireActionsinterface lists the actions,ActionNameis their union,run()
andpreload()accept only those names, andPropsOf<Name>is a handler's props. -
cyclewire check, a command that reads your templates (HTML, Blade, ERB, Django,
Jinja, Twig, Liquid, Handlebars, JSX, Vue, Svelte, Astro) and reports actions that are
not registered or exports that do not exist, with suggestions, and values CycleWire
does not understand.--format githubannotates pull requests;--unusedlists the
actions no template uses. Configured with JSON only; no dependencies. -
cyclewire types, which writes theCycleWireActionsdeclarations from your
actions directory. -
cyclewire/vite, a Vite plugin:virtual:cyclewire/actionsregisters an actions
directory as chunks; an edited action is registered again instead of reloading the
page; the declarations stay up to date;index.htmlis checked; and the build writes a
manifestcyclewire checkcan read.viteis an optional peer dependency. -
cyclewire/stream(3.3 kB brotli withdomandmorph): the server changes the
page with<cw-stream op="append|prepend|before|after|inner|outer|morph|remove">
messages, over Server-Sent Events (connect()) or in any response (apply()).
Connections are shared per URL, reconnect with the last event id and a growing delay,
close when their element leaves the page, and step aside for the back/forward cache and
prerendering. Thestreams({ channels })plugin subscribescw-streamelements
to the channels you list, on your own origin only.<cw-stream>is not a custom
element, so markup that reaches the page any other way does nothing. The full
classic-script build includes it. -
cyclewire/devtools: an inspector that runs inside the page. Its panel lists the
registered actions (loaded or not, the elements that bind them, runs and errors), every
run with its event, concurrency mode, duration and status, the development build's
trace and thecw:*events, the triggers and scheduled preloads in the page, and the
bindings of any element you pick. Open it with thedevtools()plugin, withinstall(),
or from a bookmarklet on any page; Alt+Shift+W toggles it. It also works with the
production build and with 1.0.x, and costs nothing unless imported (devtools.min.js,
about 7 kB brotli). -
Server helpers (
docs/server-helpers.md): a copy-incw()for PHP (with a Blade
directive), Ruby (with a Rails helper), Python (with a Django template tag) and
JavaScript (for template literals and JSX). It writes thecw-*attributes,
escapes them for HTML and throws on a misspelt name, option or value. Each helper is
tested in its own language against one set of shared cases, and CI checks that the
docs quote the tested files. -
A documentation site at cyclechain.github.io/CycleWire/docs/,
built fromdocs/*.mdwith search. The Markdown files stay the only source; the build
checks every link and anchor. -
cyclewire/prefetch(0.5 kB brotli): data fetched on intent, next to the
action's code. An element names a URL withcw-prefetch; when the pointer, focus or a
finger reaches it, the plugin starts a GET of that URL on the page's own origin, and
the handler'sctx.fetchtakes the response that is already on its way. The code and
the data then arrive together instead of one after the other. -
An
intent(element)plugin hook, called whenever the user heads for an element that
binds actions. -
The package exports its CDN builds as
cyclewire/dist/*.min.js, so a server can
resolve the classic-script build to inline it in<head>.
Changed
- Attributes are written
cw-action, notdata-cw-action. Every name in the
vocabulary dropsdata-:cw-action,cw-on-click,cw-props,cw-trigger,
cw-preload,cw-pending,cw-state,cw-bind,cw-store,cw-key, and so on,
as short to write as htmx'shx-*or Alpine'sx-*. Theprefixoption now names the
whole start of the attribute ('x-'givesx-action, and''still means
data-action); the helpers,cyclewire checkand the Vite plugin follow it. - Touch screens look ahead. Where the primary input cannot hover, intent arrives
with the tap, too late for a module to load over a slow connection. By default the
modules ofcw-actionelements withoutcw-preloadare now also fetched
once the page is idle, as their elements near the viewport.start({ preload })
chooses:'auto'(the default),'visible'(on every screen) or'intent'(the 1.0
behaviour). The benchmark's mobile profile showed the first category filter and quick
view waiting for their code. - Intent fetches modules whose scheduled preload has not happened yet. Hovering,
focusing or touching an element withcw-preload="idle"or"visible"now
fetches its modules at once; onlynoneopts out. - Quick handlers run at once. A handler whose module is in memory no longer waits
for a paint unless its synchronous part held the main thread for more than 10 ms the
last time it ran, per action and per device. Its result then lands in the next frame
instead of the one after. Slow handlers still let the pressed state paint first. - Speculative preloads step aside. URL entries are preloaded at high priority on
intent and forcw-preload="load", and at low priority forvisible,idleand the
touch look-ahead, so they never hold up the page's own images. - Size budgets, brotli:
cyclewire.min.js5120 B (measured 4952 B),prefetch.min.js
768 B,stream.min.js4096 B,signals.min.js3520 B,morph.min.js2304 B, and the
classic builds 5376 B and 15488 B; the full one now includescyclewire/streamand
cyclewire/prefetch.
Fixed
- A computed no longer runs again when nothing it read has changed. A write
upstream marked every computed below it stale, and each ran again even when the
computed between them came out the same; now a stale computed first checks the
versions of what it read, as the documentation always said it did. - A computed whose function threw runs again the next time it is read, instead of
returning its last value; an effect disposed during its own run stays unsubscribed. morph()puts anxlink:hrefit adds to SVG in the XLink namespace, so a<use>it
gives one points at its symbol.- The production modules of
cyclewire/prefetchandcyclewire/streamno longer
importutil.jsfor nothing: a page that loads them without a bundler makes one
request fewer, and esbuild stops warning about the import.
Performance
- One
loadlistener and one idle callback serve everyidletrigger and preload,
instead of one per element. - Finding an element's actions no longer creates an
Attrnode for each of its
attributes, so hovering over the page leaves no nodes behind. cyclewire/signalsis two to five times faster. A computed or an effect that
runs again walks the list of what its last run read and changes no subscription
while it reads the same sources in the same order; a source it reads twice is
listed once; and a source with one subscriber keeps it without a set. On the
benchmark's micro suite it moved from last in every scenario to first on dynamic
graphs, ahead of Vue on most, and close to Preact and alien-signals on the rest.
signals.min.jsgrows to 3417 B brotli (budget 3520 B).morph()moves only what changed places, and skips what did not change. It pairs
the new children with the old ones first, then leaves the longest run already in
order where it is: swapping two rows of a thousand moves two rows instead of every row
between them (8 DOM mutations instead of 3,990). A subtree equal to its new markup is
left alone after one native comparison. On 1,000 keyed rows it is now faster than
morphdom at every operation the micro suite measures, for example 6.4 ms against
10.7 ms to update every tenth row (15 ms before).morph.min.jsgrows to 2196 B
brotli (budget 2304 B); the full classic build's budget becomes 15488 B.- The pointer-over handler skips elements without attributes, the look-ahead decides
once per scanned subtree instead of once per element, and plugins'preloadhooks run
once per module instead of on every hover.
Install
npm install cyclewire@1.1.0<script src="https://cdn.jsdelivr.net/npm/cyclewire@1.1.0/dist/cyclewire.global.min.js"
integrity="sha384-R0Xl+t7aeCYouBKAN/d/TsX6Jlli/oaK7XuyR5g0Xi5asjKXEElhCYeP5qp8r9y+"
crossorigin="anonymous" defer></script>Subresource Integrity
| File | SRI |
|---|---|
cyclewire.global.min.js |
sha384-R0Xl+t7aeCYouBKAN/d/TsX6Jlli/oaK7XuyR5g0Xi5asjKXEElhCYeP5qp8r9y+ |
cyclewire.full.global.min.js |
sha384-TnV3auHyqz9f9jt/cTSMsrD/FRsGybR3WQLmVQqJdLmF/tclfYvG7rddfiwf01U7 |
cyclewire.min.js |
sha384-imWmYLJvHMvaMSEN0QmaZbaLfBgyXk2iGfvz7qzvGNgkYaq6K5eGjn9tNsJCHJ+T |
bootstrap.min.js |
sha384-3HZJ54W1oasFaN5VweBPHfZJbdTrav7vXOvfuMDQjaaB21l9yXkvoy763mWfG43A |
css.min.js |
sha384-5VJ3jZAzNdHc6WgjvNvkKdWfsy90i2HUhhABA4IiowXzLCqLEt9tyu2/Y8bJNzAP |
devtools.min.js |
sha384-F3yS5H7JvVeeikksw7Tumm9TOZ0WXLXxfANSMa1/eQkJcHUQfRktMU0LAXEEteNy |
dom.min.js |
sha384-253OQozQxR1vH8ogTFuqxezAlHZNxOaJdhmBPMRvtK+h2z01rJxvTcc3ArrCCLbd |
morph.min.js |
sha384-oboV/yW4J71thMZm85veUF4AjibUbY4R2RQgUFRQio1i4AYDhl+Wwc6G6DKS/tCn |
prefetch.min.js |
sha384-9GZDO4nUCOgDwSandikdVo9arq8NvaKeY0LeTd2EGgMafWGudNscmbyXEDTEQGdd |
signals.min.js |
sha384-DJRGbywAmEDalE48HTZuRdOrNVt5blyHLLs096nDr2dS1T02RPF4u3gIU+3WC8e3 |
stream.min.js |
sha384-gZNeXTktnB8lgyzjub2dZB8nZrlfpAa+Jztspxedn3pjR//bHjuPKc0kP6YyA2qJ |
Full changelog: CHANGELOG.md