@jay-framework/production-server 0.17.4 → 0.18.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/dist/index.js CHANGED
@@ -1,9 +1,3 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => {
4
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- return value;
6
- };
7
1
  import { build } from "vite";
8
2
  import { jayStackCompiler, extractActionsFromSource } from "@jay-framework/compiler-jay-stack";
9
3
  import { scanRoutes, JayRouteParamType, parseRouteSegments } from "@jay-framework/stack-route-scanner";
@@ -11,17 +5,21 @@ import { getLogger } from "@jay-framework/logger";
11
5
  import path from "node:path";
12
6
  import fs from "node:fs/promises";
13
7
  import { createRequire } from "node:module";
14
- import { parseJayFile, JAY_IMPORT_RESOLVER, injectHeadfullFSTemplates, discoverHeadlessInstances, assignCoordinatesToJayHtml, generateServerElementFile, parseContract, slowRenderTransform, resolveHeadlessInstances } from "@jay-framework/compiler-jay-html";
15
- import { DevSlowlyChangingPhase, slowRenderInstances, scanPlugins, runLoadParams, renderFastChangingData, mergeHeadTags, serializeHeadTags, getClientInitData, actionRegistry, setClientInitData, parseCookies } from "@jay-framework/stack-server-runtime";
16
- import { checkValidationErrors } from "@jay-framework/compiler-shared";
17
- import { jayRuntime } from "@jay-framework/vite-plugin";
8
+ import { DevSlowlyChangingPhase, slowRenderInstances, scanPlugins, runLoadParams, parseCookies } from "@jay-framework/stack-server-runtime";
9
+ import { l as loadProductionPageParts, g as buildPagePartsConfig, d as initializeServices, r as registerActionsFromManifest, i as isActionRequest, a as fetchActionRequest, c as fetchStaticFile, m as matchRequest, f as fetchPageRequest, F as FilesystemArtifactStore } from "./init-services-F6Svgfm9.js";
10
+ import { e, b } from "./init-services-F6Svgfm9.js";
18
11
  import crypto from "node:crypto";
19
12
  import fs$1 from "node:fs";
13
+ import { jayRuntime } from "@jay-framework/vite-plugin";
14
+ import { injectHeadfullFSTemplates, JAY_IMPORT_RESOLVER, parseJayFile, generateElementHydrateFile, generateServerElementFile } from "@jay-framework/compiler-jay-html";
15
+ import { checkValidationErrors, RuntimeMode } from "@jay-framework/compiler-shared";
16
+ import { parse } from "node-html-parser";
17
+ import { transform } from "esbuild";
20
18
  import http from "node:http";
21
19
  import { Readable } from "node:stream";
22
- import { deepMergeViewStates } from "@jay-framework/view-state-merge";
23
- import { asyncSwapScript } from "@jay-framework/ssr-runtime";
24
- import { isJayAction, isJayStreamAction, isJayWebhook } from "@jay-framework/fullstack-component";
20
+ import { isJayWebhook } from "@jay-framework/fullstack-component";
21
+ import "@jay-framework/view-state-merge";
22
+ import "@jay-framework/ssr-runtime";
25
23
  async function discoverServerEntries(projectRoot, pagesRoot) {
26
24
  const logger = getLogger();
27
25
  const routes = await scanRoutes(pagesRoot, {
@@ -209,384 +207,6 @@ async function parseViteManifest(outputDir, packages) {
209
207
  await fs.rm(viteManifestPath, { force: true });
210
208
  return manifest;
211
209
  }
212
- const require2 = createRequire(import.meta.url);
213
- async function loadProductionPageParts(route, pageModule, jayHtmlContent, projectRoot, tsConfigFilePath, serverBuildDir) {
214
- const exportName = route.componentExport || "page";
215
- const compDefinition = pageModule[exportName] ?? pageModule.default;
216
- const parts = compDefinition ? [{ compDefinition, clientImport: "", clientPart: "" }] : [];
217
- const dirName = path.dirname(route.jayHtmlPath);
218
- const fileName = path.basename(route.jayHtmlPath);
219
- const jayHtmlWithValidations = await parseJayFile(
220
- jayHtmlContent,
221
- fileName,
222
- dirName,
223
- { relativePath: tsConfigFilePath },
224
- JAY_IMPORT_RESOLVER,
225
- projectRoot
226
- );
227
- const jayHtml = checkValidationErrors(jayHtmlWithValidations);
228
- const headlessInstanceComponents = [];
229
- const keyedPartModules = [];
230
- const headlessModuleInfos = [];
231
- const headlessImports = jayHtml.headlessImports ?? [];
232
- getLogger().info(
233
- `[Build] headlessImports for ${fileName}: ${headlessImports.length}, keys: ${Object.keys(jayHtml).join(",")}`
234
- );
235
- for (const headlessImport of headlessImports) {
236
- const module = headlessImport.codeLink.module;
237
- const name = headlessImport.codeLink.names[0].name;
238
- const isLocalModule = module[0] === "." || module[0] === "/";
239
- let modulePath;
240
- if (isLocalModule) {
241
- const sourcePath = path.resolve(dirName, module);
242
- if (serverBuildDir) {
243
- const relativeToSrc = path.relative(path.join(projectRoot, "src"), sourcePath);
244
- let compiledPath = path.join(serverBuildDir, relativeToSrc);
245
- compiledPath = compiledPath.replace(/\.ts$/, ".js");
246
- if (!compiledPath.endsWith(".js")) {
247
- const indexPath = path.join(compiledPath, "index.js");
248
- try {
249
- await fs.access(indexPath);
250
- compiledPath = indexPath;
251
- } catch {
252
- compiledPath += ".js";
253
- }
254
- }
255
- modulePath = compiledPath;
256
- } else {
257
- modulePath = sourcePath;
258
- }
259
- } else {
260
- modulePath = require2.resolve(module, { paths: [dirName] });
261
- }
262
- const headlessModule = await import(modulePath);
263
- const headlessCompDef = headlessModule[name];
264
- if (headlessImport.key) {
265
- const clientModulePath = isLocalModule ? path.resolve(dirName, module) : `${module}/client`;
266
- const ci = headlessImport.contract ? { contractName: headlessImport.contract.name, metadata: headlessImport.metadata } : void 0;
267
- parts.push({
268
- key: headlessImport.key,
269
- compDefinition: headlessCompDef,
270
- clientImport: "",
271
- clientPart: "",
272
- contractInfo: ci
273
- });
274
- keyedPartModules.push({
275
- key: headlessImport.key,
276
- modulePath: clientModulePath,
277
- exportName: name
278
- });
279
- headlessModuleInfos.push({
280
- modulePath,
281
- exportName: name,
282
- isLocal: isLocalModule,
283
- key: headlessImport.key,
284
- contractInfo: ci
285
- });
286
- }
287
- if (!headlessImport.key && headlessImport.contract) {
288
- headlessInstanceComponents.push({
289
- contractName: headlessImport.contractName,
290
- compDefinition: headlessCompDef,
291
- contract: headlessImport.contract
292
- });
293
- headlessModuleInfos.push({
294
- modulePath,
295
- exportName: name,
296
- isLocal: isLocalModule,
297
- contractName: headlessImport.contractName,
298
- propNames: headlessImport.contract.props?.map((p) => p.name) ?? []
299
- });
300
- }
301
- }
302
- const headlessContracts = (jayHtml.headlessImports ?? []).filter((hi) => hi.contract && hi.key).map((hi) => ({
303
- key: hi.key,
304
- contract: hi.contract,
305
- contractPath: hi.contractPath
306
- }));
307
- const jayHtmlForDiscovery = injectHeadfullFSTemplates(
308
- jayHtmlContent,
309
- dirName,
310
- JAY_IMPORT_RESOLVER
311
- );
312
- let discoveredInstances = [];
313
- let forEachInstances = [];
314
- if (headlessInstanceComponents.length > 0) {
315
- const firstDiscovery = discoverHeadlessInstances(jayHtmlForDiscovery);
316
- const contractNames = new Set(headlessInstanceComponents.map((c) => c.contractName));
317
- const withCoords = assignCoordinatesToJayHtml(
318
- firstDiscovery.preRenderedJayHtml,
319
- contractNames
320
- );
321
- const finalDiscovery = discoverHeadlessInstances(withCoords);
322
- discoveredInstances = finalDiscovery.instances;
323
- forEachInstances = finalDiscovery.forEachInstances;
324
- }
325
- return {
326
- parts,
327
- headlessContracts,
328
- headlessInstanceComponents,
329
- discoveredInstances,
330
- forEachInstances,
331
- keyedPartModules,
332
- headlessModuleInfos,
333
- serverTrackByMap: jayHtml.serverTrackByMap,
334
- clientTrackByMap: jayHtml.clientTrackByMap
335
- };
336
- }
337
- function buildPagePartsConfig(pageParts, pageServerModule, pageExportName, buildDir, pageIsPlugin = false) {
338
- const parts = [];
339
- if (pageServerModule) {
340
- parts.push({
341
- modulePath: pageServerModule,
342
- exportName: pageExportName,
343
- source: pageIsPlugin ? "npm" : "local"
344
- });
345
- }
346
- for (const info of pageParts.headlessModuleInfos) {
347
- if (info.key) {
348
- parts.push({
349
- modulePath: info.isLocal ? path.relative(buildDir, info.modulePath) : info.modulePath,
350
- exportName: info.exportName,
351
- source: info.isLocal ? "local" : "npm",
352
- key: info.key,
353
- contractInfo: info.contractInfo
354
- });
355
- }
356
- }
357
- const instanceComponents = [];
358
- for (const info of pageParts.headlessModuleInfos) {
359
- if (info.contractName) {
360
- instanceComponents.push({
361
- modulePath: info.isLocal ? path.relative(buildDir, info.modulePath) : info.modulePath,
362
- exportName: info.exportName,
363
- source: info.isLocal ? "local" : "npm",
364
- contractName: info.contractName,
365
- propNames: info.propNames ?? []
366
- });
367
- }
368
- }
369
- return {
370
- parts,
371
- instanceComponents,
372
- forEachInstances: pageParts.forEachInstances.map((fi) => ({
373
- contractName: fi.contractName,
374
- forEachPath: fi.forEachPath,
375
- trackBy: fi.trackBy,
376
- propBindings: fi.propBindings,
377
- coordinateSuffix: fi.coordinateSuffix
378
- }))
379
- };
380
- }
381
- async function loadPagePartsFromConfig(configPath, buildDir) {
382
- const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
383
- async function importModule(entry) {
384
- if (!entry.modulePath) {
385
- throw new Error(
386
- `Empty modulePath in page-parts.json for "${entry.exportName}" (source: ${entry.source}). Rebuild required.`
387
- );
388
- }
389
- if (entry.source === "local") {
390
- return import(path.join(buildDir, entry.modulePath));
391
- }
392
- return import(entry.modulePath);
393
- }
394
- const parts = [];
395
- for (const entry of config.parts) {
396
- const mod = await importModule(entry);
397
- parts.push({
398
- compDefinition: mod[entry.exportName] ?? mod.default,
399
- key: entry.key,
400
- clientImport: "",
401
- clientPart: "",
402
- contractInfo: entry.contractInfo
403
- });
404
- }
405
- const headlessInstanceComponents = [];
406
- for (const entry of config.instanceComponents) {
407
- const mod = await importModule(entry);
408
- const serveTimeContract = {
409
- props: entry.propNames.map((name) => ({ name }))
410
- };
411
- headlessInstanceComponents.push({
412
- contractName: entry.contractName,
413
- compDefinition: mod[entry.exportName] ?? mod.default,
414
- contract: serveTimeContract
415
- });
416
- }
417
- return {
418
- parts,
419
- headlessContracts: [],
420
- headlessInstanceComponents,
421
- discoveredInstances: [],
422
- forEachInstances: config.forEachInstances,
423
- keyedPartModules: [],
424
- headlessModuleInfos: []
425
- };
426
- }
427
- async function compileServerElement(jayHtmlContent, jayHtmlFilename, jayHtmlDir, outputPath, projectRoot, tsConfigFilePath, sourceDir) {
428
- const jayFile = await parseJayFile(
429
- jayHtmlContent,
430
- jayHtmlFilename,
431
- jayHtmlDir,
432
- { relativePath: tsConfigFilePath },
433
- JAY_IMPORT_RESOLVER,
434
- projectRoot,
435
- sourceDir
436
- );
437
- const parsedJayFile = checkValidationErrors(jayFile);
438
- const serverElementCode = checkValidationErrors(generateServerElementFile(parsedJayFile));
439
- const outputDir = path.dirname(outputPath);
440
- await fs.mkdir(outputDir, { recursive: true });
441
- const tsPath = outputPath.replace(/\.js$/, ".ts");
442
- await fs.writeFile(tsPath, serverElementCode, "utf-8");
443
- const jayOptions = { tsConfigFilePath };
444
- await build({
445
- root: projectRoot,
446
- plugins: [jayRuntime(jayOptions)],
447
- build: {
448
- outDir: outputDir,
449
- emptyOutDir: false,
450
- minify: false,
451
- ssr: true,
452
- rollupOptions: {
453
- input: { [path.basename(outputPath, ".js")]: tsPath },
454
- external: [/^node:/, /^@jay-framework\//],
455
- output: {
456
- entryFileNames: "[name].js",
457
- format: "es"
458
- }
459
- }
460
- },
461
- logLevel: "warn"
462
- });
463
- await fs.rm(tsPath, { force: true });
464
- let cssFile;
465
- const css = parsedJayFile.css;
466
- if (css) {
467
- const cssFilename = path.basename(outputPath, ".server-element.js") + ".css";
468
- const cssPath = path.join(outputDir, cssFilename);
469
- await fs.writeFile(cssPath, css, "utf-8");
470
- cssFile = cssFilename;
471
- }
472
- getLogger().info(`[Build] Compiled server element: ${path.basename(outputPath)}`);
473
- return { cssFile };
474
- }
475
- async function generateHydrationEntry(options) {
476
- const {
477
- jayHtmlPath,
478
- pageModulePath,
479
- pageExportName = "page",
480
- slowViewState,
481
- trackByMap,
482
- outputPath,
483
- keyedParts = [],
484
- clientInits = []
485
- } = options;
486
- const hydrateImport = `${jayHtmlPath}?jay-hydrate`;
487
- const partImports = keyedParts.map((p, i) => `import { ${p.exportName} as keyedPart${i} } from '${p.modulePath}';`).join("\n");
488
- const hasPageModule = pageModulePath && pageExportName;
489
- const pagePartExpr = hasPageModule ? `pagePart && pagePart.comp ? { comp: pagePart.comp, contextMarkers: pagePart.contexts || [] } : null` : `null`;
490
- const partsArray = [
491
- pagePartExpr,
492
- ...keyedParts.map(
493
- (p, i) => `keyedPart${i} && keyedPart${i}.comp ? { comp: keyedPart${i}.comp, contextMarkers: keyedPart${i}.contexts || [], key: '${p.key}' } : null`
494
- )
495
- ];
496
- const pageImport = hasPageModule ? `import { ${pageExportName} as pagePart } from '${pageModulePath}';` : "";
497
- const initImports = clientInits.map((ci, i) => `import { ${ci.exportName} as clientInit${i} } from '${ci.modulePath}';`).join("\n");
498
- const initCalls = clientInits.map(
499
- (ci, i) => ` if (clientInit${i}?._clientInit) await clientInit${i}._clientInit(clientInitData['${ci.key}'] || {});`
500
- ).join("\n");
501
- const hasClientInit = clientInits.length > 0;
502
- const code = `import { hydrateCompositeJayComponent } from '@jay-framework/stack-client-runtime';
503
- import { deepMergeViewStates } from '@jay-framework/view-state-merge';
504
- import { hydrate } from '${hydrateImport}';
505
- ${pageImport}
506
- ${partImports}
507
- ${initImports}
508
-
509
- const slowViewState = ${JSON.stringify(slowViewState)};
510
- const trackByMap = ${JSON.stringify(trackByMap)};
511
-
512
- export async function init(fastViewState, fastCarryForward${hasClientInit ? ", clientInitData" : ""}) {
513
- ${initCalls}
514
- const viewState = deepMergeViewStates(slowViewState, fastViewState, trackByMap);
515
- const target = document.getElementById('target');
516
- const rootElement = target.firstElementChild;
517
- const parts = [
518
- ${partsArray.join(",\n ")}
519
- ].filter(p => p !== null);
520
- const pageComp = hydrateCompositeJayComponent(
521
- hydrate, viewState, fastCarryForward,
522
- parts, trackByMap, rootElement
523
- );
524
- return pageComp({});
525
- }
526
- `;
527
- const outputDir = path.dirname(outputPath);
528
- await fs.mkdir(outputDir, { recursive: true });
529
- await fs.writeFile(outputPath, code, "utf-8");
530
- getLogger().info(`[Build] Generated hydration entry: ${path.basename(outputPath)}`);
531
- }
532
- async function buildInstanceClient(hydrateEntryPath, instanceId, outputDir, projectRoot, jayOptions, minify = true, pagesRoot, buildDir) {
533
- const logger = getLogger();
534
- await fs.mkdir(outputDir, { recursive: true });
535
- const fullJayOptions = {
536
- ...jayOptions,
537
- ...pagesRoot && buildDir ? { pagesRoot, buildFolder: buildDir } : {}
538
- };
539
- await build({
540
- root: projectRoot,
541
- plugins: [...jayStackCompiler(fullJayOptions)],
542
- build: {
543
- outDir: outputDir,
544
- emptyOutDir: false,
545
- minify,
546
- manifest: `${instanceId}-manifest.json`,
547
- rollupOptions: {
548
- input: { [instanceId]: hydrateEntryPath },
549
- external: (id) => id.startsWith("@jay-framework/"),
550
- output: {
551
- entryFileNames: "[name]-[hash].js",
552
- chunkFileNames: "chunks/[name]-[hash].js",
553
- assetFileNames: "[name]-[hash].[ext]",
554
- format: "es"
555
- },
556
- preserveEntrySignatures: "exports-only"
557
- }
558
- },
559
- logLevel: "warn"
560
- });
561
- const manifestPath = path.join(outputDir, `${instanceId}-manifest.json`);
562
- const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
563
- await fs.rm(manifestPath, { force: true });
564
- const entryKey = Object.keys(manifest).find((k) => manifest[k].isEntry);
565
- if (!entryKey) {
566
- throw new Error(`No entry found in instance build manifest for ${instanceId}`);
567
- }
568
- const entry = manifest[entryKey];
569
- const result = {
570
- jsFile: entry.file,
571
- cssFile: entry.css?.[0]
572
- };
573
- logger.info(`[Build] Client bundle: ${result.jsFile}`);
574
- return result;
575
- }
576
- function resolvePackageNameForRoute(compPath) {
577
- const dir = path.dirname(compPath);
578
- for (const candidate of [dir, path.join(dir, "..")]) {
579
- try {
580
- const pkgJson = JSON.parse(
581
- fs$1.readFileSync(path.join(candidate, "package.json"), "utf-8")
582
- );
583
- if (pkgJson.name)
584
- return pkgJson.name;
585
- } catch {
586
- }
587
- }
588
- return void 0;
589
- }
590
210
  function hashParams(params, suffix) {
591
211
  const sorted = Object.keys(params).sort().reduce(
592
212
  (acc, key) => {
@@ -601,7 +221,7 @@ function hashParams(params, suffix) {
601
221
  const input = suffix ? json + ":" + suffix : json;
602
222
  return "_" + crypto.createHash("md5").update(input).digest("hex").substring(0, 8);
603
223
  }
604
- async function buildInstance(route, params, pageModule, ctx) {
224
+ async function buildInstance(route, params, pageModule, ctx, routeServerElementPath, routeCssPath, routeHydratePath, routeClientBundlePath) {
605
225
  const logger = getLogger();
606
226
  const routeDir = route.rawRoute.replace(/^\//, "") || "index";
607
227
  const paramHash = hashParams(params, ctx.rebuildSuffix);
@@ -611,7 +231,7 @@ async function buildInstance(route, params, pageModule, ctx) {
611
231
  await fs.mkdir(backendInstanceDir, { recursive: true });
612
232
  await fs.mkdir(frontendInstanceDir, { recursive: true });
613
233
  const jayHtmlContent = await fs.readFile(route.jayHtmlPath, "utf-8");
614
- const sourceDir = path.dirname(route.jayHtmlPath);
234
+ path.dirname(route.jayHtmlPath);
615
235
  const serverBuildDir = path.join(ctx.backendDir, "server");
616
236
  const pageParts = await loadProductionPageParts(
617
237
  route,
@@ -636,7 +256,7 @@ async function buildInstance(route, params, pageModule, ctx) {
636
256
  let pageIsPlugin = false;
637
257
  if (route.compPath) {
638
258
  if (route.componentExport) {
639
- pageServerModule = route.compPath;
259
+ pageServerModule = route.packageName || route.compPath;
640
260
  pageIsPlugin = true;
641
261
  } else {
642
262
  const relativePath = path.relative(ctx.projectRoot, route.compPath);
@@ -675,197 +295,57 @@ async function buildInstance(route, params, pageModule, ctx) {
675
295
  }
676
296
  const slowViewState = slowResult.rendered;
677
297
  const carryForward = slowResult.carryForward;
678
- let contract;
679
- const contractPath = route.jayHtmlPath.replace(".jay-html", ".jay-contract");
680
- try {
681
- const contractContent = await fs.readFile(contractPath, "utf-8");
682
- const parseResult = parseContract(contractContent, path.basename(contractPath));
683
- if (parseResult.val)
684
- contract = parseResult.val;
685
- } catch {
686
- }
687
- const jayHtmlWithTemplates = injectHeadfullFSTemplates(
688
- jayHtmlContent,
689
- sourceDir,
690
- JAY_IMPORT_RESOLVER
691
- );
692
- const transformResult = slowRenderTransform({
693
- jayHtmlContent: jayHtmlWithTemplates,
694
- slowViewState,
695
- contract,
696
- headlessContracts: pageParts.headlessContracts,
697
- sourceDir,
698
- importResolver: JAY_IMPORT_RESOLVER
699
- });
700
- if (!transformResult.val) {
701
- throw new Error(
702
- `Slow render transform failed for ${route.rawRoute}: ${transformResult.validations.join(", ")}`
298
+ if (pageParts.discoveredInstances.length > 0 && pageParts.headlessInstanceComponents.length > 0) {
299
+ const slowResult2 = await slowRenderInstances(
300
+ pageParts.discoveredInstances,
301
+ pageParts.headlessInstanceComponents
703
302
  );
704
- }
705
- let preRenderedJayHtml = transformResult.val.preRenderedJayHtml;
706
- if (pageParts.headlessInstanceComponents.length > 0) {
707
- const discoveryResult = discoverHeadlessInstances(preRenderedJayHtml);
708
- const htmlWithRefs = discoveryResult.preRenderedJayHtml;
709
- const contractNames = new Set(
710
- pageParts.headlessInstanceComponents.map((c) => c.contractName)
711
- );
712
- preRenderedJayHtml = assignCoordinatesToJayHtml(htmlWithRefs, contractNames);
713
- const finalDiscovery = discoverHeadlessInstances(preRenderedJayHtml);
714
- if (finalDiscovery.instances.length > 0) {
715
- const slowResult2 = await slowRenderInstances(
716
- finalDiscovery.instances,
717
- pageParts.headlessInstanceComponents
718
- );
719
- if (slowResult2) {
720
- const existingInstances = carryForward.__instances || {
721
- discovered: [],
722
- carryForwards: {}
723
- };
724
- carryForward.__instances = {
725
- discovered: [
726
- ...existingInstances.discovered,
727
- ...slowResult2.instancePhaseData.discovered
728
- ],
729
- carryForwards: {
730
- ...existingInstances.carryForwards,
731
- ...slowResult2.instancePhaseData.carryForwards
732
- },
733
- slowViewStates: {
734
- ...existingInstances.slowViewStates || {},
735
- ...slowResult2.instancePhaseData.slowViewStates
736
- }
737
- };
738
- carryForward.__instanceSlowViewStates = {
739
- ...carryForward.__instanceSlowViewStates || {},
740
- ...Object.fromEntries(
741
- slowResult2.resolvedData.map((d) => [
742
- d.coordinate.join("/"),
743
- d.slowViewState
744
- ])
745
- )
746
- };
747
- carryForward.__instanceResolvedData = [
748
- ...carryForward.__instanceResolvedData || [],
749
- ...slowResult2.resolvedData
750
- ];
751
- const pass2Result = resolveHeadlessInstances(
752
- preRenderedJayHtml,
753
- slowResult2.resolvedData,
754
- JAY_IMPORT_RESOLVER
755
- );
756
- if (pass2Result.val) {
757
- preRenderedJayHtml = pass2Result.val;
303
+ if (slowResult2) {
304
+ const existingInstances = carryForward.__instances || {
305
+ discovered: [],
306
+ carryForwards: {}
307
+ };
308
+ carryForward.__instances = {
309
+ discovered: [
310
+ ...existingInstances.discovered,
311
+ ...slowResult2.instancePhaseData.discovered
312
+ ],
313
+ carryForwards: {
314
+ ...existingInstances.carryForwards,
315
+ ...slowResult2.instancePhaseData.carryForwards
316
+ },
317
+ slowViewStates: {
318
+ ...existingInstances.slowViewStates || {},
319
+ ...slowResult2.instancePhaseData.slowViewStates
758
320
  }
759
- }
321
+ };
760
322
  }
761
323
  }
762
- preRenderedJayHtml = preRenderedJayHtml.replace(
763
- /(<script\s+type="application\/jay-headfull"[^>]*\s)(src="([^"]*)")/g,
764
- (_match, prefix, _srcAttr, srcVal) => {
765
- if (path.isAbsolute(srcVal))
766
- return prefix + `src="${srcVal}"`;
767
- const abs = path.resolve(sourceDir, srcVal);
768
- let rel = path.relative(backendInstanceDir, abs);
769
- if (!rel.startsWith("."))
770
- rel = "./" + rel;
771
- return prefix + `src="${rel}"`;
772
- }
773
- );
774
- preRenderedJayHtml = preRenderedJayHtml.replace(
775
- /(<script\s+type="application\/jay-headfull"[^>]*\s)(contract="([^"]*)")/g,
776
- (_match, prefix, _contractAttr, contractVal) => {
777
- if (path.isAbsolute(contractVal))
778
- return prefix + `contract="${contractVal}"`;
779
- const abs = path.resolve(sourceDir, contractVal);
780
- let rel = path.relative(backendInstanceDir, abs);
781
- if (!rel.startsWith("."))
782
- rel = "./" + rel;
783
- return prefix + `contract="${rel}"`;
784
- }
785
- );
786
- const preRenderedPath = path.join(backendInstanceDir, `${instanceId}.jay-html`);
787
- await fs.writeFile(preRenderedPath, preRenderedJayHtml, "utf-8");
788
- const cacheMetadataPath = path.join(backendInstanceDir, `${instanceId}.cache.json`);
324
+ if (pageParts.forEachInstances.length > 0) {
325
+ const existingInstances = carryForward.__instances || {
326
+ discovered: [],
327
+ carryForwards: {}
328
+ };
329
+ existingInstances.forEachInstances = pageParts.forEachInstances;
330
+ carryForward.__instances = existingInstances;
331
+ }
332
+ const cachePath = path.join(backendInstanceDir, `${instanceId}.cache.json`);
789
333
  await fs.writeFile(
790
- cacheMetadataPath,
334
+ cachePath,
791
335
  JSON.stringify({
792
336
  slowViewState,
793
337
  carryForward
794
338
  }),
795
339
  "utf-8"
796
340
  );
797
- logger.info(`[Build] Pre-rendered: ${routeDir}/${instanceId}`);
798
- const serverElementPath = path.join(backendInstanceDir, `${instanceId}.server-element.js`);
799
- const serverElementResult = await compileServerElement(
800
- preRenderedJayHtml,
801
- `${instanceId}.jay-html`,
802
- backendInstanceDir,
803
- serverElementPath,
804
- ctx.projectRoot,
805
- ctx.tsConfigFilePath,
806
- sourceDir
807
- );
808
- const hydrateEntryPath = path.join(backendInstanceDir, `${instanceId}.hydrate-entry.ts`);
809
- const relativeJayHtmlPath = path.relative(backendInstanceDir, preRenderedPath);
810
- let pageModulePath;
811
- let pageExportName;
812
- if (route.componentExport) {
813
- const pkgName = resolvePackageNameForRoute(route.compPath);
814
- pageModulePath = pkgName ? `${pkgName}/client` : "./" + path.relative(backendInstanceDir, route.compPath);
815
- pageExportName = route.componentExport;
816
- } else if (route.compPath) {
817
- pageModulePath = "./" + path.relative(backendInstanceDir, route.compPath);
818
- pageExportName = "page";
819
- } else {
820
- pageModulePath = "";
821
- pageExportName = "";
822
- }
823
- if (pageParts.keyedPartModules.length > 0) {
824
- logger.info(
825
- `[Build] Keyed parts for ${routeDir}: ${pageParts.keyedPartModules.map((p) => p.key).join(", ")}`
826
- );
827
- }
828
- await generateHydrationEntry({
829
- jayHtmlPath: "./" + relativeJayHtmlPath,
830
- pageModulePath,
831
- pageExportName,
832
- slowViewState,
833
- trackByMap: pageParts.clientTrackByMap || {},
834
- outputPath: hydrateEntryPath,
835
- keyedParts: pageParts.keyedPartModules,
836
- clientInits: ctx.clientInits
837
- });
838
- const clientResult = await buildInstanceClient(
839
- hydrateEntryPath,
840
- instanceId,
841
- frontendInstanceDir,
842
- ctx.projectRoot,
843
- ctx.jayOptions,
844
- ctx.minify ?? true,
845
- ctx.pagesRoot,
846
- ctx.buildDir
847
- );
848
- await fs.rm(hydrateEntryPath, { force: true });
849
- const cssFile = clientResult.cssFile || serverElementResult.cssFile;
850
- if (serverElementResult.cssFile && !clientResult.cssFile) {
851
- const srcCss = path.join(backendInstanceDir, serverElementResult.cssFile);
852
- const dstCss = path.join(frontendInstanceDir, serverElementResult.cssFile);
853
- try {
854
- await fs.rename(srcCss, dstCss);
855
- } catch {
856
- await fs.copyFile(srcCss, dstCss);
857
- await fs.rm(srcCss, { force: true });
858
- }
859
- }
341
+ logger.info(`[Build] Instance data: ${routeDir}/${instanceId}`);
342
+ const serverElementPath = routeServerElementPath ? path.join(ctx.backendDir, routeServerElementPath) : path.join(backendInstanceDir, `${instanceId}.server-element.js`);
860
343
  const instanceEntry = {
861
344
  params,
862
- preRenderedPath: path.relative(ctx.backendDir, preRenderedPath),
345
+ cachePath: path.relative(ctx.backendDir, cachePath),
863
346
  serverElementPath: path.relative(ctx.backendDir, serverElementPath),
864
- clientBundlePath: path.relative(
865
- ctx.frontendDir,
866
- path.join(frontendInstanceDir, clientResult.jsFile)
867
- ),
868
- clientCssPath: cssFile ? path.relative(ctx.frontendDir, path.join(frontendInstanceDir, cssFile)) : void 0
347
+ clientBundlePath: routeClientBundlePath || "",
348
+ clientCssPath: routeCssPath
869
349
  };
870
350
  return { status: "success", instanceEntry, slowViewState, carryForward, contracts };
871
351
  }
@@ -979,53 +459,319 @@ async function scanPluginRoutes(projectRoot, projectRoutes) {
979
459
  rawRoute: route.path,
980
460
  jayHtmlPath,
981
461
  compPath,
982
- componentExport
462
+ componentExport,
463
+ packageName: plugin.packageName
983
464
  });
984
465
  logger.info(`[Routes] Plugin "${plugin.manifest.name}" provides route ${route.path}`);
985
466
  }
986
467
  }
987
468
  return pluginRoutes;
988
469
  }
989
- function resolvePluginExport(pluginPath, exportSubpath) {
990
- const normalized = exportSubpath.replace(/^\.\//, "");
991
- const packageJsonPath = path.join(pluginPath, "package.json");
992
- try {
993
- const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
994
- if (packageJson.exports) {
995
- const exportKey = "./" + normalized;
996
- const exportValue = packageJson.exports[exportKey];
997
- if (exportValue) {
998
- const resolved = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
999
- if (resolved)
1000
- return path.join(pluginPath, resolved);
1001
- }
1002
- }
1003
- } catch {
1004
- }
1005
- for (const dir of ["dist", "lib", ""]) {
1006
- const candidate = path.join(pluginPath, dir, normalized);
1007
- try {
1008
- fs$1.accessSync(candidate);
1009
- return candidate;
1010
- } catch {
1011
- }
1012
- }
1013
- return void 0;
470
+ function resolvePluginExport(pluginPath, exportSubpath) {
471
+ const normalized = exportSubpath.replace(/^\.\//, "");
472
+ const packageJsonPath = path.join(pluginPath, "package.json");
473
+ try {
474
+ const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
475
+ if (packageJson.exports) {
476
+ const exportKey = "./" + normalized;
477
+ const exportValue = packageJson.exports[exportKey];
478
+ if (exportValue) {
479
+ const resolved = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
480
+ if (resolved)
481
+ return path.join(pluginPath, resolved);
482
+ }
483
+ }
484
+ } catch {
485
+ }
486
+ for (const dir of ["dist", "lib", ""]) {
487
+ const candidate = path.join(pluginPath, dir, normalized);
488
+ try {
489
+ fs$1.accessSync(candidate);
490
+ return candidate;
491
+ } catch {
492
+ }
493
+ }
494
+ return void 0;
495
+ }
496
+ function resolvePluginModule(pluginPath) {
497
+ const pkgJsonPath = path.join(pluginPath, "package.json");
498
+ try {
499
+ const pkg = JSON.parse(fs$1.readFileSync(pkgJsonPath, "utf-8"));
500
+ const mainExport = pkg.exports?.["."];
501
+ const mainPath = typeof mainExport === "string" ? mainExport : mainExport?.default || mainExport?.import || pkg.main;
502
+ if (mainPath) {
503
+ const resolved = path.join(pluginPath, mainPath);
504
+ if (fs$1.existsSync(resolved))
505
+ return resolved;
506
+ }
507
+ } catch {
508
+ }
509
+ return path.join(pluginPath, "dist", "index.js");
510
+ }
511
+ async function compileServerElement(jayHtmlContent, jayHtmlFilename, jayHtmlDir, outputPath, projectRoot, tsConfigFilePath, sourceDir, minifyCss = true) {
512
+ const jayFile = await parseJayFile(
513
+ jayHtmlContent,
514
+ jayHtmlFilename,
515
+ jayHtmlDir,
516
+ { relativePath: tsConfigFilePath },
517
+ JAY_IMPORT_RESOLVER,
518
+ projectRoot,
519
+ sourceDir
520
+ );
521
+ const parsedJayFile = checkValidationErrors(jayFile);
522
+ const serverElementCode = checkValidationErrors(generateServerElementFile(parsedJayFile));
523
+ const outputDir = path.dirname(outputPath);
524
+ await fs.mkdir(outputDir, { recursive: true });
525
+ const tsPath = outputPath.replace(/\.js$/, ".ts");
526
+ await fs.writeFile(tsPath, serverElementCode, "utf-8");
527
+ const jayOptions = { tsConfigFilePath };
528
+ await build({
529
+ root: projectRoot,
530
+ plugins: [jayRuntime(jayOptions)],
531
+ build: {
532
+ outDir: outputDir,
533
+ emptyOutDir: false,
534
+ minify: false,
535
+ ssr: true,
536
+ rollupOptions: {
537
+ input: { [path.basename(outputPath, ".js")]: tsPath },
538
+ external: [/^node:/, /^@jay-framework\//],
539
+ output: {
540
+ entryFileNames: "[name].js",
541
+ format: "es"
542
+ }
543
+ }
544
+ },
545
+ logLevel: "warn"
546
+ });
547
+ await fs.rm(tsPath, { force: true });
548
+ let cssFile;
549
+ let cssImports;
550
+ const css = parsedJayFile.css;
551
+ if (css) {
552
+ cssImports = extractCssImportUrls(css);
553
+ const cssFilename = path.basename(outputPath, ".server-element.js") + ".css";
554
+ const cssPath = path.join(outputDir, cssFilename);
555
+ if (minifyCss) {
556
+ const minified = await transform(css, { loader: "css", minify: true });
557
+ await fs.writeFile(cssPath, minified.code, "utf-8");
558
+ } else {
559
+ await fs.writeFile(cssPath, css, "utf-8");
560
+ }
561
+ cssFile = cssFilename;
562
+ }
563
+ getLogger().info(`[Build] Compiled server element: ${path.basename(outputPath)}`);
564
+ return { cssFile, cssImports };
565
+ }
566
+ async function compileRouteServerElement(jayHtmlPath, outputPath, projectRoot, tsConfigFilePath, minifyCss = true) {
567
+ const jayHtmlContent = await fs.readFile(jayHtmlPath, "utf-8");
568
+ const sourceDir = path.dirname(jayHtmlPath);
569
+ const outputDir = path.dirname(outputPath);
570
+ let jayHtml = injectHeadfullFSTemplates(jayHtmlContent, sourceDir, JAY_IMPORT_RESOLVER);
571
+ jayHtml = resolveJayHtmlPaths(jayHtml, sourceDir, outputDir);
572
+ return compileServerElement(
573
+ jayHtml,
574
+ path.basename(jayHtmlPath),
575
+ outputDir,
576
+ outputPath,
577
+ projectRoot,
578
+ tsConfigFilePath,
579
+ sourceDir,
580
+ minifyCss
581
+ );
582
+ }
583
+ async function compileRouteHydrateScript(jayHtmlPath, outputDir, projectRoot, tsConfigFilePath, minify = true) {
584
+ const jayHtmlContent = await fs.readFile(jayHtmlPath, "utf-8");
585
+ const sourceDir = path.dirname(jayHtmlPath);
586
+ let jayHtml = injectHeadfullFSTemplates(jayHtmlContent, sourceDir, JAY_IMPORT_RESOLVER);
587
+ jayHtml = resolveJayHtmlPaths(jayHtml, sourceDir, outputDir);
588
+ const jayFile = await parseJayFile(
589
+ jayHtml,
590
+ path.basename(jayHtmlPath),
591
+ outputDir,
592
+ { relativePath: tsConfigFilePath },
593
+ JAY_IMPORT_RESOLVER,
594
+ projectRoot,
595
+ sourceDir
596
+ );
597
+ const parsedJayFile = checkValidationErrors(jayFile);
598
+ const hydrateCode = checkValidationErrors(
599
+ generateElementHydrateFile(parsedJayFile, RuntimeMode.MainTrusted)
600
+ );
601
+ await fs.mkdir(outputDir, { recursive: true });
602
+ const tsPath = path.join(outputDir, "route.hydrate.ts");
603
+ await fs.writeFile(tsPath, hydrateCode, "utf-8");
604
+ const jayOptions = { tsConfigFilePath };
605
+ await build({
606
+ root: projectRoot,
607
+ plugins: [...jayStackCompiler(jayOptions)],
608
+ build: {
609
+ outDir: outputDir,
610
+ emptyOutDir: false,
611
+ minify,
612
+ manifest: "route-hydrate-manifest.json",
613
+ rollupOptions: {
614
+ input: { "route.hydrate": tsPath },
615
+ external: (id) => id.startsWith("@jay-framework/"),
616
+ output: {
617
+ entryFileNames: "[name]-[hash].js",
618
+ format: "es"
619
+ },
620
+ preserveEntrySignatures: "exports-only"
621
+ }
622
+ },
623
+ logLevel: "warn"
624
+ });
625
+ await fs.rm(tsPath, { force: true });
626
+ const manifestPath = path.join(outputDir, "route-hydrate-manifest.json");
627
+ const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
628
+ await fs.rm(manifestPath, { force: true });
629
+ const entryKey = Object.keys(manifest).find((k) => manifest[k].isEntry);
630
+ if (!entryKey)
631
+ throw new Error("No entry in route hydrate manifest");
632
+ const jsFile = manifest[entryKey].file;
633
+ getLogger().info(`[Build] Compiled route hydrate script: ${jsFile}`);
634
+ return { jsFile };
635
+ }
636
+ function resolveJayHtmlPaths(html, sourceDir, targetDir) {
637
+ const root = parse(html, {
638
+ comment: true,
639
+ blockTextElements: { script: true, style: true }
640
+ });
641
+ const rewrite = (el, attr) => {
642
+ const val = el.getAttribute(attr);
643
+ if (val && (val.startsWith("./") || val.startsWith("../"))) {
644
+ const abs = path.resolve(sourceDir, val);
645
+ let rel = path.relative(targetDir, abs);
646
+ if (!rel.startsWith("."))
647
+ rel = "./" + rel;
648
+ el.setAttribute(attr, rel);
649
+ }
650
+ };
651
+ for (const el of root.querySelectorAll('script[type="application/jay-data"]')) {
652
+ rewrite(el, "contract");
653
+ }
654
+ for (const el of root.querySelectorAll('script[type="application/jay-headless"]')) {
655
+ rewrite(el, "src");
656
+ rewrite(el, "contract");
657
+ }
658
+ for (const el of root.querySelectorAll('script[type="application/jay-headfull"]')) {
659
+ rewrite(el, "src");
660
+ rewrite(el, "contract");
661
+ }
662
+ for (const el of root.querySelectorAll('link[rel="stylesheet"]')) {
663
+ rewrite(el, "href");
664
+ }
665
+ return root.toString();
666
+ }
667
+ function extractCssImportUrls(css) {
668
+ const imports = [];
669
+ const re = /@import\s*(?:url\(\s*['"]?([^'")]+)['"]?\s*\)|['"]([^'"]+)['"])/g;
670
+ let match;
671
+ while ((match = re.exec(css)) !== null) {
672
+ const url = match[1] || match[2];
673
+ if (url.startsWith("https://")) {
674
+ imports.push(url);
675
+ }
676
+ }
677
+ return imports;
678
+ }
679
+ async function generateRouteHydrationEntry(options) {
680
+ const {
681
+ hydrateImport,
682
+ pageModulePath,
683
+ pageExportName = "page",
684
+ trackByMap,
685
+ outputPath,
686
+ keyedParts = [],
687
+ clientInits = []
688
+ } = options;
689
+ const partImports = keyedParts.map((p, i) => `import { ${p.exportName} as keyedPart${i} } from '${p.modulePath}';`).join("\n");
690
+ const hasPageModule = pageModulePath && pageExportName;
691
+ const pagePartExpr = hasPageModule ? `pagePart && pagePart.comp ? { comp: pagePart.comp, contextMarkers: pagePart.contexts || [] } : null` : `null`;
692
+ const partsArray = [
693
+ pagePartExpr,
694
+ ...keyedParts.map(
695
+ (p, i) => `keyedPart${i} && keyedPart${i}.comp ? { comp: keyedPart${i}.comp, contextMarkers: keyedPart${i}.contexts || [], key: '${p.key}' } : null`
696
+ )
697
+ ];
698
+ const pageImport = hasPageModule ? `import { ${pageExportName} as pagePart } from '${pageModulePath}';` : "";
699
+ const initImports = clientInits.map((ci, i) => `import { ${ci.exportName} as clientInit${i} } from '${ci.modulePath}';`).join("\n");
700
+ const initCalls = clientInits.map(
701
+ (ci, i) => ` if (clientInit${i}?._clientInit) await clientInit${i}._clientInit(clientInitData['${ci.key}'] || {});`
702
+ ).join("\n");
703
+ const code = `import { hydrateCompositeJayComponent } from '@jay-framework/stack-client-runtime';
704
+ import { deepMergeViewStates } from '@jay-framework/view-state-merge';
705
+ import { hydrate } from '${hydrateImport}';
706
+ ${pageImport}
707
+ ${partImports}
708
+ ${initImports}
709
+
710
+ const trackByMap = ${JSON.stringify(trackByMap)};
711
+
712
+ export async function init(slowViewState, fastViewState, fastCarryForward, clientInitData) {
713
+ ${initCalls}
714
+ const viewState = deepMergeViewStates(slowViewState, fastViewState, trackByMap);
715
+ const target = document.getElementById('target');
716
+ const rootElement = target.firstElementChild;
717
+ const parts = [
718
+ ${partsArray.join(",\n ")}
719
+ ].filter(p => p !== null);
720
+ const pageComp = hydrateCompositeJayComponent(
721
+ hydrate, viewState, fastCarryForward,
722
+ parts, trackByMap, rootElement
723
+ );
724
+ return pageComp({});
725
+ }
726
+ `;
727
+ const outputDir = path.dirname(outputPath);
728
+ await fs.mkdir(outputDir, { recursive: true });
729
+ await fs.writeFile(outputPath, code, "utf-8");
730
+ getLogger().info(`[Build] Generated route hydration entry: ${path.basename(outputPath)}`);
1014
731
  }
1015
- function resolvePluginModule(pluginPath) {
1016
- const pkgJsonPath = path.join(pluginPath, "package.json");
1017
- try {
1018
- const pkg = JSON.parse(fs$1.readFileSync(pkgJsonPath, "utf-8"));
1019
- const mainExport = pkg.exports?.["."];
1020
- const mainPath = typeof mainExport === "string" ? mainExport : mainExport?.default || mainExport?.import || pkg.main;
1021
- if (mainPath) {
1022
- const resolved = path.join(pluginPath, mainPath);
1023
- if (fs$1.existsSync(resolved))
1024
- return resolved;
1025
- }
1026
- } catch {
732
+ async function buildInstanceClient(hydrateEntryPath, instanceId, outputDir, projectRoot, jayOptions, minify = true, pagesRoot, buildDir) {
733
+ const logger = getLogger();
734
+ await fs.mkdir(outputDir, { recursive: true });
735
+ const fullJayOptions = {
736
+ ...jayOptions,
737
+ ...pagesRoot && buildDir ? { pagesRoot, buildFolder: buildDir } : {}
738
+ };
739
+ await build({
740
+ root: projectRoot,
741
+ plugins: [...jayStackCompiler(fullJayOptions)],
742
+ build: {
743
+ outDir: outputDir,
744
+ emptyOutDir: false,
745
+ minify,
746
+ manifest: `${instanceId}-manifest.json`,
747
+ rollupOptions: {
748
+ input: { [instanceId]: hydrateEntryPath },
749
+ external: (id) => id.startsWith("@jay-framework/") || id === "jay-route-hydrate",
750
+ output: {
751
+ entryFileNames: "[name]-[hash].js",
752
+ chunkFileNames: "chunks/[name]-[hash].js",
753
+ assetFileNames: "[name]-[hash].[ext]",
754
+ format: "es"
755
+ },
756
+ preserveEntrySignatures: "exports-only"
757
+ }
758
+ },
759
+ logLevel: "warn"
760
+ });
761
+ const manifestPath = path.join(outputDir, `${instanceId}-manifest.json`);
762
+ const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
763
+ await fs.rm(manifestPath, { force: true });
764
+ const entryKey = Object.keys(manifest).find((k) => manifest[k].isEntry);
765
+ if (!entryKey) {
766
+ throw new Error(`No entry found in instance build manifest for ${instanceId}`);
1027
767
  }
1028
- return path.join(pluginPath, "dist", "index.js");
768
+ const entry = manifest[entryKey];
769
+ const result = {
770
+ jsFile: entry.file,
771
+ cssFile: entry.css?.[0]
772
+ };
773
+ logger.info(`[Build] Client bundle: ${result.jsFile}`);
774
+ return result;
1029
775
  }
1030
776
  function crossProductParams(parts) {
1031
777
  if (parts.length === 0)
@@ -1049,8 +795,8 @@ function crossProductParams(parts) {
1049
795
  const next = parts[i].values;
1050
796
  const combined = [];
1051
797
  for (const a of result) {
1052
- for (const b of next) {
1053
- combined.push({ ...a, ...b });
798
+ for (const b2 of next) {
799
+ combined.push({ ...a, ...b2 });
1054
800
  }
1055
801
  }
1056
802
  result = combined;
@@ -1389,6 +1135,112 @@ async function buildVersion(options) {
1389
1135
  byRoute.set(info, []);
1390
1136
  byRoute.get(info).push(materialized2.params);
1391
1137
  }
1138
+ for (const [info] of byRoute) {
1139
+ const { route, entry } = info.routeEntry;
1140
+ if (!route.jayHtmlPath)
1141
+ continue;
1142
+ const routeDir = route.rawRoute.replace(/^\//, "") || "index";
1143
+ const frontendSafeRouteDir = routeDir.replace(/\[/g, "%5B").replace(/\]/g, "%5D");
1144
+ const backendRouteDir = path.join(backendDir, "pre-rendered", routeDir);
1145
+ const frontendRouteDir = path.join(frontendDir, "pages", frontendSafeRouteDir);
1146
+ await fs.mkdir(backendRouteDir, { recursive: true });
1147
+ await fs.mkdir(frontendRouteDir, { recursive: true });
1148
+ const serverElementPath = path.join(backendRouteDir, "route.server-element.js");
1149
+ try {
1150
+ const seResult = await compileRouteServerElement(
1151
+ route.jayHtmlPath,
1152
+ serverElementPath,
1153
+ options.projectRoot,
1154
+ options.tsConfigFilePath,
1155
+ options.minify ?? true
1156
+ );
1157
+ entry.serverElementPath = path.relative(backendDir, serverElementPath);
1158
+ if (seResult.cssFile) {
1159
+ const srcCss = path.join(backendRouteDir, seResult.cssFile);
1160
+ const dstCss = path.join(frontendRouteDir, seResult.cssFile);
1161
+ try {
1162
+ await fs.rename(srcCss, dstCss);
1163
+ } catch {
1164
+ await fs.copyFile(srcCss, dstCss);
1165
+ await fs.rm(srcCss, { force: true });
1166
+ }
1167
+ entry.routeCssPath = path.relative(frontendDir, dstCss);
1168
+ }
1169
+ if (seResult.cssImports?.length) {
1170
+ entry.cssImports = seResult.cssImports;
1171
+ }
1172
+ logger.important(`[Build] Route server element: ${routeDir}`);
1173
+ } catch (err) {
1174
+ logger.error(`[Build] Route server element FAILED ${route.rawRoute}: ${err.message}`);
1175
+ }
1176
+ try {
1177
+ const hydrateResult = await compileRouteHydrateScript(
1178
+ route.jayHtmlPath,
1179
+ frontendRouteDir,
1180
+ options.projectRoot,
1181
+ options.tsConfigFilePath,
1182
+ options.minify ?? true
1183
+ );
1184
+ entry.routeHydratePath = path.relative(
1185
+ frontendDir,
1186
+ path.join(frontendRouteDir, hydrateResult.jsFile)
1187
+ );
1188
+ logger.important(`[Build] Route hydrate script: ${routeDir}`);
1189
+ } catch (err) {
1190
+ logger.error(`[Build] Route hydrate script FAILED ${route.rawRoute}: ${err.message}`);
1191
+ continue;
1192
+ }
1193
+ try {
1194
+ const ROUTE_HYDRATE_KEY = "jay-route-hydrate";
1195
+ const exportName = route.componentExport || "page";
1196
+ let pageModulePath = "";
1197
+ if (route.compPath) {
1198
+ if (route.componentExport) {
1199
+ const pkgName = route.packageName || route.compPath;
1200
+ pageModulePath = `${pkgName}/client`;
1201
+ } else {
1202
+ pageModulePath = "./" + path.relative(frontendRouteDir, route.compPath);
1203
+ }
1204
+ }
1205
+ const pageParts = await loadProductionPageParts(
1206
+ route,
1207
+ {},
1208
+ await fs.readFile(route.jayHtmlPath, "utf-8"),
1209
+ options.projectRoot,
1210
+ options.tsConfigFilePath,
1211
+ path.join(backendDir, "server")
1212
+ );
1213
+ entry.trackByMap = pageParts.serverTrackByMap || pageParts.clientTrackByMap;
1214
+ const entryPath = path.join(frontendRouteDir, "route.entry.ts");
1215
+ await generateRouteHydrationEntry({
1216
+ hydrateImport: ROUTE_HYDRATE_KEY,
1217
+ pageModulePath,
1218
+ pageExportName: exportName,
1219
+ trackByMap: pageParts.clientTrackByMap || {},
1220
+ outputPath: entryPath,
1221
+ keyedParts: pageParts.keyedPartModules,
1222
+ clientInits
1223
+ });
1224
+ const clientResult = await buildInstanceClient(
1225
+ entryPath,
1226
+ "route.client",
1227
+ frontendRouteDir,
1228
+ options.projectRoot,
1229
+ { tsConfigFilePath: options.tsConfigFilePath },
1230
+ options.minify ?? true,
1231
+ options.pagesRoot,
1232
+ buildDir
1233
+ );
1234
+ await fs.rm(entryPath, { force: true });
1235
+ entry.routeClientBundlePath = path.relative(
1236
+ frontendDir,
1237
+ path.join(frontendRouteDir, clientResult.jsFile)
1238
+ );
1239
+ logger.important(`[Build] Route client bundle: ${routeDir}`);
1240
+ } catch (err) {
1241
+ logger.error(`[Build] Route client bundle FAILED ${route.rawRoute}: ${err.message}`);
1242
+ }
1243
+ }
1392
1244
  for (const [info, paramsList] of byRoute) {
1393
1245
  const { route, entry } = info.routeEntry;
1394
1246
  let pageModule;
@@ -1405,7 +1257,16 @@ async function buildVersion(options) {
1405
1257
  }
1406
1258
  for (const params of paramsList) {
1407
1259
  try {
1408
- const result = await buildInstance(route, params, pageModule, instanceCtx);
1260
+ const result = await buildInstance(
1261
+ route,
1262
+ params,
1263
+ pageModule,
1264
+ instanceCtx,
1265
+ entry.serverElementPath,
1266
+ entry.routeCssPath,
1267
+ entry.routeHydratePath,
1268
+ entry.routeClientBundlePath
1269
+ );
1409
1270
  if (result.status === "success") {
1410
1271
  entry.instances.push(result.instanceEntry);
1411
1272
  if (result.contracts.length > 0 && !entry.contracts) {
@@ -1458,516 +1319,6 @@ async function buildVersion(options) {
1458
1319
  logger.important(`[Build] Done! ${instanceCount} instances built in ${buildDir}`);
1459
1320
  return manifest;
1460
1321
  }
1461
- const CACHE_TAG_START = '<script type="application/jay-cache">';
1462
- const CACHE_TAG_END = "<\/script>";
1463
- class FilesystemArtifactStore {
1464
- constructor(basePath) {
1465
- __publicField(this, "manifestCache");
1466
- __publicField(this, "metadataMtime");
1467
- __publicField(this, "moduleCache", /* @__PURE__ */ new Map());
1468
- this.basePath = basePath;
1469
- }
1470
- async readManifest() {
1471
- const metadataPath = path.join(this.basePath, "build-metadata.json");
1472
- const manifestPath = path.join(this.basePath, "route-manifest.json");
1473
- try {
1474
- const metaStat = await fs.stat(metadataPath);
1475
- if (this.manifestCache && this.metadataMtime === metaStat.mtimeMs) {
1476
- return this.manifestCache.manifest;
1477
- }
1478
- this.metadataMtime = metaStat.mtimeMs;
1479
- } catch {
1480
- }
1481
- const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
1482
- const manifestStat = await fs.stat(manifestPath);
1483
- this.manifestCache = { manifest, mtime: manifestStat.mtimeMs };
1484
- return manifest;
1485
- }
1486
- async readPreRenderedHtml(relativePath) {
1487
- const fullPath = path.join(this.basePath, relativePath);
1488
- const content = await fs.readFile(fullPath, "utf-8");
1489
- const cachePath = fullPath.replace(/\.jay-html$/, ".cache.json");
1490
- try {
1491
- const cacheData = JSON.parse(await fs.readFile(cachePath, "utf-8"));
1492
- return {
1493
- content,
1494
- slowViewState: cacheData.slowViewState || {},
1495
- carryForward: cacheData.carryForward || {}
1496
- };
1497
- } catch {
1498
- return extractCacheMetadata(content);
1499
- }
1500
- }
1501
- async loadServerElement(relativePath) {
1502
- return this.loadModule(relativePath);
1503
- }
1504
- async loadPageModule(relativePath) {
1505
- return this.loadModule(relativePath);
1506
- }
1507
- async readRawFile(relativePath) {
1508
- const fullPath = path.join(this.basePath, relativePath);
1509
- return await fs.readFile(fullPath, "utf-8");
1510
- }
1511
- getAssetPath(relativePath) {
1512
- return path.join(this.basePath, relativePath);
1513
- }
1514
- getBuildDir() {
1515
- return this.basePath;
1516
- }
1517
- async loadModule(relativePath) {
1518
- const fullPath = path.join(this.basePath, relativePath);
1519
- const stat = await fs.stat(fullPath);
1520
- const cached = this.moduleCache.get(relativePath);
1521
- if (cached && stat.mtimeMs === cached.mtime) {
1522
- return cached.module;
1523
- }
1524
- const mod = await import(fullPath + "?t=" + stat.mtimeMs);
1525
- this.moduleCache.set(relativePath, { module: mod, mtime: stat.mtimeMs });
1526
- return mod;
1527
- }
1528
- }
1529
- function extractCacheMetadata(fileContent) {
1530
- const startIdx = fileContent.indexOf(CACHE_TAG_START);
1531
- if (startIdx === -1) {
1532
- return { content: fileContent, slowViewState: {}, carryForward: {} };
1533
- }
1534
- const jsonStart = startIdx + CACHE_TAG_START.length;
1535
- const endIdx = fileContent.indexOf(CACHE_TAG_END, jsonStart);
1536
- if (endIdx === -1) {
1537
- return { content: fileContent, slowViewState: {}, carryForward: {} };
1538
- }
1539
- const jsonStr = fileContent.substring(jsonStart, endIdx);
1540
- const metadata = JSON.parse(jsonStr);
1541
- const tagEnd = endIdx + CACHE_TAG_END.length;
1542
- const afterTag = fileContent[tagEnd] === "\n" ? tagEnd + 1 : tagEnd;
1543
- const content = fileContent.substring(0, startIdx) + fileContent.substring(afterTag);
1544
- return {
1545
- content,
1546
- slowViewState: metadata.slowViewState || {},
1547
- carryForward: metadata.carryForward || {}
1548
- };
1549
- }
1550
- function matchRequest(manifest, pathname) {
1551
- const urlSegments = pathname.split("/").filter((s) => s.length > 0);
1552
- for (const route of manifest.routes) {
1553
- const params = matchSegments(route.segments, urlSegments);
1554
- if (params) {
1555
- const instance = findInstance(route, params);
1556
- if (instance) {
1557
- return { route, instance, params, pathname };
1558
- }
1559
- }
1560
- }
1561
- return void 0;
1562
- }
1563
- function matchSegments(routeSegments, urlSegments) {
1564
- const params = {};
1565
- let urlIdx = 0;
1566
- for (let i = 0; i < routeSegments.length; i++) {
1567
- const seg = routeSegments[i];
1568
- if (seg.type === "static") {
1569
- if (urlIdx >= urlSegments.length || urlSegments[urlIdx] !== seg.value) {
1570
- return void 0;
1571
- }
1572
- urlIdx++;
1573
- } else if (seg.type === "param") {
1574
- if (urlIdx >= urlSegments.length)
1575
- return void 0;
1576
- params[seg.value] = urlSegments[urlIdx];
1577
- urlIdx++;
1578
- } else if (seg.type === "optional") {
1579
- if (urlIdx < urlSegments.length) {
1580
- params[seg.value] = urlSegments[urlIdx];
1581
- urlIdx++;
1582
- }
1583
- } else if (seg.type === "catchAll") {
1584
- if (urlIdx >= urlSegments.length)
1585
- return void 0;
1586
- params[seg.value] = urlSegments.slice(urlIdx).join("/");
1587
- urlIdx = urlSegments.length;
1588
- } else if (seg.type === "optionalCatchAll") {
1589
- if (urlIdx < urlSegments.length) {
1590
- params[seg.value] = urlSegments.slice(urlIdx).join("/");
1591
- urlIdx = urlSegments.length;
1592
- }
1593
- }
1594
- }
1595
- if (urlIdx !== urlSegments.length)
1596
- return void 0;
1597
- return params;
1598
- }
1599
- function findInstance(route, params) {
1600
- const paramNames = new Set(
1601
- route.segments.filter((s) => s.type !== "static").map((s) => s.value)
1602
- );
1603
- return route.instances.find((instance) => {
1604
- for (const name of paramNames) {
1605
- const urlVal = params[name];
1606
- const instVal = instance.params[name];
1607
- if (urlVal === void 0 && instVal === void 0)
1608
- continue;
1609
- if (urlVal !== instVal)
1610
- return false;
1611
- }
1612
- return true;
1613
- });
1614
- }
1615
- function buildImportMap(sharedManifest, publicBasePath, sharedDir = "shared") {
1616
- const imports = {};
1617
- for (const [pkgName, hashedFile] of Object.entries(sharedManifest)) {
1618
- imports[pkgName] = `${publicBasePath}${sharedDir}/${hashedFile}`;
1619
- }
1620
- return imports;
1621
- }
1622
- const pagePartsCache = /* @__PURE__ */ new Map();
1623
- async function getPageParts(route, artifacts, preRenderedPath) {
1624
- const cacheKey = route.pattern;
1625
- const cached = pagePartsCache.get(cacheKey);
1626
- if (cached)
1627
- return cached;
1628
- const routeDir = path.dirname(preRenderedPath);
1629
- const configPath = artifacts.getAssetPath(path.join(routeDir, "page-parts.json"));
1630
- const buildDir = artifacts.getBuildDir();
1631
- const parts = await loadPagePartsFromConfig(configPath, buildDir);
1632
- pagePartsCache.set(cacheKey, parts);
1633
- return parts;
1634
- }
1635
- async function fetchPageRequest(match, manifest, requestUrl, artifacts, staticBaseUrl, cookies = {}) {
1636
- const { route, instance } = match;
1637
- const preRendered = await artifacts.readPreRenderedHtml(instance.preRenderedPath);
1638
- const pageParts = await getPageParts(route, artifacts, instance.preRenderedPath);
1639
- const query = Object.fromEntries(requestUrl.searchParams.entries());
1640
- const fastResult = await renderFastChangingData(
1641
- match.params,
1642
- { params: match.params, query },
1643
- preRendered.carryForward,
1644
- pageParts.parts,
1645
- preRendered.carryForward.__instances,
1646
- pageParts.forEachInstances,
1647
- pageParts.headlessInstanceComponents,
1648
- preRendered.slowViewState,
1649
- query,
1650
- cookies
1651
- );
1652
- if (fastResult.kind === "Redirect3xx") {
1653
- return new Response(null, {
1654
- status: fastResult.status,
1655
- headers: { Location: fastResult.location }
1656
- });
1657
- }
1658
- if (fastResult.kind === "ServerError5xx" || fastResult.kind === "ClientError4xx") {
1659
- return new Response(fastResult.message || "Error", {
1660
- status: fastResult.status
1661
- });
1662
- }
1663
- const fastViewState = fastResult.rendered || {};
1664
- const fastCarryForward = fastResult.carryForward || {};
1665
- const headTagSources = [];
1666
- const slowHeadTags = preRendered.carryForward.__slowHeadTags;
1667
- if (slowHeadTags)
1668
- headTagSources.push(...slowHeadTags);
1669
- const fastHeadTags = fastResult.headTags;
1670
- if (fastHeadTags)
1671
- headTagSources.push(fastHeadTags);
1672
- const headTags = headTagSources.length > 0 ? mergeHeadTags(headTagSources) : [];
1673
- const headTagsHtml = headTags.length > 0 ? serializeHeadTags(headTags) + "\n" : "";
1674
- const fullViewState = deepMergeViewStates(
1675
- preRendered.slowViewState,
1676
- fastViewState,
1677
- route.trackByMap || {}
1678
- );
1679
- const serverElement = await artifacts.loadServerElement(instance.serverElementPath);
1680
- const asyncPromises = [];
1681
- const importMap = buildImportMap(manifest.sharedManifest, staticBaseUrl);
1682
- const modulePreloads = Object.values(importMap).map((url) => ` <link rel="modulepreload" href="${url}" />`).join("\n");
1683
- const cssLink = instance.clientCssPath ? ` <link rel="stylesheet" href="${staticBaseUrl}${instance.clientCssPath}" />` : "";
1684
- const encoder = new TextEncoder();
1685
- const stream = new ReadableStream({
1686
- async start(controller) {
1687
- const write = (s) => controller.enqueue(encoder.encode(s));
1688
- const headParts = [headTagsHtml, modulePreloads, cssLink].filter(Boolean).join("\n");
1689
- write(`<!doctype html>
1690
- <html lang="en">
1691
- <head>
1692
- <meta charset="UTF-8" />
1693
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1694
- ${headParts}
1695
- <script type="importmap">${JSON.stringify({ imports: importMap })}<\/script>
1696
- </head>
1697
- <body>
1698
- <div id="target">`);
1699
- serverElement.renderToStream(fullViewState, {
1700
- write: (chunk) => write(chunk),
1701
- onAsync: (promise, id, templates) => {
1702
- asyncPromises.push(
1703
- promise.then(
1704
- (val) => asyncSwapScript(id, templates.resolved(val)),
1705
- (err) => asyncSwapScript(id, templates.rejected(err))
1706
- )
1707
- );
1708
- }
1709
- });
1710
- write("</div>");
1711
- const asyncScripts = (await Promise.all(asyncPromises)).filter((s) => s).join("");
1712
- if (asyncScripts)
1713
- write(asyncScripts);
1714
- const clientInitData = getClientInitData();
1715
- const clientBundleUrl = `${staticBaseUrl}${instance.clientBundlePath}`;
1716
- write(`
1717
- <script type="module">
1718
- import { init } from '${clientBundleUrl}';
1719
- await init(${JSON.stringify(fastViewState)}, ${JSON.stringify(fastCarryForward)}, ${JSON.stringify(clientInitData)});
1720
- <\/script>
1721
- </body>
1722
- </html>`);
1723
- controller.close();
1724
- }
1725
- });
1726
- const responseHeaders = fastResult.responseHeaders || {};
1727
- return new Response(stream, {
1728
- headers: { "Content-Type": "text/html; charset=utf-8", ...responseHeaders }
1729
- });
1730
- }
1731
- const ACTION_PREFIX = "/_jay/actions/";
1732
- function isActionRequest(pathname) {
1733
- return pathname.startsWith(ACTION_PREFIX);
1734
- }
1735
- async function fetchActionRequest(request, registry = actionRegistry) {
1736
- const url = new URL(request.url);
1737
- const actionName = url.pathname.slice(ACTION_PREFIX.length);
1738
- if (!actionName) {
1739
- return jsonResponse(400, {
1740
- success: false,
1741
- error: {
1742
- code: "MISSING_ACTION_NAME",
1743
- message: "Action name is required",
1744
- isActionError: false
1745
- }
1746
- });
1747
- }
1748
- const action = registry.get(actionName);
1749
- if (!action) {
1750
- return jsonResponse(404, {
1751
- success: false,
1752
- error: {
1753
- code: "ACTION_NOT_FOUND",
1754
- message: `Action '${actionName}' is not registered`,
1755
- isActionError: false
1756
- }
1757
- });
1758
- }
1759
- const requestMethod = request.method.toUpperCase();
1760
- if (requestMethod !== action.method) {
1761
- return jsonResponse(405, {
1762
- success: false,
1763
- error: {
1764
- code: "METHOD_NOT_ALLOWED",
1765
- message: `Action '${actionName}' expects ${action.method}, got ${requestMethod}`,
1766
- isActionError: false
1767
- }
1768
- });
1769
- }
1770
- let input;
1771
- try {
1772
- if (requestMethod === "GET") {
1773
- const inputParam = url.searchParams.get("_input");
1774
- if (inputParam) {
1775
- input = JSON.parse(inputParam);
1776
- } else {
1777
- input = Object.fromEntries(url.searchParams.entries());
1778
- delete input._input;
1779
- }
1780
- } else {
1781
- const text = await request.text();
1782
- input = text ? JSON.parse(text) : {};
1783
- }
1784
- } catch {
1785
- return jsonResponse(400, {
1786
- success: false,
1787
- error: {
1788
- code: "INVALID_INPUT",
1789
- message: "Failed to parse request input",
1790
- isActionError: false
1791
- }
1792
- });
1793
- }
1794
- if (registry.isStreaming(actionName)) {
1795
- const encoder = new TextEncoder();
1796
- const stream = new ReadableStream({
1797
- async start(controller) {
1798
- try {
1799
- const generator = registry.executeStream(actionName, input);
1800
- for await (const chunk of generator) {
1801
- controller.enqueue(encoder.encode(JSON.stringify({ chunk }) + "\n"));
1802
- }
1803
- controller.enqueue(encoder.encode(JSON.stringify({ done: true }) + "\n"));
1804
- } catch (err) {
1805
- controller.enqueue(
1806
- encoder.encode(JSON.stringify({ error: err.message }) + "\n")
1807
- );
1808
- }
1809
- controller.close();
1810
- }
1811
- });
1812
- return new Response(stream, {
1813
- headers: { "Content-Type": "application/x-ndjson" }
1814
- });
1815
- }
1816
- const result = await registry.execute(actionName, input);
1817
- if (result.success) {
1818
- const headers = {};
1819
- if (requestMethod === "GET") {
1820
- const cacheHeaders = registry.getCacheHeaders(actionName);
1821
- if (cacheHeaders) {
1822
- headers["Cache-Control"] = cacheHeaders;
1823
- }
1824
- }
1825
- return jsonResponse(200, { success: true, data: result.data }, headers);
1826
- } else {
1827
- const statusCode = getStatusCode(result.error.code, result.error.isActionError);
1828
- return jsonResponse(statusCode, { success: false, error: result.error });
1829
- }
1830
- }
1831
- function jsonResponse(status, body, extraHeaders = {}) {
1832
- return new Response(JSON.stringify(body), {
1833
- status,
1834
- headers: { "Content-Type": "application/json", ...extraHeaders }
1835
- });
1836
- }
1837
- async function registerActionsFromManifest(actions, buildDir, registry = actionRegistry) {
1838
- const logger = getLogger();
1839
- let count = 0;
1840
- for (const entry of actions) {
1841
- try {
1842
- const modulePath = entry.isPlugin ? entry.packageName : `${buildDir}/${entry.serverModule}`;
1843
- const mod = await import(modulePath);
1844
- for (const [, exported] of Object.entries(mod)) {
1845
- if (isJayAction(exported)) {
1846
- registry.register(exported);
1847
- count++;
1848
- } else if (isJayStreamAction(exported)) {
1849
- registry.registerStream(exported);
1850
- count++;
1851
- }
1852
- }
1853
- } catch (err) {
1854
- logger.error(
1855
- `[Server] Failed to load action module ${entry.serverModule}: ${err.message}`
1856
- );
1857
- }
1858
- }
1859
- logger.info(`[Server] Registered ${count} actions`);
1860
- }
1861
- function getStatusCode(code, isActionError) {
1862
- if (isActionError)
1863
- return 422;
1864
- switch (code) {
1865
- case "ACTION_NOT_FOUND":
1866
- return 404;
1867
- case "INVALID_INPUT":
1868
- case "VALIDATION_ERROR":
1869
- return 400;
1870
- case "UNAUTHORIZED":
1871
- return 401;
1872
- case "FORBIDDEN":
1873
- return 403;
1874
- default:
1875
- return 500;
1876
- }
1877
- }
1878
- const MIME_TYPES = {
1879
- ".js": "application/javascript",
1880
- ".css": "text/css",
1881
- ".json": "application/json",
1882
- ".html": "text/html",
1883
- ".svg": "image/svg+xml",
1884
- ".png": "image/png",
1885
- ".jpg": "image/jpeg",
1886
- ".jpeg": "image/jpeg",
1887
- ".gif": "image/gif",
1888
- ".ico": "image/x-icon",
1889
- ".woff2": "font/woff2",
1890
- ".woff": "font/woff",
1891
- ".ttf": "font/ttf",
1892
- ".webp": "image/webp"
1893
- };
1894
- async function fetchStaticFile(pathname, frontendDir) {
1895
- const normalizedBase = path.resolve(frontendDir);
1896
- for (const candidate of [
1897
- path.join(frontendDir, pathname),
1898
- path.join(frontendDir, "public", pathname)
1899
- ]) {
1900
- const normalizedFile = path.resolve(candidate);
1901
- if (!normalizedFile.startsWith(normalizedBase))
1902
- continue;
1903
- try {
1904
- const content = await fs.readFile(candidate);
1905
- const ext = path.extname(candidate);
1906
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
1907
- const isHashed = /[-][a-zA-Z0-9_-]{6,}\./.test(path.basename(candidate));
1908
- const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
1909
- return new Response(content, {
1910
- headers: {
1911
- "Content-Type": contentType,
1912
- "Content-Length": String(content.length),
1913
- "Cache-Control": cacheControl
1914
- }
1915
- });
1916
- } catch {
1917
- }
1918
- }
1919
- return null;
1920
- }
1921
- async function initializeServices(buildDir, projectRoot, label) {
1922
- const logger = getLogger();
1923
- const { discoverPluginsWithInit, sortPluginsByDependencies } = await import("@jay-framework/stack-server-runtime");
1924
- try {
1925
- const pluginsWithInit = sortPluginsByDependencies(
1926
- await discoverPluginsWithInit({ projectRoot })
1927
- );
1928
- for (const pluginInit of pluginsWithInit) {
1929
- try {
1930
- let modulePath;
1931
- if (pluginInit.isLocal) {
1932
- const pluginDirName = path.basename(pluginInit.pluginPath);
1933
- const initModule = pluginInit.initModule || "index";
1934
- modulePath = path.join(
1935
- buildDir,
1936
- "server",
1937
- "plugins",
1938
- pluginDirName,
1939
- initModule + ".js"
1940
- );
1941
- } else {
1942
- modulePath = pluginInit.packageName;
1943
- }
1944
- const pluginModule = await import(modulePath);
1945
- const init = pluginModule.init || pluginModule[pluginInit.initExport || "init"];
1946
- if (init?._serverInit) {
1947
- logger.info(`[${label}] Running plugin init: ${pluginInit.name}`);
1948
- const data = await init._serverInit();
1949
- if (data)
1950
- setClientInitData(pluginInit.name, data);
1951
- }
1952
- } catch (err) {
1953
- logger.warn(`[${label}] Plugin init failed: ${pluginInit.name}: ${err.message}`);
1954
- }
1955
- }
1956
- } catch {
1957
- }
1958
- const initModulePath = path.join(buildDir, "server", "init.js");
1959
- try {
1960
- const initModule = await import(initModulePath);
1961
- const init = initModule.init || initModule.default;
1962
- if (init?._serverInit) {
1963
- logger.info(`[${label}] Running server init...`);
1964
- const data = await init._serverInit();
1965
- if (data)
1966
- setClientInitData("project", data);
1967
- }
1968
- } catch {
1969
- }
1970
- }
1971
1322
  function toFetchRequest(req) {
1972
1323
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
1973
1324
  const headers = new Headers();
@@ -2397,14 +1748,9 @@ function paramsMatch(instanceParams, targetParams) {
2397
1748
  return Object.entries(targetParams).every(([key, value]) => instanceParams[key] === value);
2398
1749
  }
2399
1750
  function collectInstanceFiles(instance) {
2400
- if (!instance.preRenderedPath)
1751
+ if (!instance.cachePath)
2401
1752
  return [];
2402
- const files = [
2403
- instance.preRenderedPath,
2404
- instance.preRenderedPath.replace(".jay-html", ".cache.json"),
2405
- instance.serverElementPath,
2406
- instance.clientBundlePath
2407
- ];
1753
+ const files = [instance.cachePath, instance.serverElementPath, instance.clientBundlePath];
2408
1754
  if (instance.clientCssPath)
2409
1755
  files.push(instance.clientCssPath);
2410
1756
  return files.filter(Boolean);
@@ -2579,11 +1925,13 @@ export {
2579
1925
  fetchPageRequest,
2580
1926
  fetchStaticFile,
2581
1927
  initializeServices,
1928
+ e as initializeServicesFromModules,
2582
1929
  isActionRequest,
2583
1930
  matchRequest,
2584
1931
  rebuild,
2585
1932
  rebuildContract,
2586
1933
  registerActionsFromManifest,
1934
+ b as registerActionsFromModules,
2587
1935
  resolveContractToRoutes,
2588
1936
  startMainServer,
2589
1937
  startRendererServer