Releases: CycleChain/CycleWire
Release list
CycleWire v1.2.0
Added
cyclewire/early(0.4 kB brotli), a script to inline at the top of<head>. Until
CycleWire starts, it keeps the clicks, typing and changes on bound elements, and marks
a tapped controlcw-pendingat once;start()then runs them in order. Links and
forms are left to the browser, and where CycleWire would have taken the place of their
default (a submit button, a link withcw-prevent), the action does not run as well.
earlyScript(prefix)returns the script for a server to inline,dist/early.min.js
is the same for the default prefix, and the Vite plugin adds it withearly: true.cyclewire/request(3.6 kB brotli): links, forms and buttons that fetch HTML and
put it into the page, declared in markup.cw-get,cw-post,cw-put,cw-patchand
cw-deletename the method and URL (empty, the element's own),cw-targetwhere the
answer goes (a selector, orclosest …),cw-swaphow (inner,outer,before,
after,prepend,append,morph,removeornone, andtransition), and
cw-selectwhich part of it. It is an action, registered like any other, so it loads
when first used and runs with the element's debounce, concurrency, trigger and pending
state. It sends a form's fields and the CSRF token of<meta name="csrf-token">, asks
the page's own origin only, shows 422 answers, applies the<cw-stream>messages in an
answer, and never runs its scripts. The full classic-script build registers it with
"request": true.- An empty
cw-prefetchfetches the element's own URL: itscw-get, or a link'shref. apply()fromcyclewire/streamalso takes nodes already parsed.cyclewire checkreportscw-swapvalues CycleWire does not know (invalid-swap).
Changed
- A page pays a listener only for the events it uses.
click,submit,inputand
changeare delegated as before;keydown,keyup,focusin,focusout,
pointerdown,toggleandcommandonce a binding needs them: in the page at start,
added later, or on an element the pointer or focus reaches. The benchmark page goes
from 14 listeners to 6. Code that sets such a binding on an element already in the page
and dispatches the event itself straight away should callscan(element)in between.
Install
npm install cyclewire@1.2.0<script src="https://cdn.jsdelivr.net/npm/cyclewire@1.2.0/dist/cyclewire.global.min.js"
integrity="sha384-LB5rG5EnqCzbgXKqv4aZR90JCdI+OQ/Qp/EwDM6QmxGWrxJ7gBOyXfltIoEbw9Ay"
crossorigin="anonymous" defer></script>Subresource Integrity
| File | SRI |
|---|---|
cyclewire.global.min.js |
sha384-LB5rG5EnqCzbgXKqv4aZR90JCdI+OQ/Qp/EwDM6QmxGWrxJ7gBOyXfltIoEbw9Ay |
cyclewire.full.global.min.js |
sha384-tVykjF4uUQzUHqNbCTi+rimJv2zQdxLeQRN53cpJz+x4/y/BUD2w39R4eeiGIa2L |
cyclewire.min.js |
sha384-X+xpeGCZnpXpEsy+iDS1eB//CO7vsS7d9cOueUSEPhb/SIMLHgpgbM5IEYJsySEU |
bootstrap.min.js |
sha384-ZweZRQaKSz68KB2+ID2rgSxdG0CtKO0sGSY+n1E/fStm6ahQK2Qe4WY6Toi+ljoi |
css.min.js |
sha384-PCGShIcejnAaiIWEJxJogTbBgmAn1oeiRgIlFk/H1dcoub6GjBn1zKruj8Oa5+cF |
devtools.min.js |
sha384-wc3wDRHE7TzGHI8MWal2IcXpuBYV8iNQDsQuOZn4RfUFtE8KfL7duAqOLKV+KHS3 |
dom.min.js |
sha384-Z0dmGaC64+GM24a1GfmMYuXv2TJsgmSmQiJqlhoswPzp5P6Khwun6KcDhS4oxSen |
early.min.js |
sha384-bBIpoHFCRqKlBZGNm+Vir5V0mI73L0GEijiYoT/uJKkb5XalHKCdSMn95SoIeHGn |
morph.min.js |
sha384-p2vljSe2SFY1gfzrnDGkpxefYonJvgT1YBVGk4OKAbP2gn3h/bX3AIHZtQUx8g4U |
prefetch.min.js |
sha384-DhUTU+rcQEVBF1hS0lT3TEHJWNnz/Fnox0hho/kNr/Z1IRjX88FdVaeI4zkwU3rD |
request.min.js |
sha384-5xhUXfO2VvzqbxG+AnIiEVO8D6hJFcxM9j6ulUC8TmMKb/ltJ4VAHvgp+6azNRn8 |
signals.min.js |
sha384-m6pX1z/WtpDBK+C0Zeef63vfcj2sRyQozW3lOryWewA7CNlSQKuuFQE9P4pcxP4f |
stream.min.js |
sha384-ptWx306rCxxnwwl/sSg4CT3DM8kMQ0NJpPBcZJ/ovXGAoX00c6Z8aBem6d9Ukxzq |
Full changelog: CHANGELOG.md
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](htt... |
CycleWire v1.0.1
Security
htmlrefusedjavascript:URLs only in the simplest case. A value split across
interpolations (href="${a}${b}"), passed as an array or as nestedhtml, preceded by
fixed text such as a space, or set through SVG animation attributes (to,from,by,
values) could still produce a script URL. The whole attribute value is now checked as
the browser reads it, and templates that end inside a tag, a comment or a raw-text
element are refused.data-cw-ignoreonly stopped event handling from reaching outer bindings. Elements
inside it that carried their own bindings still ran, andload/visibletriggers,
preloads and signals bindings inside it still activated. Nothing inside it activates
now, across shadow roots too.- Signals
attr.*bindings could write event handlers,srcdocand script URLs.
They are refused now, with a warning in the development build.
Changed
- Size budgets:
dom.min.js2304 B andsignals.min.js3200 B (brotli), for the checks
above.
Install
npm install cyclewire@1.0.1<script src="https://cdn.jsdelivr.net/npm/cyclewire@1.0.1/dist/cyclewire.global.min.js"
integrity="sha384-gc0AJD/HiRsZkqVJzFpkiXC9MICQD/4TJxO0Ym9KXeCsg542FsOPkcm6nsvtg0SQ"
crossorigin="anonymous" defer></script>Subresource Integrity
| File | SRI |
|---|---|
cyclewire.global.min.js |
sha384-gc0AJD/HiRsZkqVJzFpkiXC9MICQD/4TJxO0Ym9KXeCsg542FsOPkcm6nsvtg0SQ |
cyclewire.full.global.min.js |
sha384-k7XXWn8CMXKeBQZj6Bdc+rPV9OXKnchHO9qn47T20/Tdj3RCAA/prZrkTAOAAgZe |
cyclewire.min.js |
sha384-h5cx8AujO5AU+RULhp/Qm8yWXUPGqIzuK4IcR87QL3AnJLAmzclEVHMEwsu6pPJH |
bootstrap.min.js |
sha384-R+KFPGlTWajb/qSGqKkk6tWj/EJMbeFxYd4V0IVP4Y5muFNoAs4gcbUNKY3PMIkM |
css.min.js |
sha384-afUiywAjluRvzALKwWR4wZ8t0cTGdZk5Nh3Uk0wpSCGpF077wOXq6MGVI56NZ2hJ |
dom.min.js |
sha384-jvuCmVxMVZr05rTvPMMkQ55xoN4xgM1pzFsCH6nwcBwVyUJhzzZUbL2DSqEfSNFP |
morph.min.js |
sha384-YfcB2wsEm9aMmyeKNU+BGCB7OAoiqmGPzY/habddugYDF/CKj1gXBwfyY+BleIs7 |
signals.min.js |
sha384-O2d0eo4HLwzgeothtcouNS8WRFneFuTbUtglREEnQlXrnb6r+CBMFbBpLwFPf/Zt |
Full changelog: CHANGELOG.md
CycleWire v1.0.0
First public release: a zero-dependency engine that makes server-rendered HTML
interactive without hydration.
Added
- Core (
cyclewire):- one delegated listener per event type;
- a strict action registry: loader functions, URLs or import-map specifiers;
data-cw-actionanddata-cw-on-<event>bindings, andmodule#exportnames;- triggers:
load,idle,visible,media:; - intent, visible, idle and load preloading with
modulepreload; - concurrency modes:
drop,restart,latest,parallel; data-cw-once,data-cw-debounce,data-cw-propsanddata-cw-ignore;- a cancelable
cw:runevent, pluscw:doneandcw:error; run(),observe()for shadow roots,fromGlob()for Vite, and a configurable
attribute prefix;- plugins with
setup,context,scan,preloadandloadhooks, and object
registry entries ({ module, … }) that carry options for them.
cyclewire/css: stylesheets that arrive with the actions that need them. With the
styles()plugin, a registry entry lists the CSS its UI needs ({ module, css }); the
CSS is preloaded with the module on intent and applied before the handler runs, inside
shadow roots too.css()loads stylesheets from code.cyclewire/autoand classic-script builds that read
<script type="application/json" data-cyclewire>and exposewindow.CycleWire.cyclewire/dom: context-awarehtmltagged templates, inertfragment()
parsing,swap()andtransition()(View Transitions), Trusted Types support.cyclewire/morph: DOM morphing that keeps elements by id ordata-cw-key,
usesmoveBefore()where available, and preserves focus and user input.cyclewire/signals: signals, computed values, effects, deep reactive stores,
anddata-cw-state/data-cw-store/data-cw-bindactivated lazily from
server-serialized state.cyclewire/bootstrap: the Bootstrap 5 data API (collapse, dropdown, modal,
offcanvas, tab, alert) without Bootstrap's JavaScript.- TypeScript declarations, a development build with warnings, and size budgets in CI.
- Guides and runnable examples for React, Vue and Svelte islands, and for Flatpickr,
SweetAlert2, DataTables and jQuery, tested in Chromium, Firefox and WebKit and live
on GitHub Pages.
Install
npm install cyclewire@1.0.0<script src="https://cdn.jsdelivr.net/npm/cyclewire@1.0.0/dist/cyclewire.global.min.js"
integrity="sha384-/P0Nv08w6uODr1GidxFoywMsI2lyyontoN2RsmmOqEZrsRr+9vj3J3a9o02QDAVZ"
crossorigin="anonymous" defer></script>Subresource Integrity
| File | SRI |
|---|---|
cyclewire.global.min.js |
sha384-/P0Nv08w6uODr1GidxFoywMsI2lyyontoN2RsmmOqEZrsRr+9vj3J3a9o02QDAVZ |
cyclewire.full.global.min.js |
sha384-8kFK9hZruNLQpxVFSwGZBRPcBK+pe4N0JSdmBM+QVc8Wa5ommci0GOce7qGZ2I9E |
cyclewire.min.js |
sha384-X31qtsuoaxJVYY5k2GD4lk19XFk0O5Nu8fXQm56TeheEonStoYnsZT5b//Z5MG/H |
css.min.js |
sha384-vTnod85S0efe61Uvs3XsB0gAfXKMT/HYfBFo6s6GHyxj1U4EPQPL0nwhrPU9Bneg |
dom.min.js |
sha384-ICTUMQQ7LaqK6iPfy2Es/s8phOlVarr/LsaRq0h7KxCXSYXk5ABFk+T6Do54Z4if |
morph.min.js |
sha384-PyzvtzFAPzYgq7PseIkCw5Tq2KjolEqzqfJDGy2V6dOZF4AopytdHriT1rmvYrTP |
signals.min.js |
sha384-dOtCDZzlvzjz8b3/Ys7eet0krjIJKGWv6bw1JKyBU4rMEWly3ZCXQgvQ4mZ8J3Zt |
bootstrap.min.js |
sha384-qPsSltLcezWka0mc/QWCZIsmV9DHOp/gVZiB3W5tVH3ZSqGPZPt4lQ19QCurlx1P |
Full changelog: CHANGELOG.md