bimba-cli 0.5.11 → 0.5.12
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/package.json +1 -1
- package/serve.js +82 -4
package/package.json
CHANGED
package/serve.js
CHANGED
|
@@ -293,6 +293,68 @@ const _compileCache = new Map() // filepath → { mtime, result }
|
|
|
293
293
|
const _prevJs = new Map() // filepath → compiled js — for change detection
|
|
294
294
|
const _prevSlots = new Map() // filepath → previous symbol slot count
|
|
295
295
|
|
|
296
|
+
// ─── Import dependency graph ──────────────────────────────────────────────────
|
|
297
|
+
//
|
|
298
|
+
// When a non-tag module (utility functions, constants, shared state) is edited,
|
|
299
|
+
// the existing class-prototype patching does nothing for the modules that
|
|
300
|
+
// imported it — they hold their own captured references. To make those
|
|
301
|
+
// updates flow into the UI, we track who imports whom and, on every change,
|
|
302
|
+
// re-broadcast updates for the transitive importer set. The client's existing
|
|
303
|
+
// HMR queue then re-imports each in turn; their top-level code reruns, picks
|
|
304
|
+
// up the new symbols, and any tag re-registrations patch instances in place.
|
|
305
|
+
//
|
|
306
|
+
// Keys are absolute, normalized paths (path.resolve). Edges are added during
|
|
307
|
+
// compilation by scanning the produced JS for relative .imba imports.
|
|
308
|
+
|
|
309
|
+
const _imports = new Map() // absFile → Set<absFile> (what it imports)
|
|
310
|
+
const _importers = new Map() // absFile → Set<absFile> (who imports it)
|
|
311
|
+
|
|
312
|
+
function extractImports(js, fromAbs) {
|
|
313
|
+
const dir = path.dirname(fromAbs)
|
|
314
|
+
const out = new Set()
|
|
315
|
+
const re = /(?:^|[\s;])(?:import|from)\s*['"]([^'"]+)['"]/g
|
|
316
|
+
let m
|
|
317
|
+
while ((m = re.exec(js))) {
|
|
318
|
+
const spec = m[1]
|
|
319
|
+
if (!spec.startsWith('.') && !spec.startsWith('/')) continue
|
|
320
|
+
if (!spec.endsWith('.imba')) continue
|
|
321
|
+
const resolved = spec.startsWith('/')
|
|
322
|
+
? path.resolve('.' + spec)
|
|
323
|
+
: path.resolve(dir, spec)
|
|
324
|
+
out.add(resolved)
|
|
325
|
+
}
|
|
326
|
+
return out
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function updateImportGraph(fromAbs, newDeps) {
|
|
330
|
+
const old = _imports.get(fromAbs)
|
|
331
|
+
if (old) {
|
|
332
|
+
for (const d of old) {
|
|
333
|
+
if (newDeps.has(d)) continue
|
|
334
|
+
const set = _importers.get(d)
|
|
335
|
+
if (set) { set.delete(fromAbs); if (!set.size) _importers.delete(d) }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (const d of newDeps) {
|
|
339
|
+
let set = _importers.get(d)
|
|
340
|
+
if (!set) { set = new Set(); _importers.set(d, set) }
|
|
341
|
+
set.add(fromAbs)
|
|
342
|
+
}
|
|
343
|
+
_imports.set(fromAbs, newDeps)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function transitiveImporters(absFile) {
|
|
347
|
+
const out = new Set()
|
|
348
|
+
const stack = [absFile]
|
|
349
|
+
while (stack.length) {
|
|
350
|
+
const cur = stack.pop()
|
|
351
|
+
const ups = _importers.get(cur)
|
|
352
|
+
if (!ups) continue
|
|
353
|
+
for (const u of ups) if (!out.has(u)) { out.add(u); stack.push(u) }
|
|
354
|
+
}
|
|
355
|
+
return out
|
|
356
|
+
}
|
|
357
|
+
|
|
296
358
|
// Imba compiles tag render-cache slots as anonymous local Symbols at module top
|
|
297
359
|
// level: `var $4 = Symbol(), $11 = Symbol(), ...; let c$0 = Symbol();`. Each
|
|
298
360
|
// re-import of the file creates fresh Symbol objects, so old slot data on live
|
|
@@ -324,11 +386,12 @@ function stabilizeSymbols(js, filepath) {
|
|
|
324
386
|
}
|
|
325
387
|
|
|
326
388
|
async function compileFile(filepath) {
|
|
389
|
+
const abs = path.resolve(filepath)
|
|
327
390
|
const file = Bun.file(filepath)
|
|
328
391
|
const stat = await file.stat()
|
|
329
392
|
const mtime = stat.mtime.getTime()
|
|
330
393
|
|
|
331
|
-
const cached = _compileCache.get(
|
|
394
|
+
const cached = _compileCache.get(abs)
|
|
332
395
|
if (cached && cached.mtime === mtime) return { ...cached.result, changeType: 'cached', slots: cached.slots }
|
|
333
396
|
|
|
334
397
|
const code = await file.text()
|
|
@@ -344,11 +407,12 @@ async function compileFile(filepath) {
|
|
|
344
407
|
const prev = _prevSlots.get(filepath)
|
|
345
408
|
result.slots = (prev === undefined || prev === slotCount) ? 'stable' : 'shifted'
|
|
346
409
|
_prevSlots.set(filepath, slotCount)
|
|
410
|
+
updateImportGraph(abs, extractImports(js, abs))
|
|
347
411
|
}
|
|
348
412
|
|
|
349
|
-
const changeType = _prevJs.get(
|
|
350
|
-
_prevJs.set(
|
|
351
|
-
_compileCache.set(
|
|
413
|
+
const changeType = _prevJs.get(abs) === result.js ? 'none' : 'full'
|
|
414
|
+
_prevJs.set(abs, result.js)
|
|
415
|
+
_compileCache.set(abs, { mtime, result, slots: result.slots })
|
|
352
416
|
return { ...result, changeType }
|
|
353
417
|
}
|
|
354
418
|
|
|
@@ -505,6 +569,20 @@ export function serve(entrypoint, flags) {
|
|
|
505
569
|
printStatus(rel, 'ok')
|
|
506
570
|
broadcast({ type: 'clear-error' })
|
|
507
571
|
broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
|
|
572
|
+
|
|
573
|
+
// Cascade: re-import every module transitively importing this file.
|
|
574
|
+
// They don't need recompilation (their source didn't change), but
|
|
575
|
+
// their captured references to the changed module are stale, so we
|
|
576
|
+
// tell the client to re-import them. The client's HMR queue
|
|
577
|
+
// processes these in order; tag classes get re-patched, plain
|
|
578
|
+
// utility modules get fresh top-level state.
|
|
579
|
+
const ups = transitiveImporters(path.resolve(filepath))
|
|
580
|
+
for (const upAbs of ups) {
|
|
581
|
+
const upRel = path.relative('.', upAbs).replaceAll('\\', '/')
|
|
582
|
+
const cached = _compileCache.get(upAbs)
|
|
583
|
+
const slots = cached?.slots || 'shifted'
|
|
584
|
+
broadcast({ type: 'update', file: upRel, slots })
|
|
585
|
+
}
|
|
508
586
|
} catch(e) {
|
|
509
587
|
printStatus(rel, 'fail', [{ message: e.message }])
|
|
510
588
|
broadcast({ type: 'error', file: rel, errors: [{ message: e.message, snippet: e.stack || e.message }] })
|