@pyreon/vite-plugin 0.18.0 → 0.19.0

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.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"9860678c-1"}]}],"isRoot":true},"nodeParts":{"9860678c-1":{"renderedLength":29090,"gzipLength":9553,"brotliLength":0,"metaUid":"9860678c-0"}},"nodeMetas":{"9860678c-0":{"id":"/src/index.ts","moduleParts":{"index.js":"9860678c-1"},"imported":[{"uid":"9860678c-2"},{"uid":"9860678c-3"},{"uid":"9860678c-4"}],"importedBy":[],"isEntry":true},"9860678c-2":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"9860678c-0"}]},"9860678c-3":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"9860678c-0"}]},"9860678c-4":{"id":"@pyreon/compiler","moduleParts":{},"imported":[],"importedBy":[{"uid":"9860678c-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"88d6c780-1"}]}],"isRoot":true},"nodeParts":{"88d6c780-1":{"renderedLength":29326,"gzipLength":9623,"brotliLength":0,"metaUid":"88d6c780-0"}},"nodeMetas":{"88d6c780-0":{"id":"/src/index.ts","moduleParts":{"index.js":"88d6c780-1"},"imported":[{"uid":"88d6c780-2"},{"uid":"88d6c780-3"},{"uid":"88d6c780-4"}],"importedBy":[],"isEntry":true},"88d6c780-2":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"88d6c780-0"}]},"88d6c780-3":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"88d6c780-0"}]},"88d6c780-4":{"id":"@pyreon/compiler","moduleParts":{},"imported":[],"importedBy":[{"uid":"88d6c780-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -443,7 +443,11 @@ function injectHmr(code, moduleId) {
443
443
  if (hasSignals) lines.push(`import { __hmr_signal, __hmr_dispose } from "${HMR_RUNTIME_IMPORT}";`);
444
444
  lines.push(`if (import.meta.hot) {`);
445
445
  if (hasSignals) lines.push(` import.meta.hot.dispose(() => __hmr_dispose(${escapedId}));`);
446
- lines.push(` import.meta.hot.accept();`);
446
+ lines.push(` import.meta.hot.accept((__m) => {`);
447
+ lines.push(` const __s = globalThis.__pyreon_hmr_swap__;`);
448
+ lines.push(` if (typeof __s === "function" && __m && __s(${escapedId}, __m)) return;`);
449
+ lines.push(` import.meta.hot.invalidate();`);
450
+ lines.push(` });`);
447
451
  lines.push(`}`);
448
452
  output = `${output}\n\n${lines.join("\n")}\n`;
449
453
  return output;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/vite-plugin",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Vite plugin for Pyreon — .pyreon SFC support, HMR, compiler integration",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/vite-plugin#readme",
6
6
  "bugs": {
@@ -43,7 +43,7 @@
43
43
  "prepublishOnly": "bun run build"
44
44
  },
45
45
  "dependencies": {
46
- "@pyreon/compiler": "^0.18.0"
46
+ "@pyreon/compiler": "^0.19.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "vite": "^8.0.0"
package/src/index.ts CHANGED
@@ -745,7 +745,40 @@ function injectHmr(code: string, moduleId: string): string {
745
745
  lines.push(` import.meta.hot.dispose(() => __hmr_dispose(${escapedId}));`)
746
746
  }
747
747
 
748
- lines.push(` import.meta.hot.accept();`)
748
+ // Self-accept the module, then drive Pyreon's HMR coordinator.
749
+ //
750
+ // The OLD code emitted a bare `import.meta.hot.accept()` (no callback):
751
+ // Vite re-evaluated the module but NOTHING re-rendered the mounted tree,
752
+ // AND the self-accept suppressed Vite's full-reload fallback — so a
753
+ // component/JSX edit produced a silently-stale UI until a MANUAL refresh.
754
+ //
755
+ // Now: the accept callback hands the FRESH module namespace Vite already
756
+ // re-evaluated straight to `globalThis.__pyreon_hmr_swap__` (registered
757
+ // by `@pyreon/router` in a dev browser — zero import coupling, same
758
+ // pattern as the perf-harness counter sink), keyed by THIS module's id.
759
+ // The coordinator finds every active matched route record whose lazy
760
+ // `_hmrId` matches and swaps in the new component, re-rendering ONLY
761
+ // that subtree IN PLACE (no page reload → `__pyreon_hmr_registry__`
762
+ // survives → `__hmr_signal` restores module-scope signal values).
763
+ //
764
+ // Using the namespace Vite passes (not a re-run of the lazy thunk)
765
+ // sidesteps the stale-`?t=` trap: the dynamic-import thunk lives in the
766
+ // virtual routes module, which is NOT invalidated when this leaf route
767
+ // self-accepts — re-importing it would return the OLD module.
768
+ //
769
+ // `__pyreon_hmr_swap__` returns falsy when the edit was outside the
770
+ // active route tree (nested non-route component, unrelated route,
771
+ // signal-only module) OR no coordinator is registered (plain
772
+ // `@pyreon/runtime-dom` app, or module loaded before any router
773
+ // mounted). Then `import.meta.hot.invalidate()` → Vite propagates → an
774
+ // AUTOMATIC full reload. Either way the user never refreshes by hand.
775
+ lines.push(` import.meta.hot.accept((__m) => {`)
776
+ lines.push(` const __s = globalThis.__pyreon_hmr_swap__;`)
777
+ lines.push(
778
+ ` if (typeof __s === "function" && __m && __s(${escapedId}, __m)) return;`,
779
+ )
780
+ lines.push(` import.meta.hot.invalidate();`)
781
+ lines.push(` });`)
749
782
  lines.push(`}`)
750
783
 
751
784
  output = `${output}\n\n${lines.join('\n')}\n`
@@ -58,7 +58,7 @@ async function transform(plugin: ReturnType<typeof pyreonPlugin>, code: string,
58
58
  // ─── HMR injection ──────────────────────────────────────────────────────────
59
59
 
60
60
  describe('HMR injection', () => {
61
- it('injects HMR accept for modules with component exports', async () => {
61
+ it('injects a coordinator-driven HMR accept for modules with component exports', async () => {
62
62
  const plugin = createPlugin()
63
63
  const code = `
64
64
  import { h } from "@pyreon/core"
@@ -66,7 +66,18 @@ export function App() { return h("div", null, "hello") }
66
66
  `
67
67
  const result = await transform(plugin, code, '/src/App.tsx')
68
68
  expect(result).toBeDefined()
69
- expect(result!.code).toContain('import.meta.hot.accept()')
69
+ // Self-accept WITH a callback receiving the fresh module (the bare
70
+ // `accept()` was the bug — it suppressed Vite's reload fallback while
71
+ // re-rendering nothing).
72
+ expect(result!.code).toContain('import.meta.hot.accept((__m) => {')
73
+ expect(result!.code).not.toContain('import.meta.hot.accept();')
74
+ // Hands the fresh module to the router-registered HMR coordinator,
75
+ // keyed by THIS module's id (zero import coupling).
76
+ expect(result!.code).toContain('globalThis.__pyreon_hmr_swap__')
77
+ expect(result!.code).toContain('__s("/src/App.tsx", __m)')
78
+ // Falls back to an automatic full reload when the edit was outside the
79
+ // active route tree or no coordinator is registered.
80
+ expect(result!.code).toContain('import.meta.hot.invalidate()')
70
81
  })
71
82
 
72
83
  it('injects HMR for exported const components', async () => {