bimba-cli 0.7.7 → 0.7.9
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/INTERNALS.md +300 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/serve.js +21 -51
package/INTERNALS.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Bimba Internals: How Imba Rendering Works and How Bimba HMR Hooks Into It
|
|
2
|
+
|
|
3
|
+
Technical reference for debugging and extending bimba's dev server (`serve.js`).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. How Imba Compiles Tags to JS
|
|
8
|
+
|
|
9
|
+
Imba source:
|
|
10
|
+
```imba
|
|
11
|
+
tag my-popup
|
|
12
|
+
name = ''
|
|
13
|
+
|
|
14
|
+
def mount
|
|
15
|
+
name = 'hello'
|
|
16
|
+
|
|
17
|
+
<self @click.self=(emit('close'))>
|
|
18
|
+
<div.dialog>
|
|
19
|
+
<span.title> "Settings"
|
|
20
|
+
if condition
|
|
21
|
+
<img src=url>
|
|
22
|
+
else
|
|
23
|
+
<span.placeholder> "?"
|
|
24
|
+
<button @click=save> "Save"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Compiled JS (simplified):
|
|
28
|
+
```js
|
|
29
|
+
import { Component, defineTag, createElement, createComponent, ... } from 'imba';
|
|
30
|
+
const $beforeReconcile$ = Symbol.for('#beforeReconcile');
|
|
31
|
+
const $afterVisit$ = Symbol.for('#afterVisit');
|
|
32
|
+
const $placeChild$ = Symbol.for('#placeChild');
|
|
33
|
+
const $$up$ = Symbol.for('##up');
|
|
34
|
+
|
|
35
|
+
// Anonymous Symbols — one per DOM slot in the render tree.
|
|
36
|
+
// These are the RENDER CACHE KEYS.
|
|
37
|
+
var $7 = Symbol(), $11 = Symbol(), $13 = Symbol(), $19 = Symbol(), $24 = Symbol(), ...;
|
|
38
|
+
let c$0 = Symbol(); // class identity symbol
|
|
39
|
+
|
|
40
|
+
class MyPopupComponent extends Component {
|
|
41
|
+
[__init__$]($$ = null) {
|
|
42
|
+
super[__init__$](...arguments);
|
|
43
|
+
this.name = ($$ && $$.name !== undefined) ? $$.name : '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
mount() { this.name = 'hello'; }
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
var $4, $5, $6, $8 = this._ns_ || '', $9, $18, ...;
|
|
50
|
+
$4 = this; // $4 = the element itself
|
|
51
|
+
$4[$beforeReconcile$]();
|
|
52
|
+
|
|
53
|
+
// ── "First render" check ──
|
|
54
|
+
// $7 is a Symbol. this[$7] is stored on the INSTANCE.
|
|
55
|
+
// First render: this[$7] is undefined → $5=0 (CREATE mode)
|
|
56
|
+
// Re-render: this[$7] === 1 → $5=1 (REUSE mode)
|
|
57
|
+
($5=$6=1, $4[$7] === 1) || ($5=$6=0, $4[$7] = 1);
|
|
58
|
+
|
|
59
|
+
// ── Static children: created only on first render ($5=0) ──
|
|
60
|
+
$5 || ($4.on$('click', {self: true, ...}));
|
|
61
|
+
$5 || ($9 = createElement('div', $4, `dialog ${$8}`, null));
|
|
62
|
+
// ↑ createElement appends $9 as child of $4
|
|
63
|
+
|
|
64
|
+
// ── Cached children: checked on every render ──
|
|
65
|
+
($10 = $4[$11]) || ($4[$11] = $10 = createElement('span', $9, ...));
|
|
66
|
+
// ↑ try cache ↑ miss → create and cache
|
|
67
|
+
|
|
68
|
+
// ── Conditional blocks ──
|
|
69
|
+
$18 = null;
|
|
70
|
+
if (this.condition) {
|
|
71
|
+
($20=$21=1, $18=$4[$19]) || ($20=$21=0, $4[$19]=$18=createElement('img',...));
|
|
72
|
+
} else {
|
|
73
|
+
($25=$26=1, $18=$4[$24]) || ($25=$26=0, $4[$24]=$18=createElement('span',...));
|
|
74
|
+
}
|
|
75
|
+
// placeChild manages which branch is in the DOM
|
|
76
|
+
($4[$30] = $16[$placeChild$]($18, 0, $4[$30]));
|
|
77
|
+
|
|
78
|
+
$4[$afterReconcile$]($6);
|
|
79
|
+
return $4;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Static block runs at class definition time
|
|
83
|
+
static {
|
|
84
|
+
register$(this, c$0, 'my-popup', 2); // → calls customElements.define
|
|
85
|
+
defineTag('my-popup', this, {cssns: 'z1abc_xy', cssid: 'z1abc-xy'});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// CSS is registered as a global stylesheet
|
|
90
|
+
imba_styles.register('z1abc', "...");
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Key points
|
|
94
|
+
|
|
95
|
+
| Concept | Details |
|
|
96
|
+
|---------|---------|
|
|
97
|
+
| **Render cache** | Each DOM node is cached on the element instance under an anonymous `Symbol()` key. `this[$sym] \|\| (this[$sym] = createElement(...))`. |
|
|
98
|
+
| **Create vs Reuse** | `this[$7] === 1` is the master flag. `$5=0` = first render (create all), `$5=1` = re-render (reuse cached). |
|
|
99
|
+
| **Static children** | Guarded by `$5 \|\| (...)` — created only on first render, never recreated. |
|
|
100
|
+
| **Conditional children** | Each branch has its own cache slot (`$19` for `if`, `$24` for `else`). `$placeChild$` swaps them in/out. |
|
|
101
|
+
| **CSS namespace** | `_ns_` on prototype (e.g. `"z1abc_xy "`). Used as className prefix. Hash changes when CSS content changes. |
|
|
102
|
+
| **Tag registration** | `register$` → `customElements.define()`. `defineTag` → sets `_ns_`, `cssid`, registers in Imba's internal tag registry (`J[name]`, `xh[name]`). |
|
|
103
|
+
| **Lifecycle** | `__init__$` (property defaults), `connectedCallback` (DOM attachment), `mount` (post-connect, user code), `render` (DOM creation/update). |
|
|
104
|
+
|
|
105
|
+
### Imba runtime functions
|
|
106
|
+
|
|
107
|
+
| Function | What it does |
|
|
108
|
+
|----------|-------------|
|
|
109
|
+
| `createElement(tag, parent, className, text)` | `document.createElement` + `parent[appendChild$](el)`. For plain HTML elements. |
|
|
110
|
+
| `createComponent(name, parent, className, text)` | Same but for custom elements. If `name` is a string, uses `document.createElement(name)`. |
|
|
111
|
+
| `imba_styles.register(id, css)` | Injects/updates a `<style>` element in `<head>`. Idempotent by `id`. |
|
|
112
|
+
| `defineTag(name, klass, opts)` | Registers tag in Imba's internal registry. Sets `_ns_`, `cssid`, `flags$ns` on prototype. |
|
|
113
|
+
| `register$(klass, symbol, name, flags)` | Sets up class metadata (`__meta__$`), calls `customElements.define`. |
|
|
114
|
+
| `imba.commit()` | Schedules a render tick via `requestAnimationFrame`. All scheduled components re-render. |
|
|
115
|
+
| `$beforeReconcile$` | Called at start of render. Clears internal child tracking state. |
|
|
116
|
+
| `$afterReconcile$` | Called at end of render. Finalizes child list. |
|
|
117
|
+
| `$placeChild$(child, type, prev)` | Manages conditional/dynamic child placement. Inserts/removes/replaces nodes. |
|
|
118
|
+
| `$afterVisit$(flag)` | Post-render hook on a component child. Triggers its own render if needed. |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 2. The Problem Bimba Solves
|
|
123
|
+
|
|
124
|
+
Browsers have no built-in HMR for custom elements:
|
|
125
|
+
- `customElements.define(name, class)` can only be called ONCE per tag name
|
|
126
|
+
- Re-importing a module creates fresh `Symbol()` instances — old cache keys become orphans
|
|
127
|
+
- Without intervention, re-importing causes full duplication of DOM children
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 3. How Bimba's HMR Works
|
|
132
|
+
|
|
133
|
+
### 3.1 Symbol Stabilization (server-side)
|
|
134
|
+
|
|
135
|
+
**Problem:** Each `var $7 = Symbol()` creates a unique symbol. Re-importing the module creates a NEW `$7` symbol. Existing elements have DOM cached under the OLD `$7`. The new render method looks up `this[NEW_$7]` — not found → creates duplicate DOM.
|
|
136
|
+
|
|
137
|
+
**Solution:** Rewrite `Symbol()` calls to use a persistent global cache:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
$7 = Symbol()
|
|
141
|
+
↓
|
|
142
|
+
$7 = (__bsyms__["$7"] ||= Symbol())
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Where `__bsyms__` is keyed by absolute file path:
|
|
146
|
+
```js
|
|
147
|
+
const __bsyms__ = ((globalThis.__bimba_syms ||= {})["/abs/path/to/file.imba"] ||= {});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
First load: creates symbols, stores in cache.
|
|
151
|
+
HMR reload: reuses same symbols from cache → render finds cached DOM → REUSE mode.
|
|
152
|
+
|
|
153
|
+
**Critical:** The file path key MUST be normalized (absolute via `path.resolve`). Different string representations of the same file (e.g., `./src/foo.imba` vs `src/foo.imba`) produce different cache keys → different symbols → duplication. This was the root cause of the v0.7.8 fix.
|
|
154
|
+
|
|
155
|
+
### 3.2 Slot Stability Detection
|
|
156
|
+
|
|
157
|
+
If the user adds/removes template elements, the number of `Symbol()` declarations changes. Variable names shift (`$7` now means a different DOM slot). Even with stable symbols, the SEMANTICS change.
|
|
158
|
+
|
|
159
|
+
Detection: count `Symbol()` calls per file. Compare to previous compilation:
|
|
160
|
+
- Same count → `slots: 'stable'` → safe for in-place HMR
|
|
161
|
+
- Different count → `slots: 'shifted'` → must do destructive HMR
|
|
162
|
+
|
|
163
|
+
### 3.3 Prototype Patching (browser-side)
|
|
164
|
+
|
|
165
|
+
`customElements.define` is hooked:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
First call (page load): register normally, save class in _classes map
|
|
169
|
+
Repeat calls (HMR): _patchClass(originalClass, newClass)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`_patchClass` copies ALL own property descriptors (string + symbol keys) from the new class prototype to the original class prototype, skipping `constructor`. Also copies static properties (skipping `length`, `name`, `prototype`, `caller`, `arguments`).
|
|
173
|
+
|
|
174
|
+
Effect: all existing element instances immediately get new methods via the prototype chain. No need to recreate elements.
|
|
175
|
+
|
|
176
|
+
### 3.4 CSS Namespace Sync
|
|
177
|
+
|
|
178
|
+
When CSS changes, Imba generates a new hash → new `_ns_` (e.g., `"z1abc_xy "` → `"z9def_gh "`). The issue:
|
|
179
|
+
|
|
180
|
+
1. `register$` → `customElements.define` → bimba's hook → `_patchClass` runs
|
|
181
|
+
2. `defineTag` runs AFTER `register$` — sets `_ns_` on the NEW class prototype
|
|
182
|
+
3. But `_patchClass` already ran, so the OLD prototype still has the old `_ns_`
|
|
183
|
+
|
|
184
|
+
Solution: after `import()` completes, sync `_ns_` manually:
|
|
185
|
+
```js
|
|
186
|
+
oldCls.prototype._ns_ = newCls.prototype._ns_;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then patch `className` on ALL custom elements in the DOM, replacing old hash parts with new ones.
|
|
190
|
+
|
|
191
|
+
### 3.5 Always-Destructive HMR
|
|
192
|
+
|
|
193
|
+
> **History:** Earlier versions (≤0.7.8) had two paths — "stable" (in-place
|
|
194
|
+
> prototype patching + `imba.commit()`) and "shifted" (destructive wipe +
|
|
195
|
+
> re-render). The stable path was meant to preserve DOM state (inputs, focus,
|
|
196
|
+
> popups) when only CSS or logic changed without adding/removing template
|
|
197
|
+
> elements. However, it fundamentally didn't work: imba's reconciliation uses
|
|
198
|
+
> slot-tracking symbols (`this[$sym] === 1`) to skip re-creating elements on
|
|
199
|
+
> re-render. Even when `_patchClass` installs a new `render()` method, calling
|
|
200
|
+
> `render()` (or `imba.commit()`) does nothing — the slot check says "already
|
|
201
|
+
> created" and skips `createElement`. Static text, attributes, and other
|
|
202
|
+
> arguments baked into `createElement` calls never update.
|
|
203
|
+
>
|
|
204
|
+
> Since 0.7.9, bimba always takes the destructive path.
|
|
205
|
+
|
|
206
|
+
The `slots` field is still computed and broadcast (for potential future use),
|
|
207
|
+
but the client ignores it. Every HMR update does:
|
|
208
|
+
|
|
209
|
+
1. `_patchClass` updates prototype (during import)
|
|
210
|
+
2. `_ns_` is synced
|
|
211
|
+
3. For each instance of each affected tag:
|
|
212
|
+
- Save instance properties (`Object.keys(el)`)
|
|
213
|
+
- Call `disconnectedCallback` on all descendant custom elements
|
|
214
|
+
- Delete all anonymous Symbol properties (render cache) — skip `Symbol.for(...)` ones
|
|
215
|
+
- `innerHTML = ''` — wipe DOM
|
|
216
|
+
- Restore instance properties
|
|
217
|
+
- `el.render()` — rebuild DOM from scratch with new render method
|
|
218
|
+
- `el.connectedCallback()`, `el.mount()` — re-initialize
|
|
219
|
+
4. `imba.commit()` for final sync
|
|
220
|
+
|
|
221
|
+
**Trade-off:** Input focus, scroll position, and popup state are lost on every
|
|
222
|
+
edit. This is acceptable because correctness beats convenience — a "stable"
|
|
223
|
+
update that silently ignores the change is far more confusing than losing
|
|
224
|
+
transient UI state.
|
|
225
|
+
|
|
226
|
+
### 3.6 Body-level Deduplication
|
|
227
|
+
|
|
228
|
+
Some modules call `imba.mount(<app-root>)` at top level. Re-importing the module would create a second root element. After each HMR import, bimba checks for new body children with the same tag name as existing ones and removes duplicates.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 4. Server Architecture
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
serve.js
|
|
236
|
+
├── HMR Client (injected as <script> into HTML)
|
|
237
|
+
│ ├── customElements.define hook
|
|
238
|
+
│ ├── _patchClass / _copyDescriptors
|
|
239
|
+
│ ├── _doUpdate (stable/shifted paths)
|
|
240
|
+
│ ├── WebSocket connection
|
|
241
|
+
│ └── Error overlay
|
|
242
|
+
│
|
|
243
|
+
├── Symbol Stabilization
|
|
244
|
+
│ ├── stabilizeSymbols(js, absPath)
|
|
245
|
+
│ └── Slot count tracking (_prevSlots)
|
|
246
|
+
│
|
|
247
|
+
├── Compiler
|
|
248
|
+
│ ├── compileFile(filepath) — compile + stabilize + cache
|
|
249
|
+
│ ├── _compileCache (abs path → {mtime, result})
|
|
250
|
+
│ └── _prevJs (abs path → js string, for change detection)
|
|
251
|
+
│
|
|
252
|
+
├── Import Graph
|
|
253
|
+
│ ├── extractImports(js, absPath) — scan for .imba imports
|
|
254
|
+
│ ├── updateImportGraph(from, deps) — maintain bidirectional graph
|
|
255
|
+
│ └── _imports / _importers maps
|
|
256
|
+
│
|
|
257
|
+
├── File Watcher
|
|
258
|
+
│ └── watch(srcDir) → compile → broadcast update via WebSocket
|
|
259
|
+
│
|
|
260
|
+
├── HTTP Server
|
|
261
|
+
│ ├── / → HTML with injected import map + HMR client
|
|
262
|
+
│ ├── *.imba → compile on demand → serve as JS
|
|
263
|
+
│ ├── *.css → wrap as JS module (style injection)
|
|
264
|
+
│ ├── /node_modules/* → resolve entry, compile .imba, wrap CJS
|
|
265
|
+
│ └── Static files (htmlDir, then root)
|
|
266
|
+
│
|
|
267
|
+
├── Import Map (minimal, browser-side)
|
|
268
|
+
│ └── bare specifier → /node_modules/pkg/ prefix mapping
|
|
269
|
+
│
|
|
270
|
+
└── Node Modules Resolution (server-side)
|
|
271
|
+
├── resolveEntry(pkg.json) — exports/module/browser/main
|
|
272
|
+
├── wrapCJS(code) — detect CJS, wrap as ESM
|
|
273
|
+
└── Extension fallback (.imba → .js → .mjs)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 5. Common Pitfalls and Debugging
|
|
279
|
+
|
|
280
|
+
### Symptom: Elements duplicate on first edit, not on second
|
|
281
|
+
**Cause:** Symbol cache key mismatch between initial load and HMR. Check that `stabilizeSymbols` receives the same file path from both the HTTP handler and the file watcher. Must use `path.resolve()` for normalization.
|
|
282
|
+
|
|
283
|
+
### Symptom: CSS changes don't apply after HMR
|
|
284
|
+
**Cause:** `_ns_` not synced. Check the `_nsPatches` logic — `defineTag` sets `_ns_` AFTER `register$`, so `_patchClass` misses it. The post-import sync block must handle this.
|
|
285
|
+
|
|
286
|
+
### Symptom: Methods don't update after HMR
|
|
287
|
+
**Cause:** `_patchClass` might not be running. Check that `customElements.get(name)` returns the existing class. Verify the hook on `customElements.define` is active.
|
|
288
|
+
|
|
289
|
+
### Symptom: State lost on edit (inputs clear, popups close)
|
|
290
|
+
**Cause:** Taking the shifted path when stable would suffice. Check slot count — adding a comment or whitespace shouldn't change `Symbol()` count. If it does, the stabilization regex might be too broad/narrow.
|
|
291
|
+
|
|
292
|
+
### Symptom: 500 errors on node_modules subpaths
|
|
293
|
+
**Cause:** `serveResolved` trying `.imba` extension without existence check. The `.imba` path calls `compileFile()` which throws on non-existent files.
|
|
294
|
+
|
|
295
|
+
### Debugging approach
|
|
296
|
+
Add to HMR client `_doUpdate`:
|
|
297
|
+
```js
|
|
298
|
+
console.log('[bimba]', file, 'slots=' + slots, 'tags:', collected);
|
|
299
|
+
```
|
|
300
|
+
Check `globalThis.__bimba_syms` in browser console — keys should be absolute paths, values should be objects with `$7`, `$11`, etc. If you see two entries for the same file with different path formats, that's the symbol mismatch bug.
|
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ Duplicate root elements (caused by `imba.mount()` running again on re-import) ar
|
|
|
56
56
|
|
|
57
57
|
**Smart HMR:** bimba detects whether a change affects the template structure (adding/removing elements) or just CSS/logic. CSS-only and logic-only changes patch prototypes in place without wiping innerHTML — preserving input focus, scroll position, and open popups. Template-structural changes do a full wipe-and-rerender to ensure correctness.
|
|
58
58
|
|
|
59
|
+
For a deep dive into how Imba compiles tags, how the render cache works, and how bimba hooks into it — see [INTERNALS.md](INTERNALS.md).
|
|
60
|
+
|
|
59
61
|
**HTML setup:** add a `data-entrypoint` attribute to the script tag that loads your bundle. The dev server will replace it with your `.imba` entrypoint and inject the importmap above it:
|
|
60
62
|
|
|
61
63
|
```html
|
package/package.json
CHANGED
package/serve.js
CHANGED
|
@@ -113,8 +113,6 @@ const hmrClient = `
|
|
|
113
113
|
_collector = prev;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
console.log('[bimba HMR]', file, 'slots=' + slots, 'tags:', collected.join(', '));
|
|
117
|
-
|
|
118
116
|
// Sync _ns_ (CSS namespace) from the new classes. imba_defineTag sets
|
|
119
117
|
// _ns_ on NewClass.prototype AFTER register$ calls customElements.define,
|
|
120
118
|
// so _patchClass missed it. Now that import is done, all _ns_ values are set.
|
|
@@ -139,59 +137,31 @@ const hmrClient = `
|
|
|
139
137
|
}
|
|
140
138
|
|
|
141
139
|
// Destructive HMR: wipe inner DOM and re-render each collected tag.
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
for (const tag of collected) {
|
|
148
|
-
const els = document.querySelectorAll(tag);
|
|
149
|
-
console.log('[bimba HMR] destructive:', tag, 'instances:', els.length);
|
|
150
|
-
els.forEach(el => {
|
|
151
|
-
const state = {};
|
|
152
|
-
for (const k of Object.keys(el)) state[k] = el[k];
|
|
153
|
-
_disconnectDescendants(el);
|
|
154
|
-
for (const sym of Object.getOwnPropertySymbols(el)) {
|
|
155
|
-
if (Symbol.keyFor(sym) !== undefined) continue;
|
|
156
|
-
try { delete el[sym]; } catch(_) {}
|
|
157
|
-
}
|
|
158
|
-
el.innerHTML = '';
|
|
159
|
-
Object.assign(el, state);
|
|
160
|
-
try { el.render && el.render(); } catch(e) { console.error('[bimba] render error:', e); }
|
|
161
|
-
try { el.connectedCallback && el.connectedCallback(); } catch(_) {}
|
|
162
|
-
try { el.mount && el.mount(); } catch(_) {}
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
for (const tag of collected) {
|
|
167
|
-
const els = document.querySelectorAll(tag);
|
|
168
|
-
console.log('[bimba HMR] stable:', tag, 'instances:', els.length);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Snapshot DOM before commit to detect duplication
|
|
173
|
-
const _domBefore = {};
|
|
140
|
+
// Always destructive regardless of slots value. Imba's reconciliation
|
|
141
|
+
// uses slot-tracking symbols (this[$sym] === 1) to skip re-creating
|
|
142
|
+
// elements on re-render. Even "stable" edits (static text, attributes)
|
|
143
|
+
// won't apply unless we clear those symbols and force a fresh render.
|
|
144
|
+
// _patchClass already ran above, so the new render() method is in place.
|
|
174
145
|
for (const tag of collected) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
146
|
+
const els = document.querySelectorAll(tag);
|
|
147
|
+
els.forEach(el => {
|
|
148
|
+
const state = {};
|
|
149
|
+
for (const k of Object.keys(el)) state[k] = el[k];
|
|
150
|
+
_disconnectDescendants(el);
|
|
151
|
+
for (const sym of Object.getOwnPropertySymbols(el)) {
|
|
152
|
+
if (Symbol.keyFor(sym) !== undefined) continue;
|
|
153
|
+
try { delete el[sym]; } catch(_) {}
|
|
154
|
+
}
|
|
155
|
+
el.innerHTML = '';
|
|
156
|
+
Object.assign(el, state);
|
|
157
|
+
try { el.render && el.render(); } catch(e) { console.error('[bimba] render error:', e); }
|
|
158
|
+
try { el.connectedCallback && el.connectedCallback(); } catch(_) {}
|
|
159
|
+
try { el.mount && el.mount(); } catch(_) {}
|
|
160
|
+
});
|
|
179
161
|
}
|
|
180
162
|
|
|
181
163
|
if (typeof imba !== 'undefined') imba.commit();
|
|
182
164
|
|
|
183
|
-
// Check DOM after commit (delayed to let rAF fire)
|
|
184
|
-
requestAnimationFrame(() => {
|
|
185
|
-
for (const tag of collected) {
|
|
186
|
-
const after = document.querySelectorAll(tag).length;
|
|
187
|
-
const before = _domBefore[tag]?.count || 0;
|
|
188
|
-
if (after !== before) {
|
|
189
|
-
console.warn('[bimba HMR] DUPLICATION:', tag, before, '->', after);
|
|
190
|
-
}
|
|
191
|
-
console.log('[bimba HMR] after commit:', tag, 'count:', after, 'body children:', document.body.children.length);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
165
|
// Patch className on ALL custom elements: replace old CSS namespace
|
|
196
166
|
// hashes with new ones. Must be global because subclass elements
|
|
197
167
|
// (e.g. panel-agent < basic-panel) inherit the parent's _ns_ hash
|
|
@@ -398,7 +368,7 @@ async function compileFile(filepath) {
|
|
|
398
368
|
|
|
399
369
|
const errors = result.errors || []
|
|
400
370
|
if (!errors.length && result.js) {
|
|
401
|
-
const { js, slotCount } = stabilizeSymbols(result.js,
|
|
371
|
+
const { js, slotCount } = stabilizeSymbols(result.js, abs)
|
|
402
372
|
result.js = js
|
|
403
373
|
const prev = _prevSlots.get(abs)
|
|
404
374
|
result.slots = (prev === undefined || prev === slotCount) ? 'stable' : 'shifted'
|