alabjs-vite-plugin 0.2.6 → 0.4.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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +156 -18
- package/dist/index.js.map +1 -1
- package/dist/live-stub.d.ts +48 -0
- package/dist/live-stub.d.ts.map +1 -0
- package/dist/live-stub.js +171 -0
- package/dist/live-stub.js.map +1 -0
- package/dist/napi.d.ts +13 -0
- package/dist/napi.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +154 -18
- package/src/live-stub.ts +188 -0
- package/src/napi.ts +13 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AASnC,UAAU,iBAAiB;IACzB,iCAAiC;IACjC,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CACxB;AA8BD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,EAAE,CA0gBpE"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { parseErrorLocation, formatBoundaryError } from "./overlay.js";
|
|
2
2
|
import { devToolbarScript } from "./devtools.js";
|
|
3
|
+
import { generateLiveComponentStub, generateLiveClientRuntime, generateLiveServerWrapper } from "./live-stub.js";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { dirname } from "node:path";
|
|
6
7
|
const VIRTUAL_CLIENT_ID = "/@alabjs/client";
|
|
7
8
|
const VIRTUAL_REFRESH_ID = "/@react-refresh";
|
|
9
|
+
const VIRTUAL_LIVE_CLIENT_ID = "/@alabjs/live-client";
|
|
10
|
+
// In production builds the Rust compiler still emits jsxDEV calls (from
|
|
11
|
+
// react/jsx-dev-runtime), but React's production bundle exports jsxDEV=void 0.
|
|
12
|
+
// This virtual shim re-exports the production jsx/jsxs functions under the
|
|
13
|
+
// dev names so production builds work without changing the Rust compiler.
|
|
14
|
+
const VIRTUAL_JSX_DEV_SHIM_ID = "\0@alabjs/jsx-dev-shim";
|
|
8
15
|
// Resolve react-refresh from the plugin's own node_modules so consumers
|
|
9
16
|
// don't need to install it. We alias the package root (not runtime directly)
|
|
10
17
|
// so Vite's dep optimizer can pre-bundle it as proper ESM.
|
|
@@ -34,10 +41,15 @@ window.__vite_plugin_react_preamble_installed__ = true;
|
|
|
34
41
|
*/
|
|
35
42
|
export function alabPlugin(options = {}) {
|
|
36
43
|
let napi = null;
|
|
44
|
+
// true when running `vite serve` (dev), false during `vite build` (production).
|
|
45
|
+
let isDev = true;
|
|
37
46
|
const corePlugin = {
|
|
38
47
|
name: "alabjs",
|
|
39
48
|
enforce: "pre",
|
|
40
|
-
config
|
|
49
|
+
configResolved(config) {
|
|
50
|
+
isDev = config.command === "serve";
|
|
51
|
+
},
|
|
52
|
+
config(_cfg, env) {
|
|
41
53
|
return {
|
|
42
54
|
// ALAB_PUBLIC_* vars are inlined into the client bundle via import.meta.env.
|
|
43
55
|
// VITE_* is kept for backwards-compatibility with vanilla Vite projects.
|
|
@@ -47,7 +59,15 @@ export function alabPlugin(options = {}) {
|
|
|
47
59
|
// to install it. optimizeDeps.include ensures Vite pre-bundles it as
|
|
48
60
|
// proper ESM (CJS→ESM interop), giving us a valid `default` export.
|
|
49
61
|
resolve: {
|
|
50
|
-
alias: {
|
|
62
|
+
alias: {
|
|
63
|
+
"react-refresh": REACT_REFRESH_PKG_DIR,
|
|
64
|
+
// In production the Rust compiler still emits jsxDEV calls but React's
|
|
65
|
+
// production bundle exports jsxDEV=void 0. Map the import to a virtual
|
|
66
|
+
// shim that re-exports the production jsx/jsxs functions under the dev names.
|
|
67
|
+
...(env.command === "build"
|
|
68
|
+
? { "react/jsx-dev-runtime": VIRTUAL_JSX_DEV_SHIM_ID }
|
|
69
|
+
: {}),
|
|
70
|
+
},
|
|
51
71
|
},
|
|
52
72
|
optimizeDeps: {
|
|
53
73
|
include: ["react-refresh"],
|
|
@@ -70,9 +90,27 @@ export function alabPlugin(options = {}) {
|
|
|
70
90
|
return VIRTUAL_CLIENT_ID;
|
|
71
91
|
if (id === VIRTUAL_REFRESH_ID)
|
|
72
92
|
return VIRTUAL_REFRESH_ID;
|
|
93
|
+
if (id === VIRTUAL_LIVE_CLIENT_ID)
|
|
94
|
+
return VIRTUAL_LIVE_CLIENT_ID;
|
|
95
|
+
if (id === VIRTUAL_JSX_DEV_SHIM_ID)
|
|
96
|
+
return VIRTUAL_JSX_DEV_SHIM_ID;
|
|
97
|
+
// ?live-actual: used by the server-build live wrapper to import the real
|
|
98
|
+
// component without triggering another live transform (avoids infinite loop).
|
|
99
|
+
if (id.endsWith("?live-actual"))
|
|
100
|
+
return id;
|
|
73
101
|
return null;
|
|
74
102
|
},
|
|
75
|
-
load(id) {
|
|
103
|
+
async load(id) {
|
|
104
|
+
if (id === VIRTUAL_LIVE_CLIENT_ID) {
|
|
105
|
+
return generateLiveClientRuntime();
|
|
106
|
+
}
|
|
107
|
+
if (id.endsWith("?live-actual")) {
|
|
108
|
+
// Load the raw TypeScript source for the real live component.
|
|
109
|
+
// Vite's default transformer (esbuild) will compile it after this load hook.
|
|
110
|
+
const realPath = id.slice(0, -"?live-actual".length);
|
|
111
|
+
const { readFileSync } = await import("node:fs");
|
|
112
|
+
return readFileSync(realPath, "utf-8");
|
|
113
|
+
}
|
|
76
114
|
if (id === VIRTUAL_REFRESH_ID) {
|
|
77
115
|
// Re-export the react-refresh runtime so the preamble can import it.
|
|
78
116
|
// Use bare specifier so Vite's dep optimizer (pre-bundler) handles the
|
|
@@ -80,8 +118,57 @@ export function alabPlugin(options = {}) {
|
|
|
80
118
|
// the plugin's own copy, so consumers don't need it installed.
|
|
81
119
|
return `export { default } from "react-refresh/runtime";\n`;
|
|
82
120
|
}
|
|
121
|
+
if (id === VIRTUAL_JSX_DEV_SHIM_ID) {
|
|
122
|
+
// Production shim: the Rust compiler always emits jsxDEV/jsxsDEV calls
|
|
123
|
+
// (oxc_transformer uses the dev JSX transform). React's production build
|
|
124
|
+
// exports jsxDEV=void 0 from react/jsx-dev-runtime, causing a runtime error.
|
|
125
|
+
// Re-export the production jsx/jsxs functions under the dev names so
|
|
126
|
+
// production bundles render correctly.
|
|
127
|
+
return `export { jsx as jsxDEV, jsxs as jsxsDEV, Fragment } from "react/jsx-runtime";\n`;
|
|
128
|
+
}
|
|
83
129
|
if (id !== VIRTUAL_CLIENT_ID)
|
|
84
130
|
return null;
|
|
131
|
+
// Scan app/ for page, layout, and loading files.
|
|
132
|
+
// We generate statically-analyzable import() calls (no @vite-ignore) so
|
|
133
|
+
// Rolldown can create proper code-split chunks at build time and the
|
|
134
|
+
// browser can fetch them in production.
|
|
135
|
+
const cwd = process.cwd();
|
|
136
|
+
const appDir = cwd + "/app";
|
|
137
|
+
const { readdirSync } = await import("node:fs");
|
|
138
|
+
const { join: pathJoin } = await import("node:path");
|
|
139
|
+
function scanFiles(dir, names, base) {
|
|
140
|
+
const results = [];
|
|
141
|
+
let entries;
|
|
142
|
+
try {
|
|
143
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const full = pathJoin(dir, entry.name);
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
results.push(...scanFiles(full, names, base));
|
|
152
|
+
}
|
|
153
|
+
else if (names.includes(entry.name)) {
|
|
154
|
+
results.push(full.slice(base.length + 1).replace(/\\/g, "/"));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
const PAGE_NAMES = ["page.tsx", "page.ts", "page.jsx", "page.js"];
|
|
160
|
+
const LAYOUT_NAMES = ["layout.tsx", "layout.ts", "layout.jsx", "layout.js"];
|
|
161
|
+
const LOADING_NAMES = ["loading.tsx", "loading.ts", "loading.jsx", "loading.js"];
|
|
162
|
+
const pageFiles = scanFiles(appDir, PAGE_NAMES, cwd);
|
|
163
|
+
const layoutFiles = scanFiles(appDir, LAYOUT_NAMES, cwd);
|
|
164
|
+
const loadingFiles = scanFiles(appDir, LOADING_NAMES, cwd);
|
|
165
|
+
function makeEntry(f) {
|
|
166
|
+
// Static import() — Rolldown analyzes these and creates code-split chunks.
|
|
167
|
+
return ` ${JSON.stringify(f)}: () => import(${JSON.stringify("/" + f)})`;
|
|
168
|
+
}
|
|
169
|
+
const pagesMap = pageFiles.map(makeEntry).join(",\n");
|
|
170
|
+
const layoutsMap = layoutFiles.map(makeEntry).join(",\n");
|
|
171
|
+
const loadingMap = loadingFiles.map(makeEntry).join(",\n");
|
|
85
172
|
// This module is injected into every page as `<script type="module" src="/@alabjs/client">`.
|
|
86
173
|
// It reads the route metadata embedded in <meta> tags by the SSR renderer and
|
|
87
174
|
// hydrates (or mounts) the React page component on the client.
|
|
@@ -93,25 +180,47 @@ import { hydrateRoot, createRoot } from "react-dom/client";
|
|
|
93
180
|
import { AlabProvider } from "alabjs/client";
|
|
94
181
|
import { ErrorBoundary } from "alabjs/components";
|
|
95
182
|
|
|
183
|
+
// Statically-analyzable import maps — Rolldown creates code-split chunks from these.
|
|
184
|
+
// Keys are cwd-relative source paths matching the alabjs-route / alabjs-layouts meta values.
|
|
185
|
+
const PAGES = {
|
|
186
|
+
${pagesMap}
|
|
187
|
+
};
|
|
188
|
+
const LAYOUT_MODS = {
|
|
189
|
+
${layoutsMap}
|
|
190
|
+
};
|
|
191
|
+
const LOADING_MODS = {
|
|
192
|
+
${loadingMap}
|
|
193
|
+
};
|
|
194
|
+
|
|
96
195
|
const meta = (name) => document.querySelector(\`meta[name="\${name}"]\`)?.getAttribute("content") ?? "";
|
|
97
196
|
|
|
98
197
|
/** Load a page module, its layout modules, and optional loading fallback. */
|
|
99
198
|
async function buildApp(routeFile, layoutFiles, loadingFile, params, searchParams) {
|
|
100
|
-
|
|
199
|
+
// Normalize .js extension to .tsx for lookups (server meta may store compiled paths).
|
|
200
|
+
const norm = (p) => LAYOUT_MODS[p] ? p : p.replace(/\\.js$/, ".tsx");
|
|
201
|
+
|
|
202
|
+
const pageFn = PAGES[routeFile];
|
|
203
|
+
if (!pageFn) return null;
|
|
204
|
+
const mod = await pageFn();
|
|
101
205
|
const Page = mod.default;
|
|
102
206
|
if (!Page) return null;
|
|
103
207
|
|
|
104
|
-
const layoutMods = await Promise.all(
|
|
208
|
+
const layoutMods = await Promise.all(
|
|
209
|
+
layoutFiles.map(f => { const fn = LAYOUT_MODS[norm(f)]; return fn ? fn() : Promise.resolve({}); })
|
|
210
|
+
);
|
|
105
211
|
const layouts = layoutMods.map(m => m.default).filter(Boolean);
|
|
106
212
|
|
|
107
213
|
// Loading fallback: import loading.tsx if present
|
|
108
214
|
let loadingEl = null;
|
|
109
215
|
if (loadingFile) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
216
|
+
const lFn = LOADING_MODS[norm(loadingFile)];
|
|
217
|
+
if (lFn) {
|
|
218
|
+
try {
|
|
219
|
+
const lMod = await lFn();
|
|
220
|
+
const Loading = lMod.default;
|
|
221
|
+
if (Loading) loadingEl = createElement(Loading, {});
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
115
224
|
}
|
|
116
225
|
|
|
117
226
|
let el = createElement(Page, { params, searchParams });
|
|
@@ -306,8 +415,37 @@ if (import.meta.env.DEV) {
|
|
|
306
415
|
return null;
|
|
307
416
|
if (id.includes("node_modules"))
|
|
308
417
|
return null;
|
|
418
|
+
// ?live-actual IDs are loaded by generateLiveServerWrapper imports; let
|
|
419
|
+
// esbuild compile them normally without triggering another live transform.
|
|
420
|
+
if (id.includes("?live-actual"))
|
|
421
|
+
return null;
|
|
309
422
|
const isServerFile = /\.server\.(ts|tsx)$/.test(id);
|
|
310
423
|
const isClientBuild = !transformOptions?.ssr;
|
|
424
|
+
// ── Live component detection ─────────────────────────────────────────
|
|
425
|
+
// A file is a live component when it uses the *.live.tsx convention OR
|
|
426
|
+
// has a "use live" directive as its first statement.
|
|
427
|
+
const isLiveByConvention = /\.live\.(ts|tsx)$/.test(id);
|
|
428
|
+
let isLiveByDirective = false;
|
|
429
|
+
if (!isLiveByConvention && napi) {
|
|
430
|
+
const directiveJson = napi.detectDirective(code, id);
|
|
431
|
+
const directive = JSON.parse(directiveJson);
|
|
432
|
+
isLiveByDirective = directive.kind === "use_live";
|
|
433
|
+
}
|
|
434
|
+
const isLiveFile = isLiveByConvention || isLiveByDirective;
|
|
435
|
+
if (isLiveFile) {
|
|
436
|
+
// Derive a stable 16-char ID from the module path (reuses existing FNV-1a hash).
|
|
437
|
+
const moduleId = napi ? napi.hashBuildId(id) : id.replace(/[^a-z0-9]/gi, "_");
|
|
438
|
+
if (isClientBuild) {
|
|
439
|
+
// Client build: replace with LiveMount stub — server code never ships to browser.
|
|
440
|
+
return { code: generateLiveComponentStub(moduleId, ["default"]), map: null };
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
// Server build: wrap default export in a data-live-id div so SSR pages emit
|
|
444
|
+
// the same DOM structure as LiveMount, enabling React hydration without errors.
|
|
445
|
+
// The ?live-actual import loads the real component without re-triggering this path.
|
|
446
|
+
return { code: generateLiveServerWrapper(moduleId, id + "?live-actual"), map: null };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
311
449
|
// Skip Rust compiler for SSR transforms — the Rust compiler injects React
|
|
312
450
|
// Fast Refresh globals ($RefreshReg$) that don't exist in the SSR context.
|
|
313
451
|
// esbuild (Vite's default) handles SSR compilation correctly.
|
|
@@ -339,9 +477,9 @@ if (import.meta.env.DEV) {
|
|
|
339
477
|
// Compile TypeScript/TSX with the Rust compiler.
|
|
340
478
|
// Catch errors and attach source location so Vite's overlay shows
|
|
341
479
|
// the exact line/column instead of a raw stack trace.
|
|
342
|
-
const minify =
|
|
480
|
+
const minify = !isDev;
|
|
343
481
|
// Emit source maps in dev mode so browser devtools map to original TS/TSX.
|
|
344
|
-
const sourceMap =
|
|
482
|
+
const sourceMap = isDev;
|
|
345
483
|
let outputJson;
|
|
346
484
|
try {
|
|
347
485
|
outputJson = napi.compileSource(code, id, minify, sourceMap);
|
|
@@ -353,12 +491,12 @@ if (import.meta.env.DEV) {
|
|
|
353
491
|
}
|
|
354
492
|
const output = JSON.parse(outputJson);
|
|
355
493
|
let finalCode = output.code;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
494
|
+
if (isDev && /\.tsx$/.test(id)) {
|
|
495
|
+
// In dev mode, append the react-refresh HMR accept footer to TSX files.
|
|
496
|
+
// This tells Vite the module self-accepts so hot updates stay component-
|
|
497
|
+
// level instead of propagating to a full page reload.
|
|
498
|
+
// The $RefreshReg$ / $RefreshSig$ calls are already emitted by the Rust
|
|
499
|
+
// compiler (oxc_transformer::enable_all includes the react-refresh pass).
|
|
362
500
|
finalCode +=
|
|
363
501
|
`\nimport __RefreshRuntime__ from "${VIRTUAL_REFRESH_ID}";` +
|
|
364
502
|
`\nif (import.meta.hot) {` +
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAC5C,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live component stub generator.
|
|
3
|
+
*
|
|
4
|
+
* When the Vite plugin encounters a `"use live"` file (or a `*.live.tsx` file)
|
|
5
|
+
* during a CLIENT build, it replaces the entire module with a stub that:
|
|
6
|
+
*
|
|
7
|
+
* 1. Renders the server-provided initial HTML (already in the DOM via SSR, or
|
|
8
|
+
* fetched from `/_alabjs/live/:id` on first mount in CSR pages).
|
|
9
|
+
* 2. Opens an SSE connection to `/_alabjs/live/:id?props=<base64>` and swaps
|
|
10
|
+
* the component's DOM node when new HTML arrives.
|
|
11
|
+
*
|
|
12
|
+
* The actual component code (which may contain DB calls, secrets, heavy deps)
|
|
13
|
+
* never ships to the browser.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Generate the client stub for a live component module.
|
|
17
|
+
*
|
|
18
|
+
* @param moduleId - Stable hash ID used as the SSE channel (e.g. `"a3f8c1d2"`)
|
|
19
|
+
* @param exportNames - Named exports from the original module (usually just `["default"]`)
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateLiveComponentStub(moduleId: string, exportNames: string[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* The virtual `/@alabjs/live-client` module source.
|
|
24
|
+
*
|
|
25
|
+
* This is the only JavaScript that ships to the browser for the live update
|
|
26
|
+
* path. It is intentionally minimal (~1.5 kb minified):
|
|
27
|
+
*
|
|
28
|
+
* - `LiveMount(id, props)` — renders a placeholder div, subscribes to SSE,
|
|
29
|
+
* swaps innerHTML on each `data:` event, reconnects with backoff.
|
|
30
|
+
*
|
|
31
|
+
* React is used only for the initial mount and to expose the component API.
|
|
32
|
+
* All subsequent updates bypass React entirely — they are raw DOM swaps.
|
|
33
|
+
*/
|
|
34
|
+
export declare function generateLiveClientRuntime(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Generate the SERVER-BUILD wrapper for a live component.
|
|
37
|
+
*
|
|
38
|
+
* In SSR pages, the server must emit the same `<div data-live-id="...">` wrapper
|
|
39
|
+
* that `LiveMount` renders on the client, so React hydration doesn't error on a
|
|
40
|
+
* structural mismatch. The actual component is rendered inside the wrapper div —
|
|
41
|
+
* the client uses `suppressHydrationWarning` to preserve those children until
|
|
42
|
+
* the first SSE event arrives.
|
|
43
|
+
*
|
|
44
|
+
* @param moduleId - Stable 16-char ID (same value used by the client stub).
|
|
45
|
+
* @param actualId - Virtual import path for the real component (`<path>?live-actual`).
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateLiveServerWrapper(moduleId: string, actualId: string): string;
|
|
48
|
+
//# sourceMappingURL=live-stub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-stub.d.ts","sourceRoot":"","sources":["../src/live-stub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EAAE,GACpB,MAAM,CA+BR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAwFlD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAcpF"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live component stub generator.
|
|
3
|
+
*
|
|
4
|
+
* When the Vite plugin encounters a `"use live"` file (or a `*.live.tsx` file)
|
|
5
|
+
* during a CLIENT build, it replaces the entire module with a stub that:
|
|
6
|
+
*
|
|
7
|
+
* 1. Renders the server-provided initial HTML (already in the DOM via SSR, or
|
|
8
|
+
* fetched from `/_alabjs/live/:id` on first mount in CSR pages).
|
|
9
|
+
* 2. Opens an SSE connection to `/_alabjs/live/:id?props=<base64>` and swaps
|
|
10
|
+
* the component's DOM node when new HTML arrives.
|
|
11
|
+
*
|
|
12
|
+
* The actual component code (which may contain DB calls, secrets, heavy deps)
|
|
13
|
+
* never ships to the browser.
|
|
14
|
+
*/
|
|
15
|
+
const VIRTUAL_LIVE_CLIENT_ID = "/@alabjs/live-client";
|
|
16
|
+
/**
|
|
17
|
+
* Generate the client stub for a live component module.
|
|
18
|
+
*
|
|
19
|
+
* @param moduleId - Stable hash ID used as the SSE channel (e.g. `"a3f8c1d2"`)
|
|
20
|
+
* @param exportNames - Named exports from the original module (usually just `["default"]`)
|
|
21
|
+
*/
|
|
22
|
+
export function generateLiveComponentStub(moduleId, exportNames) {
|
|
23
|
+
const hasDefault = exportNames.includes("default");
|
|
24
|
+
const lines = [
|
|
25
|
+
`import { LiveMount } from ${JSON.stringify(VIRTUAL_LIVE_CLIENT_ID)};`,
|
|
26
|
+
"",
|
|
27
|
+
];
|
|
28
|
+
// Re-export the default as a LiveMount wrapper.
|
|
29
|
+
// Props are forwarded so the server receives the same input.
|
|
30
|
+
if (hasDefault) {
|
|
31
|
+
lines.push(`export default function LiveComponent(props) {`, ` return LiveMount({ id: ${JSON.stringify(moduleId)}, props });`, `}`, "");
|
|
32
|
+
}
|
|
33
|
+
// Any named exports besides `default` that are NOT live-config exports
|
|
34
|
+
// (liveInterval, liveTags) are stripped — they are server-only.
|
|
35
|
+
// Export them as undefined so destructured imports don't break.
|
|
36
|
+
const serverOnlyExports = exportNames.filter((n) => n !== "default" && n !== "liveInterval" && n !== "liveTags");
|
|
37
|
+
for (const name of serverOnlyExports) {
|
|
38
|
+
lines.push(`export const ${name} = undefined; // stripped: server-only`);
|
|
39
|
+
}
|
|
40
|
+
lines.push(`// alabjs: live module — server code stripped from client bundle`);
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The virtual `/@alabjs/live-client` module source.
|
|
45
|
+
*
|
|
46
|
+
* This is the only JavaScript that ships to the browser for the live update
|
|
47
|
+
* path. It is intentionally minimal (~1.5 kb minified):
|
|
48
|
+
*
|
|
49
|
+
* - `LiveMount(id, props)` — renders a placeholder div, subscribes to SSE,
|
|
50
|
+
* swaps innerHTML on each `data:` event, reconnects with backoff.
|
|
51
|
+
*
|
|
52
|
+
* React is used only for the initial mount and to expose the component API.
|
|
53
|
+
* All subsequent updates bypass React entirely — they are raw DOM swaps.
|
|
54
|
+
*/
|
|
55
|
+
export function generateLiveClientRuntime() {
|
|
56
|
+
return `
|
|
57
|
+
import { createElement, useEffect, useRef } from "react";
|
|
58
|
+
|
|
59
|
+
const RECONNECT_DELAYS = [500, 1000, 2000, 5000, 10000, 30000];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* LiveMount — client runtime for "use live" components.
|
|
63
|
+
*
|
|
64
|
+
* On mount:
|
|
65
|
+
* 1. Checks if the server already rendered HTML into the placeholder
|
|
66
|
+
* (SSR pages: the div has children from the initial render).
|
|
67
|
+
* 2. Opens an SSE connection to /_alabjs/live/:id?props=<base64>.
|
|
68
|
+
* 3. On each "data:" event, replaces innerHTML with the new fragment.
|
|
69
|
+
* 4. On disconnect, reconnects with exponential backoff.
|
|
70
|
+
*
|
|
71
|
+
* @param id - Stable component ID (hash of the module path).
|
|
72
|
+
* @param props - Component props forwarded to the server renderer.
|
|
73
|
+
*/
|
|
74
|
+
export function LiveMount({ id, props }) {
|
|
75
|
+
const ref = useRef(null);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const node = ref.current;
|
|
79
|
+
if (!node) return;
|
|
80
|
+
|
|
81
|
+
let attempt = 0;
|
|
82
|
+
let es = null;
|
|
83
|
+
let dead = false;
|
|
84
|
+
|
|
85
|
+
const propsParam = encodeURIComponent(
|
|
86
|
+
btoa(JSON.stringify(props ?? {}))
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
function connect(lastEventId) {
|
|
90
|
+
if (dead) return;
|
|
91
|
+
let url = "/_alabjs/live/" + id + "?props=" + propsParam;
|
|
92
|
+
if (lastEventId) url += "&lastEventId=" + encodeURIComponent(lastEventId);
|
|
93
|
+
|
|
94
|
+
es = new EventSource(url);
|
|
95
|
+
|
|
96
|
+
es.onmessage = (e) => {
|
|
97
|
+
attempt = 0; // reset backoff on successful message
|
|
98
|
+
if (node && e.data) {
|
|
99
|
+
node.innerHTML = e.data;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Named "event: error" — server pushed a render error message.
|
|
104
|
+
// Log it to the console and show a placeholder; do NOT reconnect
|
|
105
|
+
// (the server will keep the connection alive and push a fix later).
|
|
106
|
+
es.addEventListener("error", (e) => {
|
|
107
|
+
if (e.data) {
|
|
108
|
+
console.error("[alabjs/live] server render error:", e.data);
|
|
109
|
+
if (node) {
|
|
110
|
+
node.innerHTML =
|
|
111
|
+
'<div style="color:#f87171;font-size:12px;padding:8px;border:1px solid #f87171;border-radius:4px">' +
|
|
112
|
+
"Live component render error — check the server console." +
|
|
113
|
+
"</div>";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
es.onerror = () => {
|
|
119
|
+
es.close();
|
|
120
|
+
if (dead) return;
|
|
121
|
+
const delay = RECONNECT_DELAYS[Math.min(attempt, RECONNECT_DELAYS.length - 1)];
|
|
122
|
+
attempt++;
|
|
123
|
+
setTimeout(() => connect(null), delay);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
connect(null);
|
|
128
|
+
|
|
129
|
+
return () => {
|
|
130
|
+
dead = true;
|
|
131
|
+
es?.close();
|
|
132
|
+
};
|
|
133
|
+
}, [id]); // props changes are intentionally not in deps — server re-renders on interval/tag
|
|
134
|
+
|
|
135
|
+
return createElement("div", {
|
|
136
|
+
ref,
|
|
137
|
+
"data-live-id": id,
|
|
138
|
+
"data-live-props": JSON.stringify(props ?? {}),
|
|
139
|
+
suppressHydrationWarning: true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
`.trimStart();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Generate the SERVER-BUILD wrapper for a live component.
|
|
146
|
+
*
|
|
147
|
+
* In SSR pages, the server must emit the same `<div data-live-id="...">` wrapper
|
|
148
|
+
* that `LiveMount` renders on the client, so React hydration doesn't error on a
|
|
149
|
+
* structural mismatch. The actual component is rendered inside the wrapper div —
|
|
150
|
+
* the client uses `suppressHydrationWarning` to preserve those children until
|
|
151
|
+
* the first SSE event arrives.
|
|
152
|
+
*
|
|
153
|
+
* @param moduleId - Stable 16-char ID (same value used by the client stub).
|
|
154
|
+
* @param actualId - Virtual import path for the real component (`<path>?live-actual`).
|
|
155
|
+
*/
|
|
156
|
+
export function generateLiveServerWrapper(moduleId, actualId) {
|
|
157
|
+
return [
|
|
158
|
+
`import _LiveImpl from ${JSON.stringify(actualId)};`,
|
|
159
|
+
`import { createElement as _h } from "react";`,
|
|
160
|
+
`export default function _LiveWrapper(props) {`,
|
|
161
|
+
` return _h("div", {`,
|
|
162
|
+
` "data-live-id": ${JSON.stringify(moduleId)},`,
|
|
163
|
+
` "data-live-props": JSON.stringify(props ?? {}),`,
|
|
164
|
+
` suppressHydrationWarning: true,`,
|
|
165
|
+
` }, _h(_LiveImpl, props));`,
|
|
166
|
+
`}`,
|
|
167
|
+
`export * from ${JSON.stringify(actualId)};`,
|
|
168
|
+
``,
|
|
169
|
+
].join("\n");
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=live-stub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-stub.js","sourceRoot":"","sources":["../src/live-stub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACvC,QAAgB,EAChB,WAAqB;IAErB,MAAM,UAAU,GAAG,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAEnD,MAAM,KAAK,GAAa;QACtB,6BAA6B,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAAG;QACtE,EAAE;KACH,CAAC;IAEF,gDAAgD;IAChD,6DAA6D;IAC7D,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,gDAAgD,EAChD,4BAA4B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,EACjE,GAAG,EACH,EAAE,CACH,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,gEAAgE;IAChE,gEAAgE;IAChE,MAAM,iBAAiB,GAAG,WAAW,CAAC,MAAM,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,cAAc,IAAI,CAAC,KAAK,UAAU,CACnE,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,gBAAgB,IAAI,wCAAwC,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsFR,CAAC,SAAS,EAAE,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAE,QAAgB;IAC1E,OAAO;QACL,yBAAyB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG;QACpD,8CAA8C;QAC9C,+CAA+C;QAC/C,sBAAsB;QACtB,uBAAuB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG;QAClD,qDAAqD;QACrD,qCAAqC;QACrC,6BAA6B;QAC7B,GAAG;QACH,iBAAiB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG;QAC5C,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
package/dist/napi.d.ts
CHANGED
|
@@ -13,5 +13,18 @@ export interface AlabNapi {
|
|
|
13
13
|
* Used by the build pipeline to derive a content-addressed build ID.
|
|
14
14
|
*/
|
|
15
15
|
hashBuildId(content: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Detect a `"use live"` or `"use client"` directive at the top of a source file.
|
|
18
|
+
* Returns JSON: `{ kind: "use_live" | "use_client" | "none", offset: number }`.
|
|
19
|
+
* Only inspects the first statement — O(1) per file.
|
|
20
|
+
*/
|
|
21
|
+
detectDirective(source: string, filename: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Walk `appDir` and validate all RouteLink/Link/navigate path references
|
|
24
|
+
* against the compiled route manifest.
|
|
25
|
+
* Returns JSON: `Array<{ file, offset, kind, path, suggestion? }>`.
|
|
26
|
+
* An empty array means no violations.
|
|
27
|
+
*/
|
|
28
|
+
checkRouteRefs(appDir: string, manifestJson: string): string;
|
|
16
29
|
}
|
|
17
30
|
//# sourceMappingURL=napi.d.ts.map
|
package/dist/napi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"napi.d.ts","sourceRoot":"","sources":["../src/napi.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7F,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IACxD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,aAAa,CACX,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EACvB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,+DAA+D;IAC/D,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3D,8EAA8E;IAC9E,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IACrD;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"napi.d.ts","sourceRoot":"","sources":["../src/napi.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7F,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IACxD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,aAAa,CACX,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EACvB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,+DAA+D;IAC/D,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3D,8EAA8E;IAC9E,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IACrD;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACrC;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1D;;;;;OAKG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alabjs-vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Vite plugin that wires AlabJS's Rust compiler into the transform pipeline",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"react-refresh": "^0.14.0"
|
|
16
16
|
},
|
|
17
17
|
"optionalDependencies": {
|
|
18
|
-
"@alabjs/compiler": "0.
|
|
18
|
+
"@alabjs/compiler": "0.4.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"typescript": "^5.8.0",
|