litzjs 0.0.1 → 0.1.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/README.md +37 -35
- package/dist/client.js +255 -219
- package/dist/client.mjs +255 -219
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/{internal-transport-DR0r68ff.js → internal-transport-Er_DZe-u.js} +45 -3
- package/dist/{internal-transport-dsMykcNK.mjs → internal-transport-ceutGxl7.mjs} +45 -3
- package/dist/{request-headers-DepZ5tjg.mjs → request-headers-B-mkIVuc.mjs} +1 -1
- package/dist/{request-headers-ZPR3TQs3.js → request-headers-CpcQCSrb.js} +1 -1
- package/dist/server.js +4 -4
- package/dist/server.mjs +4 -4
- package/dist/vite.d.mts +58 -23
- package/dist/vite.d.ts +58 -23
- package/dist/vite.js +257 -132
- package/dist/vite.mjs +249 -134
- package/package.json +5 -2
package/dist/vite.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as matchPathname, i as extractRouteLikeParams, o as sortByPathSpecificity } from "./internal-transport-
|
|
2
|
-
import { n as parseInternalRequestBody, t as createInternalHandlerHeaders } from "./request-headers-
|
|
1
|
+
import { a as matchPathname, i as extractRouteLikeParams, o as sortByPathSpecificity } from "./internal-transport-ceutGxl7.mjs";
|
|
2
|
+
import { n as parseInternalRequestBody, t as createInternalHandlerHeaders } from "./request-headers-B-mkIVuc.mjs";
|
|
3
3
|
import vitePluginRsc from "@vitejs/plugin-rsc";
|
|
4
|
-
import {
|
|
5
|
-
import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
5
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
7
6
|
import path from "node:path";
|
|
7
|
+
import picomatch from "picomatch";
|
|
8
8
|
import { glob } from "tinyglobby";
|
|
9
9
|
import ts from "typescript";
|
|
10
10
|
//#region src/client-projection.ts
|
|
@@ -269,6 +269,11 @@ const LITZ_BROWSER_ENTRY_ID = "virtual:litzjs:browser-entry";
|
|
|
269
269
|
const RESOLVED_LITZ_BROWSER_ENTRY_ID = "\0virtual:litzjs:browser-entry";
|
|
270
270
|
const LITZ_RSC_RENDERER_ID = "virtual:litzjs:rsc-renderer";
|
|
271
271
|
const RESOLVED_LITZ_RSC_RENDERER_ID = "\0virtual:litzjs:rsc-renderer";
|
|
272
|
+
/**
|
|
273
|
+
* Creates the Litz Vite plugin array. Returns the `@vitejs/plugin-rsc` plugins
|
|
274
|
+
* plus the core Litz plugin. The mutable state variables below are populated
|
|
275
|
+
* during `configResolved` and kept in sync during dev via file watching.
|
|
276
|
+
*/
|
|
272
277
|
function litz(options = {}) {
|
|
273
278
|
let root = process.cwd();
|
|
274
279
|
let browserEntryPath = "src/main.tsx";
|
|
@@ -282,6 +287,7 @@ function litz(options = {}) {
|
|
|
282
287
|
let resourceManifest = [];
|
|
283
288
|
let apiManifest = [];
|
|
284
289
|
let clientProjectedFiles = /* @__PURE__ */ new Set();
|
|
290
|
+
let closeBundlePassCount = 0;
|
|
285
291
|
let hasFinalizedServerArtifacts = false;
|
|
286
292
|
let hasRegisteredExitFinalizer = false;
|
|
287
293
|
const routePatterns = options.routes ?? [
|
|
@@ -292,6 +298,7 @@ function litz(options = {}) {
|
|
|
292
298
|
const resourcePatterns = options.resources ?? ["src/routes/resources/**/*.{ts,tsx}"];
|
|
293
299
|
const apiPatterns = options.api ?? ["src/routes/api/**/*.{ts,tsx}"];
|
|
294
300
|
const rscPlugins = vitePluginRsc({
|
|
301
|
+
...options.rsc,
|
|
295
302
|
entries: {
|
|
296
303
|
client: LITZ_BROWSER_ENTRY_ID,
|
|
297
304
|
rsc: LITZ_RSC_ENTRY_ID
|
|
@@ -300,6 +307,12 @@ function litz(options = {}) {
|
|
|
300
307
|
});
|
|
301
308
|
const litzPlugin = {
|
|
302
309
|
name: "litzjs/vite",
|
|
310
|
+
buildStart() {
|
|
311
|
+
if (this.environment?.name === "rsc") {
|
|
312
|
+
closeBundlePassCount = 0;
|
|
313
|
+
hasFinalizedServerArtifacts = false;
|
|
314
|
+
}
|
|
315
|
+
},
|
|
303
316
|
config(userConfig) {
|
|
304
317
|
const baseOutDir = userConfig.build?.outDir ?? "dist";
|
|
305
318
|
return { environments: {
|
|
@@ -307,7 +320,14 @@ function litz(options = {}) {
|
|
|
307
320
|
outDir: path.join(baseOutDir, "client"),
|
|
308
321
|
manifest: true
|
|
309
322
|
} },
|
|
310
|
-
rsc: { build: {
|
|
323
|
+
rsc: { build: {
|
|
324
|
+
outDir: path.join(baseOutDir, "server"),
|
|
325
|
+
rollupOptions: { output: {
|
|
326
|
+
entryFileNames: "index.js",
|
|
327
|
+
format: "es",
|
|
328
|
+
codeSplitting: false
|
|
329
|
+
} }
|
|
330
|
+
} },
|
|
311
331
|
ssr: { build: {
|
|
312
332
|
outDir: path.join(baseOutDir, "server"),
|
|
313
333
|
emptyOutDir: false
|
|
@@ -319,6 +339,12 @@ function litz(options = {}) {
|
|
|
319
339
|
outputRootDir = path.resolve(root, config.build.outDir || "dist");
|
|
320
340
|
clientOutDir = path.resolve(root, config.environments.client?.build.outDir || path.join("dist", "client"));
|
|
321
341
|
serverOutDir = path.resolve(root, config.environments.rsc?.build.outDir || path.join("dist", "server"));
|
|
342
|
+
if (config.command === "build") {
|
|
343
|
+
const rscOutput = config.environments.rsc?.build.rollupOptions?.output;
|
|
344
|
+
const rscOutputs = Array.isArray(rscOutput) ? rscOutput : rscOutput ? [rscOutput] : [];
|
|
345
|
+
if (rscOutputs.length === 0) throw new Error("litz: could not find a rollupOptions.output entry for the RSC environment. This is an internal configuration error.");
|
|
346
|
+
for (const output of rscOutputs) if (output.codeSplitting !== false) throw new Error("litz: the RSC environment must have codeSplitting disabled (rollupOptions.output.codeSplitting: false). The server build pipeline requires a single entry file.");
|
|
347
|
+
}
|
|
322
348
|
browserEntryPath = await discoverBrowserEntry(root);
|
|
323
349
|
serverEntryPath = await discoverServerEntry(root, options.server);
|
|
324
350
|
serverEntryFilePath = serverEntryPath ? path.resolve(root, serverEntryPath) : null;
|
|
@@ -373,26 +399,138 @@ export async function renderView(node, metadata = {}) {
|
|
|
373
399
|
return null;
|
|
374
400
|
},
|
|
375
401
|
configureServer(server) {
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
402
|
+
const isRouteCandidate = picomatch(routePatterns);
|
|
403
|
+
const isResourceCandidate = picomatch(resourcePatterns);
|
|
404
|
+
const isApiCandidate = picomatch(apiPatterns);
|
|
405
|
+
const isManifestCandidate = (p) => isRouteCandidate(p) || isResourceCandidate(p) || isApiCandidate(p);
|
|
406
|
+
let debounceTimer = null;
|
|
407
|
+
let pendingFullDiscovery = false;
|
|
408
|
+
let manifestGeneration = 0;
|
|
409
|
+
const pendingRetry = /* @__PURE__ */ new Set();
|
|
410
|
+
const inFlightSingleFile = /* @__PURE__ */ new Set();
|
|
411
|
+
const flushManifestRefresh = async () => {
|
|
412
|
+
if (pendingFullDiscovery) {
|
|
413
|
+
pendingFullDiscovery = false;
|
|
414
|
+
manifestGeneration++;
|
|
415
|
+
const generation = manifestGeneration;
|
|
416
|
+
const next = await discoverAllManifests(root, routePatterns, resourcePatterns, apiPatterns);
|
|
417
|
+
if (manifestGeneration !== generation || pendingFullDiscovery) return;
|
|
418
|
+
const changed = JSON.stringify(routeManifest) !== JSON.stringify(next.routeManifest) || JSON.stringify(layoutManifest) !== JSON.stringify(next.layoutManifest) || JSON.stringify(resourceManifest) !== JSON.stringify(next.resourceManifest) || JSON.stringify(apiManifest) !== JSON.stringify(next.apiManifest);
|
|
419
|
+
routeManifest = next.routeManifest;
|
|
420
|
+
layoutManifest = next.layoutManifest;
|
|
421
|
+
resourceManifest = next.resourceManifest;
|
|
422
|
+
apiManifest = next.apiManifest;
|
|
423
|
+
clientProjectedFiles = createClientProjectedFileSet(root, routeManifest, layoutManifest, resourceManifest, apiManifest);
|
|
424
|
+
if (changed) {
|
|
425
|
+
invalidateVirtualModule(server, RESOLVED_ROUTE_MANIFEST_ID);
|
|
426
|
+
invalidateVirtualModule(server, RESOLVED_RESOURCE_MANIFEST_ID);
|
|
427
|
+
server.ws.send({ type: "full-reload" });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const scheduleRefresh = () => {
|
|
432
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
433
|
+
debounceTimer = setTimeout(() => {
|
|
434
|
+
debounceTimer = null;
|
|
435
|
+
flushManifestRefresh().catch((err) => {
|
|
436
|
+
console.error("[litzjs] error during manifest discovery:", err);
|
|
437
|
+
});
|
|
438
|
+
}, 50);
|
|
439
|
+
};
|
|
440
|
+
const updateManifestEntry = (manifest, entry, file, sort) => {
|
|
441
|
+
const modulePath = normalizeRelativePath(root, file);
|
|
442
|
+
const idx = manifest.findIndex((r) => r.modulePath === modulePath);
|
|
443
|
+
if (entry && idx >= 0) {
|
|
444
|
+
if (JSON.stringify(manifest[idx]) !== JSON.stringify(entry)) {
|
|
445
|
+
const next = manifest.map((item, i) => i === idx ? entry : item);
|
|
446
|
+
return {
|
|
447
|
+
manifest: sort ? sort(next) : next,
|
|
448
|
+
changed: true
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
} else if (entry && idx < 0) {
|
|
452
|
+
const next = [...manifest, entry];
|
|
453
|
+
return {
|
|
454
|
+
manifest: sort ? sort(next) : next,
|
|
455
|
+
changed: true
|
|
456
|
+
};
|
|
457
|
+
} else if (!entry && idx >= 0) return {
|
|
458
|
+
manifest: manifest.filter((_, i) => i !== idx),
|
|
459
|
+
changed: true
|
|
460
|
+
};
|
|
461
|
+
return {
|
|
462
|
+
manifest,
|
|
463
|
+
changed: false
|
|
464
|
+
};
|
|
465
|
+
};
|
|
466
|
+
const refreshSingleFile = async (file) => {
|
|
467
|
+
if (pendingFullDiscovery) return;
|
|
468
|
+
const generation = manifestGeneration;
|
|
469
|
+
const relativePath = normalizeRelativePath(root, file);
|
|
470
|
+
let changed = false;
|
|
471
|
+
if (isRouteCandidate(relativePath)) {
|
|
472
|
+
const entry = await discoverRouteFromFile(root, file);
|
|
473
|
+
if (manifestGeneration !== generation) return;
|
|
474
|
+
const result = updateManifestEntry(routeManifest, entry, file, sortByPathSpecificity);
|
|
475
|
+
routeManifest = result.manifest;
|
|
476
|
+
changed = changed || result.changed;
|
|
477
|
+
const layoutEntry = await discoverLayoutFromFile(root, file);
|
|
478
|
+
if (manifestGeneration !== generation) return;
|
|
479
|
+
const layoutResult = updateManifestEntry(layoutManifest, layoutEntry, file);
|
|
480
|
+
layoutManifest = layoutResult.manifest;
|
|
481
|
+
changed = changed || layoutResult.changed;
|
|
482
|
+
}
|
|
483
|
+
if (isResourceCandidate(relativePath)) {
|
|
484
|
+
const entry = await discoverResourceFromFile(root, file);
|
|
485
|
+
if (manifestGeneration !== generation) return;
|
|
486
|
+
const result = updateManifestEntry(resourceManifest, entry, file);
|
|
487
|
+
resourceManifest = result.manifest;
|
|
488
|
+
changed = changed || result.changed;
|
|
489
|
+
}
|
|
490
|
+
if (isApiCandidate(relativePath)) {
|
|
491
|
+
const entry = await discoverApiRouteFromFile(root, file);
|
|
492
|
+
if (manifestGeneration !== generation) return;
|
|
493
|
+
const result = updateManifestEntry(apiManifest, entry, file, sortByPathSpecificity);
|
|
494
|
+
apiManifest = result.manifest;
|
|
495
|
+
changed = changed || result.changed;
|
|
496
|
+
}
|
|
497
|
+
if (changed) {
|
|
498
|
+
clientProjectedFiles = createClientProjectedFileSet(root, routeManifest, layoutManifest, resourceManifest, apiManifest);
|
|
499
|
+
invalidateVirtualModule(server, RESOLVED_ROUTE_MANIFEST_ID);
|
|
500
|
+
invalidateVirtualModule(server, RESOLVED_RESOURCE_MANIFEST_ID);
|
|
501
|
+
server.ws.send({ type: "full-reload" });
|
|
502
|
+
}
|
|
389
503
|
};
|
|
390
|
-
const
|
|
391
|
-
|
|
504
|
+
const runSingleFileRefresh = (file) => {
|
|
505
|
+
inFlightSingleFile.add(file);
|
|
506
|
+
refreshSingleFile(file).catch((err) => {
|
|
507
|
+
if (err?.code !== "ENOENT") console.error("[litzjs] error refreshing file manifest:", err);
|
|
508
|
+
}).finally(() => {
|
|
509
|
+
inFlightSingleFile.delete(file);
|
|
510
|
+
if (pendingRetry.has(file)) {
|
|
511
|
+
pendingRetry.delete(file);
|
|
512
|
+
if (!pendingFullDiscovery) runSingleFileRefresh(file);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
};
|
|
516
|
+
const onFileAddOrUnlink = (file) => {
|
|
517
|
+
if (!/\.(ts|tsx)$/.test(file)) return;
|
|
518
|
+
if (!isManifestCandidate(normalizeRelativePath(root, file))) return;
|
|
519
|
+
pendingFullDiscovery = true;
|
|
520
|
+
scheduleRefresh();
|
|
521
|
+
};
|
|
522
|
+
const onFileChange = (file) => {
|
|
523
|
+
if (!/\.(ts|tsx)$/.test(file)) return;
|
|
524
|
+
if (!isManifestCandidate(normalizeRelativePath(root, file))) return;
|
|
525
|
+
if (inFlightSingleFile.has(file)) {
|
|
526
|
+
pendingRetry.add(file);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
runSingleFileRefresh(file);
|
|
392
530
|
};
|
|
393
|
-
server.watcher.on("add",
|
|
394
|
-
server.watcher.on("change",
|
|
395
|
-
server.watcher.on("unlink",
|
|
531
|
+
server.watcher.on("add", onFileAddOrUnlink);
|
|
532
|
+
server.watcher.on("change", onFileChange);
|
|
533
|
+
server.watcher.on("unlink", onFileAddOrUnlink);
|
|
396
534
|
server.middlewares.use((request, response, next) => {
|
|
397
535
|
handleLitzResourceRequest(server, resourceManifest, request, response, next);
|
|
398
536
|
});
|
|
@@ -430,6 +568,7 @@ export async function renderView(node, metadata = {}) {
|
|
|
430
568
|
};
|
|
431
569
|
},
|
|
432
570
|
async closeBundle() {
|
|
571
|
+
closeBundlePassCount++;
|
|
433
572
|
await Promise.all([writeProductionIndexHtml(root, clientOutDir), removeLegacyBuildArtifacts(outputRootDir)]);
|
|
434
573
|
if (hasFinalizedServerArtifacts) return;
|
|
435
574
|
if (!hasRegisteredExitFinalizer) {
|
|
@@ -437,9 +576,15 @@ export async function renderView(node, metadata = {}) {
|
|
|
437
576
|
process.once("exit", () => {
|
|
438
577
|
if (hasFinalizedServerArtifacts) return;
|
|
439
578
|
hasFinalizedServerArtifacts = finalizeServerArtifacts(serverOutDir, clientOutDir, options.embedAssets ?? false);
|
|
579
|
+
if (!hasFinalizedServerArtifacts) {
|
|
580
|
+
process.exitCode = 1;
|
|
581
|
+
console.error("litz: failed to finalize server artifacts. The assets manifest import could not be inlined — the production server bundle may be broken.");
|
|
582
|
+
}
|
|
440
583
|
});
|
|
441
584
|
}
|
|
442
|
-
|
|
585
|
+
const inlineClientAssets = options.embedAssets ?? false;
|
|
586
|
+
if (inlineClientAssets && closeBundlePassCount <= 1) return;
|
|
587
|
+
hasFinalizedServerArtifacts = finalizeServerArtifacts(serverOutDir, clientOutDir, inlineClientAssets);
|
|
443
588
|
}
|
|
444
589
|
};
|
|
445
590
|
return [...rscPlugins, litzPlugin];
|
|
@@ -595,6 +740,13 @@ function createClientProjectedFileSet(root, routes, layouts, resources, apiRoute
|
|
|
595
740
|
...apiRoutes
|
|
596
741
|
].map((entry) => path.resolve(root, entry.modulePath)));
|
|
597
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Build-time transform that injects the server manifest into the user's server
|
|
745
|
+
* entry. Uses the TypeScript compiler API to find all `createServer()` calls
|
|
746
|
+
* (imported from `litzjs/server`) and wraps their options argument with a
|
|
747
|
+
* helper that merges in the route/resource/API manifest. Returns `null` if no
|
|
748
|
+
* `createServer` import is found.
|
|
749
|
+
*/
|
|
598
750
|
function injectServerManifestIntoServerEntry(filePath, source) {
|
|
599
751
|
const scriptKind = filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
600
752
|
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, scriptKind);
|
|
@@ -712,7 +864,9 @@ async function handleLitzResourceRequest(server, manifest, request, response, ne
|
|
|
712
864
|
return;
|
|
713
865
|
}
|
|
714
866
|
const normalizedRequest = normalizeInternalResourceRequest(internalRequest, resourcePath, body.request, body.payload);
|
|
715
|
-
const
|
|
867
|
+
const controller = new AbortController();
|
|
868
|
+
request.once("close", () => controller.abort());
|
|
869
|
+
const signal = controller.signal;
|
|
716
870
|
await sendServerResult(server, response, await runDevMiddlewareChain({
|
|
717
871
|
middleware: resource.middleware ?? [],
|
|
718
872
|
request: normalizedRequest.request,
|
|
@@ -730,9 +884,10 @@ async function handleLitzResourceRequest(server, manifest, request, response, ne
|
|
|
730
884
|
}), `${entry.path}#${operation}`);
|
|
731
885
|
} catch (error) {
|
|
732
886
|
server.ssrFixStacktrace(error);
|
|
887
|
+
console.error(error);
|
|
733
888
|
sendLitzJson(response, 500, {
|
|
734
889
|
kind: "fault",
|
|
735
|
-
message:
|
|
890
|
+
message: "Resource request failed."
|
|
736
891
|
});
|
|
737
892
|
}
|
|
738
893
|
}
|
|
@@ -791,7 +946,9 @@ async function handleLitzRouteRequest(server, manifest, request, response, next)
|
|
|
791
946
|
return;
|
|
792
947
|
}
|
|
793
948
|
const normalizedRequest = normalizeInternalResourceRequest(internalRequest, routePath, body.request, body.payload);
|
|
794
|
-
const
|
|
949
|
+
const controller = new AbortController();
|
|
950
|
+
request.once("close", () => controller.abort());
|
|
951
|
+
const signal = controller.signal;
|
|
795
952
|
await sendServerResult(server, response, await runDevMiddlewareChain({
|
|
796
953
|
middleware: chain.slice(0, targetIndex + 1).flatMap((candidate) => candidate.middleware),
|
|
797
954
|
request: normalizedRequest.request,
|
|
@@ -810,9 +967,10 @@ async function handleLitzRouteRequest(server, manifest, request, response, next)
|
|
|
810
967
|
}), `${target.id}#${operation}`);
|
|
811
968
|
} catch (error) {
|
|
812
969
|
server.ssrFixStacktrace(error);
|
|
970
|
+
console.error(error);
|
|
813
971
|
sendLitzJson(response, 500, {
|
|
814
972
|
kind: "fault",
|
|
815
|
-
message:
|
|
973
|
+
message: "Route request failed."
|
|
816
974
|
});
|
|
817
975
|
}
|
|
818
976
|
}
|
|
@@ -868,7 +1026,9 @@ async function handleLitzApiRequest(server, manifest, request, response, next) {
|
|
|
868
1026
|
return;
|
|
869
1027
|
}
|
|
870
1028
|
const apiRequest = await createNodeRequest(request, requestUrl);
|
|
871
|
-
const
|
|
1029
|
+
const controller = new AbortController();
|
|
1030
|
+
request.once("close", () => controller.abort());
|
|
1031
|
+
const signal = controller.signal;
|
|
872
1032
|
await writeFetchResponseToNode(response, await runDevMiddlewareChain({
|
|
873
1033
|
middleware: api?.middleware ?? [],
|
|
874
1034
|
request: apiRequest,
|
|
@@ -886,8 +1046,9 @@ async function handleLitzApiRequest(server, manifest, request, response, next) {
|
|
|
886
1046
|
}));
|
|
887
1047
|
} catch (error) {
|
|
888
1048
|
server.ssrFixStacktrace(error);
|
|
1049
|
+
console.error(error);
|
|
889
1050
|
response.statusCode = 500;
|
|
890
|
-
response.end(
|
|
1051
|
+
response.end("API route failed.");
|
|
891
1052
|
}
|
|
892
1053
|
}
|
|
893
1054
|
async function readRequestBuffer(request) {
|
|
@@ -1078,6 +1239,13 @@ function isDevLayout(value) {
|
|
|
1078
1239
|
function findDevTargetRouteMatch(chain, targetId) {
|
|
1079
1240
|
return chain.find((entry) => entry.id === targetId);
|
|
1080
1241
|
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Koa-style middleware dispatch. Recursively walks through `middleware` handlers
|
|
1244
|
+
* — each receives the current context and a `next()` function. Calling `next()`
|
|
1245
|
+
* invokes the next handler in the chain; the final handler calls `execute()`.
|
|
1246
|
+
* A guard prevents `next()` from being called more than once per handler.
|
|
1247
|
+
* Handlers can override the context for downstream middleware via `next({ context })`.
|
|
1248
|
+
*/
|
|
1081
1249
|
async function runDevMiddlewareChain(options) {
|
|
1082
1250
|
const dispatch = async (index, context) => {
|
|
1083
1251
|
const middleware = options.middleware[index];
|
|
@@ -1123,15 +1291,25 @@ async function readJson(filePath) {
|
|
|
1123
1291
|
async function removeLegacyBuildArtifacts(outputRootDir) {
|
|
1124
1292
|
await rm(path.join(outputRootDir, "index.html"), { force: true });
|
|
1125
1293
|
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Post-build finalization pipeline:
|
|
1296
|
+
* 1. Read the single-file RSC bundle (`index.js`) and assets manifest
|
|
1297
|
+
* 2. Inline the manifest by replacing its import with the manifest source
|
|
1298
|
+
* 3. Wrap in a server module (or an inline-asset wrapper if `embedAssets`)
|
|
1299
|
+
* 4. Write the final `index.js` and clean up RSC plugin artifacts
|
|
1300
|
+
*
|
|
1301
|
+
* Returns `false` if the manifest import replacement doesn't match — this
|
|
1302
|
+
* signals that the RSC plugin hasn't written its artifacts yet, and the
|
|
1303
|
+
* caller should retry (see the `closeBundle` hook).
|
|
1304
|
+
*/
|
|
1126
1305
|
function finalizeServerArtifacts(serverOutDir, clientOutDir, inlineClientAssets) {
|
|
1127
|
-
const rscIndexPath = path.join(serverOutDir, "index.js");
|
|
1128
|
-
const rscAssetsManifestPath = path.join(serverOutDir, "__vite_rsc_assets_manifest.js");
|
|
1129
1306
|
let rscEntrySource;
|
|
1130
1307
|
let rscAssetsManifestSource;
|
|
1131
1308
|
let documentHtml = "";
|
|
1132
1309
|
let clientAssets = [];
|
|
1133
1310
|
try {
|
|
1134
|
-
|
|
1311
|
+
rscEntrySource = readFileSync(path.join(serverOutDir, "index.js"), "utf8");
|
|
1312
|
+
rscAssetsManifestSource = readFileSync(path.join(serverOutDir, "__vite_rsc_assets_manifest.js"), "utf8");
|
|
1135
1313
|
if (inlineClientAssets) {
|
|
1136
1314
|
documentHtml = readFileSync(path.join(clientOutDir, "index.html"), "utf8");
|
|
1137
1315
|
clientAssets = collectEmbeddedClientAssets(clientOutDir);
|
|
@@ -1140,36 +1318,25 @@ function finalizeServerArtifacts(serverOutDir, clientOutDir, inlineClientAssets)
|
|
|
1140
1318
|
return false;
|
|
1141
1319
|
}
|
|
1142
1320
|
const manifestBindingSource = rscAssetsManifestSource.replace(/^export default\s+/, "const assetsManifest = ");
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
"",
|
|
1155
|
-
" getStore() {",
|
|
1156
|
-
" return this.store;",
|
|
1157
|
-
" }",
|
|
1158
|
-
"",
|
|
1159
|
-
" enterWith(store) {",
|
|
1160
|
-
" this.store = store;",
|
|
1161
|
-
" }",
|
|
1162
|
-
"}",
|
|
1163
|
-
"",
|
|
1164
|
-
"globalThis.AsyncLocalStorage ??= __LitzAsyncLocalStorage;"
|
|
1165
|
-
].join("\n");
|
|
1166
|
-
const inlinedServerSource = rscEntrySource.replace("import assetsManifest from \"./__vite_rsc_assets_manifest.js\";", manifestBindingSource).replace(/import \* as __viteRscAsyncHooks from "node:async_hooks";\s*/g, "").replace(/const __viteRscAsyncHooks = require\("node:async_hooks"\);\s*/g, "").replace(/globalThis\.AsyncLocalStorage = __viteRscAsyncHooks\.AsyncLocalStorage;/g, asyncLocalStorageShimSource);
|
|
1167
|
-
const bundledWrapperSource = bundleServerWrapper(serverOutDir, inlineClientAssets ? createInlineAssetServerWrapper(inlinedServerSource, documentHtml, clientAssets) : createServerModuleWrapper(inlinedServerSource));
|
|
1168
|
-
if (!bundledWrapperSource) return false;
|
|
1169
|
-
mkdirSync(serverOutDir, { recursive: true });
|
|
1170
|
-
writeFileSync(path.join(serverOutDir, "index.js"), bundledWrapperSource, "utf8");
|
|
1321
|
+
if (manifestBindingSource === rscAssetsManifestSource) return false;
|
|
1322
|
+
const inlinedServerSource = rscEntrySource.replace("import assetsManifest from \"./__vite_rsc_assets_manifest.js\";", manifestBindingSource);
|
|
1323
|
+
if (inlinedServerSource === rscEntrySource) return false;
|
|
1324
|
+
let wrapperSource;
|
|
1325
|
+
try {
|
|
1326
|
+
wrapperSource = inlineClientAssets ? createInlineAssetServerWrapper(inlinedServerSource, documentHtml, clientAssets) : createServerModuleWrapper(inlinedServerSource);
|
|
1327
|
+
} catch {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
writeFileSync(path.join(serverOutDir, "index.js"), wrapperSource, "utf8");
|
|
1331
|
+
cleanupRscPluginArtifacts(serverOutDir);
|
|
1171
1332
|
return true;
|
|
1172
1333
|
}
|
|
1334
|
+
function cleanupRscPluginArtifacts(serverOutDir) {
|
|
1335
|
+
for (const entry of readdirSync(serverOutDir)) if (entry.startsWith("__vite_rsc_")) rmSync(path.join(serverOutDir, entry), {
|
|
1336
|
+
force: true,
|
|
1337
|
+
recursive: true
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1173
1340
|
function createServerModuleWrapper(serverModuleSource) {
|
|
1174
1341
|
const { source, handlerName } = transformServerModuleSource(serverModuleSource);
|
|
1175
1342
|
return [
|
|
@@ -1179,6 +1346,19 @@ function createServerModuleWrapper(serverModuleSource) {
|
|
|
1179
1346
|
""
|
|
1180
1347
|
].join("\n");
|
|
1181
1348
|
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Strips the default export from the bundled RSC server module and rebinds it
|
|
1351
|
+
* to `__litzjsServerHandler` so the wrapper can re-export it. Uses the
|
|
1352
|
+
* TypeScript compiler API to handle four export patterns:
|
|
1353
|
+
*
|
|
1354
|
+
* 1. Named export lists — `export { handler as default }` → extract the binding
|
|
1355
|
+
* 2. Export assignments — `export default expr` → `const __litzjsServerHandler = expr`
|
|
1356
|
+
* 3. Default function declarations — strip modifiers, keep name, set binding
|
|
1357
|
+
* 4. Default class declarations — same treatment as functions
|
|
1358
|
+
*
|
|
1359
|
+
* If no handler was directly declared (pattern 1), a final `const` binding is
|
|
1360
|
+
* appended from the recorded default expression.
|
|
1361
|
+
*/
|
|
1182
1362
|
function transformServerModuleSource(serverModuleSource) {
|
|
1183
1363
|
const handlerName = "__litzjsServerHandler";
|
|
1184
1364
|
const sourceFile = ts.createSourceFile("server/index.js", serverModuleSource, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
|
@@ -1257,6 +1437,13 @@ function transformServerModuleSource(serverModuleSource) {
|
|
|
1257
1437
|
handlerName
|
|
1258
1438
|
};
|
|
1259
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Generates a self-contained server module for edge runtimes. Embeds all client
|
|
1442
|
+
* assets (JS, CSS, images, fonts) and the document HTML as data literals. The
|
|
1443
|
+
* generated `handle(request)` function serves static assets directly, returns
|
|
1444
|
+
* `index.html` for document requests, and delegates everything else to the RSC
|
|
1445
|
+
* server handler.
|
|
1446
|
+
*/
|
|
1260
1447
|
function createInlineAssetServerWrapper(serverModuleSource, documentHtml, clientAssets) {
|
|
1261
1448
|
const serializedClientAssets = JSON.stringify(clientAssets);
|
|
1262
1449
|
const serializedDocumentHtml = JSON.stringify(documentHtml);
|
|
@@ -1334,78 +1521,6 @@ function createInlineAssetServerWrapper(serverModuleSource, documentHtml, client
|
|
|
1334
1521
|
""
|
|
1335
1522
|
].join("\n");
|
|
1336
1523
|
}
|
|
1337
|
-
function bundleServerWrapper(serverOutDir, wrapperSource) {
|
|
1338
|
-
const wrapperEntryPath = path.join(serverOutDir, "__litzjs_wrapped_server_entry.mjs");
|
|
1339
|
-
const bundleOutDir = path.join(serverOutDir, "__litzjs_bundle_out");
|
|
1340
|
-
const bundleScript = `
|
|
1341
|
-
const entryPath = process.env.LITZ_SERVER_ENTRY_PATH;
|
|
1342
|
-
const outDir = process.env.LITZ_SERVER_OUT_DIR;
|
|
1343
|
-
|
|
1344
|
-
if (!entryPath || !outDir) {
|
|
1345
|
-
throw new Error("Missing Litz server bundle paths.");
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
(async () => {
|
|
1349
|
-
const { build } = await import("vite");
|
|
1350
|
-
|
|
1351
|
-
await build({
|
|
1352
|
-
appType: "custom",
|
|
1353
|
-
configFile: false,
|
|
1354
|
-
publicDir: false,
|
|
1355
|
-
logLevel: "silent",
|
|
1356
|
-
build: {
|
|
1357
|
-
ssr: entryPath,
|
|
1358
|
-
copyPublicDir: false,
|
|
1359
|
-
emptyOutDir: false,
|
|
1360
|
-
minify: false,
|
|
1361
|
-
outDir,
|
|
1362
|
-
target: "esnext",
|
|
1363
|
-
write: true,
|
|
1364
|
-
rollupOptions: {
|
|
1365
|
-
output: {
|
|
1366
|
-
entryFileNames: "index.js",
|
|
1367
|
-
format: "es",
|
|
1368
|
-
inlineDynamicImports: true,
|
|
1369
|
-
},
|
|
1370
|
-
},
|
|
1371
|
-
},
|
|
1372
|
-
});
|
|
1373
|
-
})().catch((error) => {
|
|
1374
|
-
console.error(error);
|
|
1375
|
-
process.exit(1);
|
|
1376
|
-
});
|
|
1377
|
-
`;
|
|
1378
|
-
writeFileSync(wrapperEntryPath, wrapperSource, "utf8");
|
|
1379
|
-
rmSync(bundleOutDir, {
|
|
1380
|
-
force: true,
|
|
1381
|
-
recursive: true
|
|
1382
|
-
});
|
|
1383
|
-
mkdirSync(bundleOutDir, { recursive: true });
|
|
1384
|
-
const result = spawnSync(process.execPath, ["-e", bundleScript], {
|
|
1385
|
-
cwd: serverOutDir,
|
|
1386
|
-
encoding: "utf8",
|
|
1387
|
-
env: {
|
|
1388
|
-
...process.env,
|
|
1389
|
-
LITZ_SERVER_ENTRY_PATH: wrapperEntryPath,
|
|
1390
|
-
LITZ_SERVER_OUT_DIR: bundleOutDir
|
|
1391
|
-
}
|
|
1392
|
-
});
|
|
1393
|
-
try {
|
|
1394
|
-
if (result.status !== 0) {
|
|
1395
|
-
const stderr = result.stderr.trim();
|
|
1396
|
-
const stdout = result.stdout.trim();
|
|
1397
|
-
const failureMessage = stderr || stdout || "Failed to bundle the production Litz server.";
|
|
1398
|
-
throw new Error(failureMessage);
|
|
1399
|
-
}
|
|
1400
|
-
return readFileSync(path.join(bundleOutDir, "index.js"), "utf8");
|
|
1401
|
-
} finally {
|
|
1402
|
-
rmSync(wrapperEntryPath, { force: true });
|
|
1403
|
-
rmSync(bundleOutDir, {
|
|
1404
|
-
force: true,
|
|
1405
|
-
recursive: true
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
1524
|
function collectEmbeddedClientAssets(clientOutDir) {
|
|
1410
1525
|
const assets = [];
|
|
1411
1526
|
walkClientAssets(clientOutDir, clientOutDir, assets);
|
|
@@ -1455,4 +1570,4 @@ function getContentType(filePath) {
|
|
|
1455
1570
|
}
|
|
1456
1571
|
}
|
|
1457
1572
|
//#endregion
|
|
1458
|
-
export { discoverServerEntry, litz, transformServerModuleSource };
|
|
1573
|
+
export { cleanupRscPluginArtifacts, discoverAllManifests, discoverApiRouteFromFile, discoverLayoutFromFile, discoverResourceFromFile, discoverRouteFromFile, discoverServerEntry, handleLitzApiRequest, handleLitzResourceRequest, handleLitzRouteRequest, litz, transformServerModuleSource };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "litzjs",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Client-first React framework with RSC support built on Vite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -63,11 +63,13 @@
|
|
|
63
63
|
"fixture:build": "vite build --config fixtures/rsc-smoke/vite.config.ts"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
+
"picomatch": "4.0.4",
|
|
66
67
|
"tinyglobby": "0.2.15"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
69
70
|
"@changesets/cli": "2.30.0",
|
|
70
71
|
"@types/node": "25.5.0",
|
|
72
|
+
"@types/picomatch": "4.0.2",
|
|
71
73
|
"@types/react": "19.2.14",
|
|
72
74
|
"@types/react-dom": "19.2.3",
|
|
73
75
|
"@vitejs/plugin-rsc": "0.5.21",
|
|
@@ -79,7 +81,8 @@
|
|
|
79
81
|
"react": "19.2.4",
|
|
80
82
|
"react-dom": "19.2.4",
|
|
81
83
|
"tsdown": "0.21.4",
|
|
82
|
-
"vite": "8.0.1"
|
|
84
|
+
"vite": "8.0.1",
|
|
85
|
+
"vite-plugin-pwa": "1.2.0"
|
|
83
86
|
},
|
|
84
87
|
"peerDependencies": {
|
|
85
88
|
"@vitejs/plugin-rsc": "^0.5.21",
|