|
| 1 | +# 🚲 The Fixi Project |
| 2 | + |
| 3 | +[The Fixi Project](https://fixiproject.org) is a collection of five small web libraries based on other libraries we work |
| 4 | +on. |
| 5 | + |
| 6 | +Each library in The Fixi Project is constrained to have an *unminified, uncompressed* source size smaller |
| 7 | +than the excellent [Preact](https://preactjs.org) library's [min.gz'd size](https://bundlephobia.com/package/preact) |
| 8 | +(~ 4.7kb). |
| 9 | + |
| 10 | +## The Libraries |
| 11 | + |
| 12 | +The five libraries each provide independent bits of functionality, but are designed to compose well. You can mix and |
| 13 | +match them as you see fit. |
| 14 | + |
| 15 | +### 🚲 `fixi.js` - supercharged HTML |
| 16 | + |
| 17 | +`fixi.js` is the original and main library in this collection. It is based on [htmx](https://htmx.org) and, like htmx, |
| 18 | +makes it possible to issue HTTP requests from elements in response to events. |
| 19 | + |
| 20 | +Here is a fixi-powered button: |
| 21 | + |
| 22 | +```html |
| 23 | +<button fx-action="/like" fx-method="post"> |
| 24 | + Like |
| 25 | +</button> |
| 26 | +``` |
| 27 | + |
| 28 | +This button will issue an HTTP POSt to `/like` when it is clicked and will replace itself with whatever HTML content the |
| 29 | +server responds with. This simple concept is a surprisingly powerful way to build web applications. |
| 30 | + |
| 31 | +You can read more about how fixi works on its [homepage](https://github.com/bigskysoftware/fixi). |
| 32 | + |
| 33 | +### 🥊 `moxi.js` - inline scripting & simple reactivity |
| 34 | + |
| 35 | +`moxi.js` adds inline scripting and DOM-based reactivity. It is based on [hyperscript](https://hyperscript.org) and lets |
| 36 | +you put behavior directly on elements via `on-*` attributes, plus a `live` attribute that re-runs whenever the page |
| 37 | +changes. |
| 38 | + |
| 39 | +Here is a moxi-powered button: |
| 40 | + |
| 41 | +```html |
| 42 | + |
| 43 | +<button on-click="this.disabled = true; this.innerText = 'thanks!'"> |
| 44 | + Click me |
| 45 | +</button> |
| 46 | +``` |
| 47 | + |
| 48 | +This button disables itself and updates its text when clicked, without a separate `<script>` block. Each `on-*` handler |
| 49 | +is compiled into an async function with access to helpers like `q()` (a proxy over a set of matched elements), `trigger()`, |
| 50 | +`wait()`, and `debounce()`. |
| 51 | + |
| 52 | +You can read more about how moxi works on its [homepage](https://github.com/bigskysoftware/moxi). |
| 53 | + |
| 54 | +### 📡 `ssexi.js` - streaming HTML & events |
| 55 | + |
| 56 | +`ssexi.js` is a companion library for fixi that |
| 57 | +adds [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) support. It is inspired |
| 58 | +by [htmx's SSE extension](https://github.com/bigskysoftware/htmx/blob/four/src/ext/hx-sse.js): whenever a fixi |
| 59 | +response comes back with `Content-Type: text/event-stream`, ssexi takes over the swap loop and streams each message into |
| 60 | +the target as it arrives. |
| 61 | + |
| 62 | +Here is an ssexi-powered log: |
| 63 | + |
| 64 | +```html |
| 65 | + |
| 66 | +<button fx-action="/events" fx-swap="beforeend" fx-target="#log"> |
| 67 | + Start |
| 68 | +</button> |
| 69 | +<div id="log"></div> |
| 70 | +``` |
| 71 | + |
| 72 | +When `/events` responds with an SSE stream, each `data:` line is appended to `#log`. Messages with a named `event:` |
| 73 | +field fire DOM events (`fx:sse:<name>`) instead of swapping, which gives you a clean seam for JavaScript hooks like |
| 74 | +progress or "done" signals. |
| 75 | + |
| 76 | +You can read more about how ssexi works on its [homepage](https://github.com/bigskysoftware/ssexi). |
| 77 | + |
| 78 | +### ♻️ `paxi.js` - DOM patching (morphing) |
| 79 | + |
| 80 | +`paxi.js` adds a `morph` swap strategy to fixi. It is based on [idiomorph](https://github.com/bigskysoftware/idiomorph) |
| 81 | +and patches an existing subtree into the shape of a new one in place, matching elements by id rather than replacing them |
| 82 | +wholesale. |
| 83 | + |
| 84 | +Here is a paxi-powered swap: |
| 85 | + |
| 86 | +```html |
| 87 | + |
| 88 | +<button fx-action="/counter" fx-swap="morph" fx-target="#count"> |
| 89 | + Increment |
| 90 | +</button> |
| 91 | +<span id="count">0</span> |
| 92 | +``` |
| 93 | + |
| 94 | +When the server responds with an updated `<span id="count">1</span>`, paxi morphs the existing span instead of replacing |
| 95 | +the node. The practical upshot is that focus, selection, input state, and event listeners survive a swap. It also allows |
| 96 | +you to use CSS transitions. |
| 97 | + |
| 98 | +You can read more about how paxi works on its [homepage](https://github.com/bigskysoftware/paxi). |
| 99 | + |
| 100 | +### 🐕 `rexi.js` - an ergonomic `fetch()` wrapper |
| 101 | + |
| 102 | +`rexi.js` is a small fluent wrapper around `fetch()`, inspired by [hyperscript's |
| 103 | +`fetch` command](https://hyperscript.org/commands/fetch/). It handles the usual boring parts of calling an HTTP endpoint |
| 104 | +from JavaScript: serializing a form or a plain object as a body, throwing on non-2xx responses, decoding the result, and |
| 105 | +aborting on demand. |
| 106 | + |
| 107 | +Here is a rexi-powered POST: |
| 108 | + |
| 109 | +```js |
| 110 | +let user = await post('/users', {name: 'carson'}).json() |
| 111 | +``` |
| 112 | + |
| 113 | +rexi serializes the object as a JSON body, throws if the server returns an error status, and decodes the JSON response |
| 114 | +for you. It also accepts `FormData`, `URLSearchParams`, or a form `Element` directly as the body, so it slots neatly |
| 115 | +into moxi handlers and fixi-driven pages. |
| 116 | + |
| 117 | +You can read more about how rexi works on its [homepage](https://github.com/bigskysoftware/rexi). |
| 118 | + |
| 119 | +## 🧰 `the-fixi-project.js` |
| 120 | + |
| 121 | +All five libraries are also published as a single pre-concatenated, minified, and brotli-compressed bundle under the [ |
| 122 | +`the-fixi-project`](https://www.npmjs.com/package/the-fixi-project) npm package: |
| 123 | + |
| 124 | +```html |
| 125 | + |
| 126 | +<script src="https://cdn.jsdelivr.net/npm/the-fixi-project/dist/the-fixi-project.min.js"></script> |
| 127 | +``` |
| 128 | + |
| 129 | +The entire fixi project comes in at ~4.2kb when brotli-compressed. |
| 130 | + |
| 131 | +## Developing |
| 132 | + |
| 133 | +From a fresh checkout of this repo: |
| 134 | + |
| 135 | +```bash |
| 136 | +npm install # installs playwright + terser (dev-only) |
| 137 | +npm run clone # clones each sub-project repo into its directory |
| 138 | +npm test # runs the full test suite across all libraries (headless) |
| 139 | +npm run build # builds the all-in-one bundle into dist/ |
| 140 | +npm run serve # serves the project over http://localhost:8000 |
| 141 | +``` |
| 142 | + |
| 143 | +`npm test` accepts a subset of project names, so `npm test rexi paxi` runs only those two. |
| 144 | + |
| 145 | +## License |
| 146 | + |
| 147 | +BSD-0 (Zero-Clause BSD) across all libraries. |
0 commit comments