micra.js 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +43 -0
- package/dist/core/reactive.d.ts +4 -0
- package/dist/dom/directives.d.ts +2 -2
- package/dist/dom/scan.d.ts +3 -3
- package/dist/micra.cjs.js +11 -12
- package/dist/micra.cjs.js.map +2 -2
- package/dist/micra.esm.js +11 -12
- package/dist/micra.esm.js.map +2 -2
- package/dist/micra.js +11 -12
- package/dist/micra.js.map +2 -2
- package/dist/micra.min.js +2 -2
- package/dist/types.d.ts +0 -2
- package/llms-full.txt +110 -15
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/core/mount.ts +1 -1
- package/src/core/reactive.ts +6 -1
- package/src/dom/directives.ts +1 -3
- package/src/dom/each.ts +3 -5
- package/src/dom/scan.ts +3 -3
- package/src/types.ts +0 -2
- package/src/dom/query.ts +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,49 @@ All notable changes to Micra.js will be documented in this file. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning follows
|
|
5
5
|
[SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.3.1] — 2026-05-30
|
|
8
|
+
|
|
9
|
+
### Performance
|
|
10
|
+
|
|
11
|
+
- **Batch scheduler now uses `queueMicrotask` instead of
|
|
12
|
+
`Promise.resolve().then(...)`.** Each render batch enqueues a single
|
|
13
|
+
microtask instead of allocating a Promise plus a reaction job, and the
|
|
14
|
+
flush callback is hoisted out of the hot path so it isn't re-created on
|
|
15
|
+
every `schedule()` call. Behaviour is identical — same microtask timing,
|
|
16
|
+
same write-collapsing. No public-API change.
|
|
17
|
+
|
|
18
|
+
### Internal — dead-code removal
|
|
19
|
+
|
|
20
|
+
- Removed the `src/dom/query.ts` module (`queryAll` / `queryOwn` /
|
|
21
|
+
`queryOwnAll` / `filterOwn`). It had no importers since the 2.2.0
|
|
22
|
+
single-pass scan replaced per-render `querySelectorAll` calls with one
|
|
23
|
+
`TreeWalker` traversal — esbuild already tree-shook it out of the
|
|
24
|
+
bundle, so this is a source-only cleanup.
|
|
25
|
+
- Removed two dead bookkeeping writes: `node.__micraEach` and
|
|
26
|
+
`node.__micraKey` were assigned during list rendering but never read
|
|
27
|
+
(keys live in the keyed-diff `Map`; the no-key path doesn't tag rows).
|
|
28
|
+
Dropped the matching fields from `MicraElement`.
|
|
29
|
+
- Dropped the unused `instance` parameter from `applyDirectives` — it was
|
|
30
|
+
never referenced in the body.
|
|
31
|
+
|
|
32
|
+
### Docs
|
|
33
|
+
|
|
34
|
+
- New [Rails + Micra recipe](https://github.com/denisfl/micra.js/blob/master/docs/recipes/rails.md)
|
|
35
|
+
(`docs/recipes/rails.md` + a site page): manual importmap integration,
|
|
36
|
+
the `micra-rails` gem with its caveats, a Tasks board demonstrating SSR
|
|
37
|
+
props / CSRF-attached `this.fetch` / cross-component bus, and the Turbo
|
|
38
|
+
Drive / Streams / Frames mount-and-cleanup story.
|
|
39
|
+
- README gains a **TypeScript** section spelling out what's checked
|
|
40
|
+
end-to-end (state, methods, event payloads) versus what isn't (the
|
|
41
|
+
expression strings inside `data-*` attributes).
|
|
42
|
+
- Landing page gains **Speed** (cross-library benchmark cards) and **AI
|
|
43
|
+
sandboxes** (copy-the-LLM-prompt) sections.
|
|
44
|
+
|
|
45
|
+
### Bundle
|
|
46
|
+
|
|
47
|
+
- **5.5 KB gzip** (5582 bytes) — a few bytes lighter than 2.3.0 after the
|
|
48
|
+
dead-code removal.
|
|
49
|
+
|
|
7
50
|
## [2.3.0] — 2026-05-30
|
|
8
51
|
|
|
9
52
|
### TypeScript — type-safe event bus
|
package/README.md
CHANGED
|
@@ -71,6 +71,48 @@ npm install micra.js
|
|
|
71
71
|
import * as Micra from "micra.js";
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### TypeScript
|
|
75
|
+
|
|
76
|
+
The npm package ships its own `dist/index.d.ts` — no `@types/micra.js` package
|
|
77
|
+
needed. Inside every method body and lifecycle hook, both `this.state.X` and
|
|
78
|
+
`this.someMethod()` are fully checked at the call site (both `state` and the
|
|
79
|
+
method set are inferred from the literal you pass to `Micra.define`).
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import * as Micra from "micra.js";
|
|
83
|
+
|
|
84
|
+
Micra.define("counter", {
|
|
85
|
+
state: { count: 0 },
|
|
86
|
+
inc() {
|
|
87
|
+
this.state.count++; // ✓ number
|
|
88
|
+
this.dec(); // ✓ inferred sibling method
|
|
89
|
+
// this.foo(); // ✗ Property 'foo' does not exist
|
|
90
|
+
},
|
|
91
|
+
dec() { this.state.count--; },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Type-safe event bus via declaration merging
|
|
95
|
+
declare module "micra.js" {
|
|
96
|
+
interface MicraEvents {
|
|
97
|
+
"cart:updated": { count: number };
|
|
98
|
+
"modal:close": void;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Micra.emit("cart:updated", { count: 3 }); // ✓
|
|
103
|
+
Micra.emit("cart:updated", { count: "3" }); // ✗ type error
|
|
104
|
+
Micra.emit("modal:close"); // ✓ void → no args
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**What's checked:** imports, state shape, method names, event-bus payloads,
|
|
108
|
+
lifecycle hooks, refs, `Micra.mount()` return type.
|
|
109
|
+
|
|
110
|
+
**What's not:** the expression strings inside `data-text="…"` / `@click="…"`
|
|
111
|
+
attributes — those are plain HTML to the IDE and validated only at mount
|
|
112
|
+
time. Same trade-off as Alpine.js `x-*` and petite-vue `v-*`; the
|
|
113
|
+
alternatives are JSX or a single-file-component compiler, neither of which
|
|
114
|
+
Micra ships.
|
|
115
|
+
|
|
74
116
|
## Basic usage
|
|
75
117
|
|
|
76
118
|
A counter mounted automatically from `data-component`:
|
|
@@ -183,6 +225,7 @@ this.on(event, handler)
|
|
|
183
225
|
- [Todo app](./docs/recipes/todo-app.md)
|
|
184
226
|
- [Server-sent events (SSE)](./docs/recipes/sse.md)
|
|
185
227
|
- [htmx bridge](./docs/recipes/htmx.md)
|
|
228
|
+
- [Rails + Micra](./docs/recipes/rails.md)
|
|
186
229
|
|
|
187
230
|
## Code generation with LLMs
|
|
188
231
|
|
package/dist/core/reactive.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export declare function createReactiveState<S extends StateRecord>(obj: S, sched
|
|
|
23
23
|
* Return a debounce function that defers `render` to the next microtask.
|
|
24
24
|
* Multiple calls within the same tick collapse to a single render.
|
|
25
25
|
*
|
|
26
|
+
* Uses `queueMicrotask` so each batch enqueues a single microtask instead of
|
|
27
|
+
* allocating a Promise + reaction job. `flush` is hoisted out of the hot path
|
|
28
|
+
* so it isn't re-created on every schedule() call.
|
|
29
|
+
*
|
|
26
30
|
* @example
|
|
27
31
|
* const schedule = createScheduler(render)
|
|
28
32
|
* schedule() // defers render
|
package/dist/dom/directives.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Important: this module does NOT handle data-each — see dom/each.ts.
|
|
13
13
|
*/
|
|
14
|
-
import type {
|
|
14
|
+
import type { ScanIndex, StateRecord } from '../types';
|
|
15
15
|
import { warn } from '../utils/expr';
|
|
16
16
|
/**
|
|
17
17
|
* Apply all non-each directives to a component subtree.
|
|
@@ -23,7 +23,7 @@ import { warn } from '../utils/expr';
|
|
|
23
23
|
* @param state - Expression state (may include item/index for each rows)
|
|
24
24
|
* @param rawState - Raw (non-proxy) state for model sync
|
|
25
25
|
*/
|
|
26
|
-
export declare function applyDirectives
|
|
26
|
+
export declare function applyDirectives(scan: ScanIndex, state: StateRecord, rawState: StateRecord): void;
|
|
27
27
|
/**
|
|
28
28
|
* Validate directive usage and emit dev warnings.
|
|
29
29
|
* Called once after the initial render of a component, with the already-built
|
package/dist/dom/scan.d.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* traversal that classifies every directive attribute in a single visit.
|
|
6
6
|
*
|
|
7
7
|
* Boundaries:
|
|
8
|
-
* - REJECT (skip subtree) on nested [data-component] —
|
|
9
|
-
*
|
|
10
|
-
* even *visit* those nodes.
|
|
8
|
+
* - REJECT (skip subtree) on nested [data-component] — a parent component
|
|
9
|
+
* never processes directives owned by a nested child. Applied during the
|
|
10
|
+
* walk so we don't even *visit* those nodes.
|
|
11
11
|
* - <template> contents are not visited (browser TreeWalker default).
|
|
12
12
|
* `<template data-each>` itself IS visited and classified into scan.each;
|
|
13
13
|
* its children are processed by each.ts on every render — fresh rows
|
package/dist/micra.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Micra.js v2.3.
|
|
1
|
+
/* Micra.js v2.3.1 — https://github.com/micra-js/micra — MIT */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -253,13 +253,14 @@ function createReactiveState(obj, schedule, onKey) {
|
|
|
253
253
|
}
|
|
254
254
|
function createScheduler(render) {
|
|
255
255
|
let pending = false;
|
|
256
|
+
const flush = () => {
|
|
257
|
+
pending = false;
|
|
258
|
+
render();
|
|
259
|
+
};
|
|
256
260
|
return function schedule() {
|
|
257
261
|
if (pending) return;
|
|
258
262
|
pending = true;
|
|
259
|
-
|
|
260
|
-
pending = false;
|
|
261
|
-
render();
|
|
262
|
-
});
|
|
263
|
+
queueMicrotask(flush);
|
|
263
264
|
};
|
|
264
265
|
}
|
|
265
266
|
|
|
@@ -325,7 +326,7 @@ function applyModel(el, key, rawState) {
|
|
|
325
326
|
const desired = stateVal == null ? "" : String(stateVal);
|
|
326
327
|
if (html.value !== desired) html.value = desired;
|
|
327
328
|
}
|
|
328
|
-
function applyDirectives(scan, state, rawState
|
|
329
|
+
function applyDirectives(scan, state, rawState) {
|
|
329
330
|
for (const b of scan.if) applyIf(b, state);
|
|
330
331
|
for (const b of scan.text) applyText(b.el, b.expr, state);
|
|
331
332
|
for (const b of scan.html) applyHtml(b.el, b.expr, state);
|
|
@@ -608,7 +609,6 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, inst
|
|
|
608
609
|
let node = keyMap.get(key);
|
|
609
610
|
if (!node) {
|
|
610
611
|
node = createRowNode(tmpl, state, instance);
|
|
611
|
-
node.__micraKey = key;
|
|
612
612
|
keyMap.set(key, node);
|
|
613
613
|
} else if (canSkipUnchanged && node.__micraItem === item && node.__micraIndex === index) {
|
|
614
614
|
nextNodes.push(node);
|
|
@@ -621,7 +621,7 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, inst
|
|
|
621
621
|
itemState.index = index;
|
|
622
622
|
itemState.$index = index;
|
|
623
623
|
const rowScan = (_a = node.__micraScan) != null ? _a : node.__micraScan = scanComponent(node);
|
|
624
|
-
applyDirectives(rowScan, itemState, rawState
|
|
624
|
+
applyDirectives(rowScan, itemState, rawState);
|
|
625
625
|
nextNodes.push(node);
|
|
626
626
|
}
|
|
627
627
|
for (const [key, node] of keyMap) {
|
|
@@ -706,7 +706,7 @@ function renderNoKey(tmpl, items, marker, state, rawState, instance, canSkipUnch
|
|
|
706
706
|
itemState.item = item;
|
|
707
707
|
itemState.index = i;
|
|
708
708
|
itemState.$index = i;
|
|
709
|
-
applyDirectives(node.__micraScan, itemState, rawState
|
|
709
|
+
applyDirectives(node.__micraScan, itemState, rawState);
|
|
710
710
|
nextList[i] = node;
|
|
711
711
|
}
|
|
712
712
|
for (let i = nextLen; i < prevLen; i++) {
|
|
@@ -721,10 +721,9 @@ function renderNoKey(tmpl, items, marker, state, rawState, instance, canSkipUnch
|
|
|
721
721
|
itemState.item = item;
|
|
722
722
|
itemState.index = i;
|
|
723
723
|
itemState.$index = i;
|
|
724
|
-
node.__micraEach = true;
|
|
725
724
|
node.__micraItem = item;
|
|
726
725
|
node.__micraIndex = i;
|
|
727
|
-
applyDirectives(node.__micraScan, itemState, rawState
|
|
726
|
+
applyDirectives(node.__micraScan, itemState, rawState);
|
|
728
727
|
nextList[i] = node;
|
|
729
728
|
frag.append(node);
|
|
730
729
|
}
|
|
@@ -823,7 +822,7 @@ function mount(selector, definition) {
|
|
|
823
822
|
try {
|
|
824
823
|
const mRoot2 = root;
|
|
825
824
|
const scan = (_a2 = mRoot2.__micraScan) != null ? _a2 : mRoot2.__micraScan = scanComponent(root);
|
|
826
|
-
applyDirectives(scan, exprState, rawState
|
|
825
|
+
applyDirectives(scan, exprState, rawState);
|
|
827
826
|
renderList(scan.each, exprState, rawState, instance, triggerKey);
|
|
828
827
|
bindDataOn(scan.on, instance);
|
|
829
828
|
bindAtEvents(scan.atEvents, instance);
|