@absolutejs/absolute 0.16.10 → 0.16.11-beta.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/.absolutejs/prettier.cache.json +99 -98
- package/.absolutejs/tsconfig.tsbuildinfo +1 -1
- package/.claude/settings.local.json +11 -7
- package/dist/index.js +8 -5
- package/dist/index.js.map +5 -5
- package/dist/react/index.js +4 -2
- package/dist/react/index.js.map +3 -3
- package/hmr-react-macos-fix.md +113 -0
- package/package.json +2 -1
package/dist/react/index.js
CHANGED
|
@@ -71,9 +71,11 @@ var handleReactPageRequest = async (PageComponent, index, ...props) => {
|
|
|
71
71
|
const { createElement } = await import("react");
|
|
72
72
|
const { renderToReadableStream } = await import("react-dom/server");
|
|
73
73
|
const element = maybeProps !== undefined ? createElement(PageComponent, maybeProps) : createElement(PageComponent);
|
|
74
|
+
const refreshStubs = "window.$RefreshReg$=function(){};window.$RefreshSig$=function(){return function(t){return t}};";
|
|
75
|
+
const propsScript = maybeProps ? `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps)}` : "";
|
|
74
76
|
const stream = await renderToReadableStream(element, {
|
|
75
77
|
bootstrapModules: [index],
|
|
76
|
-
bootstrapScriptContent:
|
|
78
|
+
bootstrapScriptContent: refreshStubs + propsScript || undefined,
|
|
77
79
|
onError(error) {
|
|
78
80
|
console.error("[SSR] React streaming error:", error);
|
|
79
81
|
}
|
|
@@ -93,5 +95,5 @@ export {
|
|
|
93
95
|
handleReactPageRequest
|
|
94
96
|
};
|
|
95
97
|
|
|
96
|
-
//# debugId=
|
|
98
|
+
//# debugId=9A0CEFFBBBC3DE3864756E2164756E21
|
|
97
99
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"sources": ["../src/utils/ssrErrorPage.ts", "../src/react/pageHandler.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"export const ssrErrorPage = (framework: string, error: unknown): string => {\n\tconst frameworkColors: Record<string, string> = {\n\t\tangular: '#dd0031',\n\t\thtml: '#e34c26',\n\t\thtmx: '#1a365d',\n\t\treact: '#61dafb',\n\t\tsvelte: '#ff3e00',\n\t\tvue: '#42b883'\n\t};\n\n\tconst accent = frameworkColors[framework] ?? '#94a3b8';\n\tconst label = framework.charAt(0).toUpperCase() + framework.slice(1);\n\tconst message = error instanceof Error ? error.message : String(error);\n\n\treturn `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>SSR Error - AbsoluteJS</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{min-height:100vh;background:linear-gradient(135deg,rgba(15,23,42,0.98) 0%,rgba(30,41,59,0.98) 100%);color:#e2e8f0;font-family:\"JetBrains Mono\",\"Fira Code\",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:14px;line-height:1.6;display:flex;align-items:flex-start;justify-content:center;padding:32px}\n.card{max-width:720px;width:100%;background:rgba(30,41,59,0.6);border:1px solid rgba(71,85,105,0.5);border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5),0 0 0 1px rgba(255,255,255,0.05);overflow:hidden}\n.header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 24px;background:rgba(15,23,42,0.5);border-bottom:1px solid rgba(71,85,105,0.4)}\n.brand{font-weight:700;font-size:20px;color:#fff;letter-spacing:-0.02em}\n.badge{padding:5px 10px;border-radius:8px;font-size:12px;font-weight:600;background:${accent};color:#fff;opacity:0.95;box-shadow:0 2px 4px rgba(0,0,0,0.2)}\n.kind{color:#94a3b8;font-size:13px;font-weight:500}\n.content{padding:24px}\n.label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin-bottom:8px}\n.message{margin:0;padding:16px 20px;background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.25);border-radius:10px;overflow-x:auto;white-space:pre-wrap;word-break:break-word;color:#fca5a5;font-size:13px;line-height:1.5}\n.hint{margin-top:20px;padding:12px 20px;background:rgba(71,85,105,0.3);border-radius:10px;border:1px solid rgba(71,85,105,0.4);color:#cbd5e1;font-size:13px}\n</style>\n</head>\n<body>\n<div class=\"card\">\n<div class=\"header\">\n<div style=\"display:flex;align-items:center;gap:12px\">\n<span class=\"brand\">AbsoluteJS</span>\n<span class=\"badge\">${label}</span>\n</div>\n<span class=\"kind\">Server Render Error</span>\n</div>\n<div class=\"content\">\n<div class=\"label\">What went wrong</div>\n<pre class=\"message\">${message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</pre>\n<div class=\"hint\">A component threw during server-side rendering. Check the terminal for the full stack trace.</div>\n</div>\n</div>\n</body>\n</html>`;\n};\n",
|
|
6
|
-
"import type { ComponentType as ReactComponent } from 'react';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\n\nexport const handleReactPageRequest = async <\n\tProps extends Record<string, unknown> = Record<never, never>\n>(\n\tPageComponent: ReactComponent<Props>,\n\tindex: string,\n\t...props: keyof Props extends never ? [] : [props: Props]\n) => {\n\ttry {\n\t\tconst [maybeProps] = props;\n\t\tconst { createElement } = await import('react');\n\t\tconst { renderToReadableStream } = await import('react-dom/server');\n\n\t\tconst element =\n\t\t\tmaybeProps !== undefined\n\t\t\t\t? createElement(PageComponent, maybeProps)\n\t\t\t\t: createElement(PageComponent);\n\n\t\
|
|
6
|
+
"import type { ComponentType as ReactComponent } from 'react';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\n\nexport const handleReactPageRequest = async <\n\tProps extends Record<string, unknown> = Record<never, never>\n>(\n\tPageComponent: ReactComponent<Props>,\n\tindex: string,\n\t...props: keyof Props extends never ? [] : [props: Props]\n) => {\n\ttry {\n\t\tconst [maybeProps] = props;\n\t\tconst { createElement } = await import('react');\n\t\tconst { renderToReadableStream } = await import('react-dom/server');\n\n\t\tconst element =\n\t\t\tmaybeProps !== undefined\n\t\t\t\t? createElement(PageComponent, maybeProps)\n\t\t\t\t: createElement(PageComponent);\n\n\t\t// In dev mode, Bun's reactFastRefresh injects $RefreshReg$/$RefreshSig$\n\t\t// calls into component code. With code splitting, shared component chunks\n\t\t// may load before the chunk containing reactRefreshSetup.ts — causing a\n\t\t// ReferenceError. These no-op stubs ensure the globals exist before any\n\t\t// module code runs. reactRefreshSetup.ts overwrites them with the real\n\t\t// implementations once its chunk executes.\n\t\tconst refreshStubs =\n\t\t\tprocess.env.NODE_ENV === 'development'\n\t\t\t\t? 'window.$RefreshReg$=function(){};window.$RefreshSig$=function(){return function(t){return t}};'\n\t\t\t\t: '';\n\t\tconst propsScript = maybeProps\n\t\t\t? `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps)}`\n\t\t\t: '';\n\n\t\tconst stream = await renderToReadableStream(element, {\n\t\t\tbootstrapModules: [index],\n\t\t\tbootstrapScriptContent: refreshStubs + propsScript || undefined,\n\t\t\tonError(error: unknown) {\n\t\t\t\tconsole.error('[SSR] React streaming error:', error);\n\t\t\t}\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] React render error:', error);\n\n\t\treturn new Response(ssrErrorPage('react', error), {\n\t\t\tstatus: 500,\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t}\n};\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";;;;;;;;;;;;;;;IAAa,eAAe,CAAC,WAAmB,UAA2B;AAAA,EAC1E,MAAM,kBAA0C;AAAA,IAC/C,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,KAAK;AAAA,EACN;AAAA,EAEA,MAAM,SAAS,gBAAgB,cAAc;AAAA,EAC7C,MAAM,QAAQ,UAAU,OAAO,CAAC,EAAE,YAAY,IAAI,UAAU,MAAM,CAAC;AAAA,EACnE,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,EAErE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sFAY8E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAahE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAMC,QAAQ,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC1CzF,IAAM,yBAAyB,OAGrC,eACA,UACG,UACC;AAAA,EACJ,IAAI;AAAA,IACH,OAAO,cAAc;AAAA,IACrB,QAAQ,kBAAkB,MAAa;AAAA,IACvC,QAAQ,2BAA2B,MAAa;AAAA,IAEhD,MAAM,UACL,eAAe,YACZ,cAAc,eAAe,UAAU,IACvC,cAAc,aAAa;AAAA,
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": ";;;;;;;;;;;;;;;IAAa,eAAe,CAAC,WAAmB,UAA2B;AAAA,EAC1E,MAAM,kBAA0C;AAAA,IAC/C,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,KAAK;AAAA,EACN;AAAA,EAEA,MAAM,SAAS,gBAAgB,cAAc;AAAA,EAC7C,MAAM,QAAQ,UAAU,OAAO,CAAC,EAAE,YAAY,IAAI,UAAU,MAAM,CAAC;AAAA,EACnE,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,EAErE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sFAY8E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAahE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAMC,QAAQ,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC1CzF,IAAM,yBAAyB,OAGrC,eACA,UACG,UACC;AAAA,EACJ,IAAI;AAAA,IACH,OAAO,cAAc;AAAA,IACrB,QAAQ,kBAAkB,MAAa;AAAA,IACvC,QAAQ,2BAA2B,MAAa;AAAA,IAEhD,MAAM,UACL,eAAe,YACZ,cAAc,eAAe,UAAU,IACvC,cAAc,aAAa;AAAA,IAQ/B,MAAM,eAEF;AAAA,IAEJ,MAAM,cAAc,aACjB,4BAA4B,KAAK,UAAU,UAAU,MACrD;AAAA,IAEH,MAAM,SAAS,MAAM,uBAAuB,SAAS;AAAA,MACpD,kBAAkB,CAAC,KAAK;AAAA,MACxB,wBAAwB,eAAe,eAAe;AAAA,MACtD,OAAO,CAAC,OAAgB;AAAA,QACvB,QAAQ,MAAM,gCAAgC,KAAK;AAAA;AAAA,IAErD,CAAC;AAAA,IAED,OAAO,IAAI,SAAS,QAAQ;AAAA,MAC3B,SAAS,EAAE,gBAAgB,YAAY;AAAA,IACxC,CAAC;AAAA,IACA,OAAO,OAAO;AAAA,IACf,QAAQ,MAAM,6BAA6B,KAAK;AAAA,IAEhD,OAAO,IAAI,SAAS,aAAa,SAAS,KAAK,GAAG;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,YAAY;AAAA,IACxC,CAAC;AAAA;AAAA;",
|
|
9
|
+
"debugId": "9A0CEFFBBBC3DE3864756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Fix: React HMR on macOS
|
|
2
|
+
|
|
3
|
+
React HMR updates were detected and logged on macOS (`[hmr] hmr update ...`), but changes never appeared in the browser. CSS hot-swaps worked fine. HTML, Svelte, Vue, and HTMX HMR were unaffected. Two issues were identified and fixed.
|
|
4
|
+
|
|
5
|
+
## Files Changed
|
|
6
|
+
|
|
7
|
+
| File | Change |
|
|
8
|
+
|------|--------|
|
|
9
|
+
| `src/dev/rebuildTrigger.ts` | Moved `populateAssetStore` + `cleanStaleAssets` before broadcasts |
|
|
10
|
+
| `src/build/generateReactIndexes.ts` | Added `reactRefreshSetup` import to `_refresh.tsx` shared chunk seed |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Issue 1: Asset Store Race Condition
|
|
15
|
+
|
|
16
|
+
**File:** `src/dev/rebuildTrigger.ts`
|
|
17
|
+
|
|
18
|
+
### Problem
|
|
19
|
+
|
|
20
|
+
After a rebuild, the server broadcast the `react-update` WebSocket message to the browser *before* `populateAssetStore()` loaded the new bundles into the in-memory asset store. The client received the message, immediately called `import()` on the new bundle URL, and hit the server — which couldn't serve the file yet.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Build completes → broadcast (react-update) → client import() → 404/stale ← populateAssetStore (too late)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Why HTML/Svelte/Vue/HTMX HMR were unaffected
|
|
27
|
+
|
|
28
|
+
Those frameworks send the rendered HTML *inline* in the WebSocket message. The browser applies it via DOM manipulation — no follow-up HTTP request needed.
|
|
29
|
+
|
|
30
|
+
### Why macOS specifically
|
|
31
|
+
|
|
32
|
+
macOS FSEvents and I/O scheduling produce a wider timing window between the broadcast and the `import()` arrival than Linux's inotify. On Linux the asset store was usually populated before the request landed; on macOS it consistently wasn't.
|
|
33
|
+
|
|
34
|
+
### Fix
|
|
35
|
+
|
|
36
|
+
Moved both calls to *before* the first `broadcastToClients`:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// After build succeeds and manifest is validated:
|
|
40
|
+
await populateAssetStore(state.assetStore, manifest, state.resolvedPaths.buildDir);
|
|
41
|
+
await cleanStaleAssets(state.assetStore, manifest, state.resolvedPaths.buildDir);
|
|
42
|
+
|
|
43
|
+
// Now safe to tell clients about the update:
|
|
44
|
+
broadcastToClients(state, { ... });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The previous comment warned this could trigger a Bun `--hot` restart before broadcasts. That concern was unfounded — `populateAssetStore` only reads files into a `Map` and does not modify source modules. The build (which writes to disk) has already finished, so any `--hot` restart is already queued regardless.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Issue 2: React Fast Refresh Initialization Order
|
|
52
|
+
|
|
53
|
+
**File:** `src/build/generateReactIndexes.ts`
|
|
54
|
+
|
|
55
|
+
### Problem
|
|
56
|
+
|
|
57
|
+
Even with the asset store fix, `performReactRefresh()` silently did nothing. The new bundle was fetched and executed, but React Fast Refresh couldn't reach React's reconciler to swap the components.
|
|
58
|
+
|
|
59
|
+
React Fast Refresh requires `react-refresh/runtime` to call `injectIntoGlobalHook(window)` **before** React initializes. This patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` so React registers its internals with the Refresh Runtime during initialization. If React initializes first, it never connects — and `performReactRefresh()` becomes a no-op.
|
|
60
|
+
|
|
61
|
+
The build produces two kinds of client entry points:
|
|
62
|
+
|
|
63
|
+
- **`_refresh.tsx`** (shared chunk seed) — only imported `react` and `react-dom/client`
|
|
64
|
+
- **Hydration indexes** (e.g., `ReactExample.tsx`) — imported `reactRefreshSetup`, then React
|
|
65
|
+
|
|
66
|
+
With `splitting: true`, Bun extracts common dependencies into a shared chunk. `react` and `react-dom/client` were common to both → shared chunk. But `reactRefreshSetup` was only in the hydration indexes → stayed in the per-entry chunk.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Shared chunk executes first:
|
|
70
|
+
react initializes → checks __REACT_DEVTOOLS_GLOBAL_HOOK__ → not patched yet → skips
|
|
71
|
+
|
|
72
|
+
Entry chunk executes second:
|
|
73
|
+
reactRefreshSetup runs → patches the hook → too late
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Why CSS updates still worked
|
|
77
|
+
|
|
78
|
+
CSS HMR uses `reloadReactCSS()` which swaps `<link>` stylesheet `href` attributes in the DOM. No `import()`, no React reconciler, no Fast Refresh involved.
|
|
79
|
+
|
|
80
|
+
### Why macOS was affected but Linux/Windows worked
|
|
81
|
+
|
|
82
|
+
The Bun bundler's chunk splitting heuristics and module ordering within chunks can vary based on file system glob ordering. macOS (case-insensitive APFS) and Linux (case-sensitive ext4) produce different glob orders, which can affect whether `reactRefreshSetup` ends up in the shared chunk or the entry chunk. On Linux it happened to land in the shared chunk; on macOS it consistently didn't.
|
|
83
|
+
|
|
84
|
+
### Fix
|
|
85
|
+
|
|
86
|
+
Added the `reactRefreshSetup` import to `_refresh.tsx` so it becomes a common dependency and lands in the shared chunk — *before* React:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Before:
|
|
90
|
+
import 'react';
|
|
91
|
+
import 'react-dom/client';
|
|
92
|
+
|
|
93
|
+
// After:
|
|
94
|
+
import '...reactRefreshSetup.ts'; // patches the global hook
|
|
95
|
+
import 'react'; // React initializes, sees the hook
|
|
96
|
+
import 'react-dom/client';
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Since `reactRefreshSetup` is now imported by both `_refresh.tsx` and every hydration index, Bun extracts it into the shared chunk alongside React. The import order within the chunk guarantees the hook is patched before React initializes.
|
|
100
|
+
|
|
101
|
+
`reactRefreshSetup` is idempotent (guarded by `if (!window.$RefreshRuntime$)`), so the additional import site is harmless. Component state is fully preserved across HMR updates via React Fast Refresh.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Verified Behavior
|
|
106
|
+
|
|
107
|
+
All tested on macOS after applying both fixes:
|
|
108
|
+
|
|
109
|
+
- Editing a React page component (`ReactExample.tsx`) — change appears live, state preserved
|
|
110
|
+
- Editing a shared component (`App.tsx`) — change appears live via dependency graph
|
|
111
|
+
- Editing a CSS file (`react-example.css`) — style updates without page reload
|
|
112
|
+
- HTML, Svelte, Vue, HTMX HMR — all continue to work as before
|
|
113
|
+
- `bun run typecheck` — passes with no errors
|
package/package.json
CHANGED
|
@@ -95,9 +95,10 @@
|
|
|
95
95
|
"format": "bun run src/cli/index.ts prettier --write",
|
|
96
96
|
"lint": "bun run src/cli/index.ts eslint",
|
|
97
97
|
"release": "bun run format && bun run build && bun publish",
|
|
98
|
+
"release:beta": "bun run format && bun run build && bun publish --tag beta",
|
|
98
99
|
"test": "bun test",
|
|
99
100
|
"typecheck": "bun run tsc --noEmit"
|
|
100
101
|
},
|
|
101
102
|
"types": "./dist/src/index.d.ts",
|
|
102
|
-
"version": "0.16.
|
|
103
|
+
"version": "0.16.11-beta.1"
|
|
103
104
|
}
|