@jay-framework/production-server 0.17.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,20 @@ 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-BKVwxzYb.js";
10
+ import { e, b } from "./init-services-BKVwxzYb.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";
20
17
  import http from "node:http";
21
18
  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";
19
+ import { isJayWebhook } from "@jay-framework/fullstack-component";
20
+ import "@jay-framework/view-state-merge";
21
+ import "@jay-framework/ssr-runtime";
25
22
  async function discoverServerEntries(projectRoot, pagesRoot) {
26
23
  const logger = getLogger();
27
24
  const routes = await scanRoutes(pagesRoot, {
@@ -209,384 +206,6 @@ async function parseViteManifest(outputDir, packages) {
209
206
  await fs.rm(viteManifestPath, { force: true });
210
207
  return manifest;
211
208
  }
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
209
  function hashParams(params, suffix) {
591
210
  const sorted = Object.keys(params).sort().reduce(
592
211
  (acc, key) => {
@@ -601,7 +220,7 @@ function hashParams(params, suffix) {
601
220
  const input = suffix ? json + ":" + suffix : json;
602
221
  return "_" + crypto.createHash("md5").update(input).digest("hex").substring(0, 8);
603
222
  }
604
- async function buildInstance(route, params, pageModule, ctx) {
223
+ async function buildInstance(route, params, pageModule, ctx, routeServerElementPath, routeCssPath, routeHydratePath, routeClientBundlePath) {
605
224
  const logger = getLogger();
606
225
  const routeDir = route.rawRoute.replace(/^\//, "") || "index";
607
226
  const paramHash = hashParams(params, ctx.rebuildSuffix);
@@ -611,7 +230,7 @@ async function buildInstance(route, params, pageModule, ctx) {
611
230
  await fs.mkdir(backendInstanceDir, { recursive: true });
612
231
  await fs.mkdir(frontendInstanceDir, { recursive: true });
613
232
  const jayHtmlContent = await fs.readFile(route.jayHtmlPath, "utf-8");
614
- const sourceDir = path.dirname(route.jayHtmlPath);
233
+ path.dirname(route.jayHtmlPath);
615
234
  const serverBuildDir = path.join(ctx.backendDir, "server");
616
235
  const pageParts = await loadProductionPageParts(
617
236
  route,
@@ -636,7 +255,7 @@ async function buildInstance(route, params, pageModule, ctx) {
636
255
  let pageIsPlugin = false;
637
256
  if (route.compPath) {
638
257
  if (route.componentExport) {
639
- pageServerModule = route.compPath;
258
+ pageServerModule = route.packageName || route.compPath;
640
259
  pageIsPlugin = true;
641
260
  } else {
642
261
  const relativePath = path.relative(ctx.projectRoot, route.compPath);
@@ -675,197 +294,57 @@ async function buildInstance(route, params, pageModule, ctx) {
675
294
  }
676
295
  const slowViewState = slowResult.rendered;
677
296
  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(", ")}`
297
+ if (pageParts.discoveredInstances.length > 0 && pageParts.headlessInstanceComponents.length > 0) {
298
+ const slowResult2 = await slowRenderInstances(
299
+ pageParts.discoveredInstances,
300
+ pageParts.headlessInstanceComponents
703
301
  );
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;
302
+ if (slowResult2) {
303
+ const existingInstances = carryForward.__instances || {
304
+ discovered: [],
305
+ carryForwards: {}
306
+ };
307
+ carryForward.__instances = {
308
+ discovered: [
309
+ ...existingInstances.discovered,
310
+ ...slowResult2.instancePhaseData.discovered
311
+ ],
312
+ carryForwards: {
313
+ ...existingInstances.carryForwards,
314
+ ...slowResult2.instancePhaseData.carryForwards
315
+ },
316
+ slowViewStates: {
317
+ ...existingInstances.slowViewStates || {},
318
+ ...slowResult2.instancePhaseData.slowViewStates
758
319
  }
759
- }
320
+ };
760
321
  }
761
322
  }
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`);
323
+ if (pageParts.forEachInstances.length > 0) {
324
+ const existingInstances = carryForward.__instances || {
325
+ discovered: [],
326
+ carryForwards: {}
327
+ };
328
+ existingInstances.forEachInstances = pageParts.forEachInstances;
329
+ carryForward.__instances = existingInstances;
330
+ }
331
+ const cachePath = path.join(backendInstanceDir, `${instanceId}.cache.json`);
789
332
  await fs.writeFile(
790
- cacheMetadataPath,
333
+ cachePath,
791
334
  JSON.stringify({
792
335
  slowViewState,
793
336
  carryForward
794
337
  }),
795
338
  "utf-8"
796
339
  );
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
- }
340
+ logger.info(`[Build] Instance data: ${routeDir}/${instanceId}`);
341
+ const serverElementPath = routeServerElementPath ? path.join(ctx.backendDir, routeServerElementPath) : path.join(backendInstanceDir, `${instanceId}.server-element.js`);
860
342
  const instanceEntry = {
861
343
  params,
862
- preRenderedPath: path.relative(ctx.backendDir, preRenderedPath),
344
+ cachePath: path.relative(ctx.backendDir, cachePath),
863
345
  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
346
+ clientBundlePath: routeClientBundlePath || "",
347
+ clientCssPath: routeCssPath
869
348
  };
870
349
  return { status: "success", instanceEntry, slowViewState, carryForward, contracts };
871
350
  }
@@ -979,53 +458,299 @@ async function scanPluginRoutes(projectRoot, projectRoutes) {
979
458
  rawRoute: route.path,
980
459
  jayHtmlPath,
981
460
  compPath,
982
- componentExport
461
+ componentExport,
462
+ packageName: plugin.packageName
983
463
  });
984
464
  logger.info(`[Routes] Plugin "${plugin.manifest.name}" provides route ${route.path}`);
985
465
  }
986
466
  }
987
467
  return pluginRoutes;
988
468
  }
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;
469
+ function resolvePluginExport(pluginPath, exportSubpath) {
470
+ const normalized = exportSubpath.replace(/^\.\//, "");
471
+ const packageJsonPath = path.join(pluginPath, "package.json");
472
+ try {
473
+ const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
474
+ if (packageJson.exports) {
475
+ const exportKey = "./" + normalized;
476
+ const exportValue = packageJson.exports[exportKey];
477
+ if (exportValue) {
478
+ const resolved = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
479
+ if (resolved)
480
+ return path.join(pluginPath, resolved);
481
+ }
482
+ }
483
+ } catch {
484
+ }
485
+ for (const dir of ["dist", "lib", ""]) {
486
+ const candidate = path.join(pluginPath, dir, normalized);
487
+ try {
488
+ fs$1.accessSync(candidate);
489
+ return candidate;
490
+ } catch {
491
+ }
492
+ }
493
+ return void 0;
494
+ }
495
+ function resolvePluginModule(pluginPath) {
496
+ const pkgJsonPath = path.join(pluginPath, "package.json");
497
+ try {
498
+ const pkg = JSON.parse(fs$1.readFileSync(pkgJsonPath, "utf-8"));
499
+ const mainExport = pkg.exports?.["."];
500
+ const mainPath = typeof mainExport === "string" ? mainExport : mainExport?.default || mainExport?.import || pkg.main;
501
+ if (mainPath) {
502
+ const resolved = path.join(pluginPath, mainPath);
503
+ if (fs$1.existsSync(resolved))
504
+ return resolved;
505
+ }
506
+ } catch {
507
+ }
508
+ return path.join(pluginPath, "dist", "index.js");
509
+ }
510
+ async function compileServerElement(jayHtmlContent, jayHtmlFilename, jayHtmlDir, outputPath, projectRoot, tsConfigFilePath, sourceDir) {
511
+ const jayFile = await parseJayFile(
512
+ jayHtmlContent,
513
+ jayHtmlFilename,
514
+ jayHtmlDir,
515
+ { relativePath: tsConfigFilePath },
516
+ JAY_IMPORT_RESOLVER,
517
+ projectRoot,
518
+ sourceDir
519
+ );
520
+ const parsedJayFile = checkValidationErrors(jayFile);
521
+ const serverElementCode = checkValidationErrors(generateServerElementFile(parsedJayFile));
522
+ const outputDir = path.dirname(outputPath);
523
+ await fs.mkdir(outputDir, { recursive: true });
524
+ const tsPath = outputPath.replace(/\.js$/, ".ts");
525
+ await fs.writeFile(tsPath, serverElementCode, "utf-8");
526
+ const jayOptions = { tsConfigFilePath };
527
+ await build({
528
+ root: projectRoot,
529
+ plugins: [jayRuntime(jayOptions)],
530
+ build: {
531
+ outDir: outputDir,
532
+ emptyOutDir: false,
533
+ minify: false,
534
+ ssr: true,
535
+ rollupOptions: {
536
+ input: { [path.basename(outputPath, ".js")]: tsPath },
537
+ external: [/^node:/, /^@jay-framework\//],
538
+ output: {
539
+ entryFileNames: "[name].js",
540
+ format: "es"
541
+ }
542
+ }
543
+ },
544
+ logLevel: "warn"
545
+ });
546
+ await fs.rm(tsPath, { force: true });
547
+ let cssFile;
548
+ const css = parsedJayFile.css;
549
+ if (css) {
550
+ const cssFilename = path.basename(outputPath, ".server-element.js") + ".css";
551
+ const cssPath = path.join(outputDir, cssFilename);
552
+ await fs.writeFile(cssPath, css, "utf-8");
553
+ cssFile = cssFilename;
554
+ }
555
+ getLogger().info(`[Build] Compiled server element: ${path.basename(outputPath)}`);
556
+ return { cssFile };
557
+ }
558
+ async function compileRouteServerElement(jayHtmlPath, outputPath, projectRoot, tsConfigFilePath) {
559
+ const jayHtmlContent = await fs.readFile(jayHtmlPath, "utf-8");
560
+ const sourceDir = path.dirname(jayHtmlPath);
561
+ const outputDir = path.dirname(outputPath);
562
+ let jayHtml = injectHeadfullFSTemplates(jayHtmlContent, sourceDir, JAY_IMPORT_RESOLVER);
563
+ jayHtml = resolveJayHtmlPaths(jayHtml, sourceDir, outputDir);
564
+ return compileServerElement(
565
+ jayHtml,
566
+ path.basename(jayHtmlPath),
567
+ outputDir,
568
+ outputPath,
569
+ projectRoot,
570
+ tsConfigFilePath,
571
+ sourceDir
572
+ );
573
+ }
574
+ async function compileRouteHydrateScript(jayHtmlPath, outputDir, projectRoot, tsConfigFilePath, minify = true) {
575
+ const jayHtmlContent = await fs.readFile(jayHtmlPath, "utf-8");
576
+ const sourceDir = path.dirname(jayHtmlPath);
577
+ let jayHtml = injectHeadfullFSTemplates(jayHtmlContent, sourceDir, JAY_IMPORT_RESOLVER);
578
+ jayHtml = resolveJayHtmlPaths(jayHtml, sourceDir, outputDir);
579
+ const jayFile = await parseJayFile(
580
+ jayHtml,
581
+ path.basename(jayHtmlPath),
582
+ outputDir,
583
+ { relativePath: tsConfigFilePath },
584
+ JAY_IMPORT_RESOLVER,
585
+ projectRoot,
586
+ sourceDir
587
+ );
588
+ const parsedJayFile = checkValidationErrors(jayFile);
589
+ const hydrateCode = checkValidationErrors(
590
+ generateElementHydrateFile(parsedJayFile, RuntimeMode.MainTrusted)
591
+ );
592
+ await fs.mkdir(outputDir, { recursive: true });
593
+ const tsPath = path.join(outputDir, "route.hydrate.ts");
594
+ await fs.writeFile(tsPath, hydrateCode, "utf-8");
595
+ const jayOptions = { tsConfigFilePath };
596
+ await build({
597
+ root: projectRoot,
598
+ plugins: [...jayStackCompiler(jayOptions)],
599
+ build: {
600
+ outDir: outputDir,
601
+ emptyOutDir: false,
602
+ minify,
603
+ manifest: "route-hydrate-manifest.json",
604
+ rollupOptions: {
605
+ input: { "route.hydrate": tsPath },
606
+ external: (id) => id.startsWith("@jay-framework/"),
607
+ output: {
608
+ entryFileNames: "[name]-[hash].js",
609
+ format: "es"
610
+ },
611
+ preserveEntrySignatures: "exports-only"
612
+ }
613
+ },
614
+ logLevel: "warn"
615
+ });
616
+ await fs.rm(tsPath, { force: true });
617
+ const manifestPath = path.join(outputDir, "route-hydrate-manifest.json");
618
+ const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
619
+ await fs.rm(manifestPath, { force: true });
620
+ const entryKey = Object.keys(manifest).find((k) => manifest[k].isEntry);
621
+ if (!entryKey)
622
+ throw new Error("No entry in route hydrate manifest");
623
+ const jsFile = manifest[entryKey].file;
624
+ getLogger().info(`[Build] Compiled route hydrate script: ${jsFile}`);
625
+ return { jsFile };
626
+ }
627
+ function resolveJayHtmlPaths(html, sourceDir, targetDir) {
628
+ const root = parse(html, {
629
+ comment: true,
630
+ blockTextElements: { script: true, style: true }
631
+ });
632
+ const rewrite = (el, attr) => {
633
+ const val = el.getAttribute(attr);
634
+ if (val && (val.startsWith("./") || val.startsWith("../"))) {
635
+ const abs = path.resolve(sourceDir, val);
636
+ let rel = path.relative(targetDir, abs);
637
+ if (!rel.startsWith("."))
638
+ rel = "./" + rel;
639
+ el.setAttribute(attr, rel);
640
+ }
641
+ };
642
+ for (const el of root.querySelectorAll('script[type="application/jay-data"]')) {
643
+ rewrite(el, "contract");
644
+ }
645
+ for (const el of root.querySelectorAll('script[type="application/jay-headless"]')) {
646
+ rewrite(el, "src");
647
+ rewrite(el, "contract");
648
+ }
649
+ for (const el of root.querySelectorAll('script[type="application/jay-headfull"]')) {
650
+ rewrite(el, "src");
651
+ rewrite(el, "contract");
652
+ }
653
+ for (const el of root.querySelectorAll('link[rel="stylesheet"]')) {
654
+ rewrite(el, "href");
655
+ }
656
+ return root.toString();
657
+ }
658
+ async function generateRouteHydrationEntry(options) {
659
+ const {
660
+ hydrateImport,
661
+ pageModulePath,
662
+ pageExportName = "page",
663
+ trackByMap,
664
+ outputPath,
665
+ keyedParts = [],
666
+ clientInits = []
667
+ } = options;
668
+ const partImports = keyedParts.map((p, i) => `import { ${p.exportName} as keyedPart${i} } from '${p.modulePath}';`).join("\n");
669
+ const hasPageModule = pageModulePath && pageExportName;
670
+ const pagePartExpr = hasPageModule ? `pagePart && pagePart.comp ? { comp: pagePart.comp, contextMarkers: pagePart.contexts || [] } : null` : `null`;
671
+ const partsArray = [
672
+ pagePartExpr,
673
+ ...keyedParts.map(
674
+ (p, i) => `keyedPart${i} && keyedPart${i}.comp ? { comp: keyedPart${i}.comp, contextMarkers: keyedPart${i}.contexts || [], key: '${p.key}' } : null`
675
+ )
676
+ ];
677
+ const pageImport = hasPageModule ? `import { ${pageExportName} as pagePart } from '${pageModulePath}';` : "";
678
+ const initImports = clientInits.map((ci, i) => `import { ${ci.exportName} as clientInit${i} } from '${ci.modulePath}';`).join("\n");
679
+ const initCalls = clientInits.map(
680
+ (ci, i) => ` if (clientInit${i}?._clientInit) await clientInit${i}._clientInit(clientInitData['${ci.key}'] || {});`
681
+ ).join("\n");
682
+ const code = `import { hydrateCompositeJayComponent } from '@jay-framework/stack-client-runtime';
683
+ import { deepMergeViewStates } from '@jay-framework/view-state-merge';
684
+ import { hydrate } from '${hydrateImport}';
685
+ ${pageImport}
686
+ ${partImports}
687
+ ${initImports}
688
+
689
+ const trackByMap = ${JSON.stringify(trackByMap)};
690
+
691
+ export async function init(slowViewState, fastViewState, fastCarryForward, clientInitData) {
692
+ ${initCalls}
693
+ const viewState = deepMergeViewStates(slowViewState, fastViewState, trackByMap);
694
+ const target = document.getElementById('target');
695
+ const rootElement = target.firstElementChild;
696
+ const parts = [
697
+ ${partsArray.join(",\n ")}
698
+ ].filter(p => p !== null);
699
+ const pageComp = hydrateCompositeJayComponent(
700
+ hydrate, viewState, fastCarryForward,
701
+ parts, trackByMap, rootElement
702
+ );
703
+ return pageComp({});
704
+ }
705
+ `;
706
+ const outputDir = path.dirname(outputPath);
707
+ await fs.mkdir(outputDir, { recursive: true });
708
+ await fs.writeFile(outputPath, code, "utf-8");
709
+ getLogger().info(`[Build] Generated route hydration entry: ${path.basename(outputPath)}`);
1014
710
  }
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 {
711
+ async function buildInstanceClient(hydrateEntryPath, instanceId, outputDir, projectRoot, jayOptions, minify = true, pagesRoot, buildDir) {
712
+ const logger = getLogger();
713
+ await fs.mkdir(outputDir, { recursive: true });
714
+ const fullJayOptions = {
715
+ ...jayOptions,
716
+ ...pagesRoot && buildDir ? { pagesRoot, buildFolder: buildDir } : {}
717
+ };
718
+ await build({
719
+ root: projectRoot,
720
+ plugins: [...jayStackCompiler(fullJayOptions)],
721
+ build: {
722
+ outDir: outputDir,
723
+ emptyOutDir: false,
724
+ minify,
725
+ manifest: `${instanceId}-manifest.json`,
726
+ rollupOptions: {
727
+ input: { [instanceId]: hydrateEntryPath },
728
+ external: (id) => id.startsWith("@jay-framework/") || id === "jay-route-hydrate",
729
+ output: {
730
+ entryFileNames: "[name]-[hash].js",
731
+ chunkFileNames: "chunks/[name]-[hash].js",
732
+ assetFileNames: "[name]-[hash].[ext]",
733
+ format: "es"
734
+ },
735
+ preserveEntrySignatures: "exports-only"
736
+ }
737
+ },
738
+ logLevel: "warn"
739
+ });
740
+ const manifestPath = path.join(outputDir, `${instanceId}-manifest.json`);
741
+ const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
742
+ await fs.rm(manifestPath, { force: true });
743
+ const entryKey = Object.keys(manifest).find((k) => manifest[k].isEntry);
744
+ if (!entryKey) {
745
+ throw new Error(`No entry found in instance build manifest for ${instanceId}`);
1027
746
  }
1028
- return path.join(pluginPath, "dist", "index.js");
747
+ const entry = manifest[entryKey];
748
+ const result = {
749
+ jsFile: entry.file,
750
+ cssFile: entry.css?.[0]
751
+ };
752
+ logger.info(`[Build] Client bundle: ${result.jsFile}`);
753
+ return result;
1029
754
  }
1030
755
  function crossProductParams(parts) {
1031
756
  if (parts.length === 0)
@@ -1049,8 +774,8 @@ function crossProductParams(parts) {
1049
774
  const next = parts[i].values;
1050
775
  const combined = [];
1051
776
  for (const a of result) {
1052
- for (const b of next) {
1053
- combined.push({ ...a, ...b });
777
+ for (const b2 of next) {
778
+ combined.push({ ...a, ...b2 });
1054
779
  }
1055
780
  }
1056
781
  result = combined;
@@ -1389,6 +1114,108 @@ async function buildVersion(options) {
1389
1114
  byRoute.set(info, []);
1390
1115
  byRoute.get(info).push(materialized2.params);
1391
1116
  }
1117
+ for (const [info] of byRoute) {
1118
+ const { route, entry } = info.routeEntry;
1119
+ if (!route.jayHtmlPath)
1120
+ continue;
1121
+ const routeDir = route.rawRoute.replace(/^\//, "") || "index";
1122
+ const frontendSafeRouteDir = routeDir.replace(/\[/g, "%5B").replace(/\]/g, "%5D");
1123
+ const backendRouteDir = path.join(backendDir, "pre-rendered", routeDir);
1124
+ const frontendRouteDir = path.join(frontendDir, "pages", frontendSafeRouteDir);
1125
+ await fs.mkdir(backendRouteDir, { recursive: true });
1126
+ await fs.mkdir(frontendRouteDir, { recursive: true });
1127
+ const serverElementPath = path.join(backendRouteDir, "route.server-element.js");
1128
+ try {
1129
+ const seResult = await compileRouteServerElement(
1130
+ route.jayHtmlPath,
1131
+ serverElementPath,
1132
+ options.projectRoot,
1133
+ options.tsConfigFilePath
1134
+ );
1135
+ entry.serverElementPath = path.relative(backendDir, serverElementPath);
1136
+ if (seResult.cssFile) {
1137
+ const srcCss = path.join(backendRouteDir, seResult.cssFile);
1138
+ const dstCss = path.join(frontendRouteDir, seResult.cssFile);
1139
+ try {
1140
+ await fs.rename(srcCss, dstCss);
1141
+ } catch {
1142
+ await fs.copyFile(srcCss, dstCss);
1143
+ await fs.rm(srcCss, { force: true });
1144
+ }
1145
+ entry.routeCssPath = path.relative(frontendDir, dstCss);
1146
+ }
1147
+ logger.important(`[Build] Route server element: ${routeDir}`);
1148
+ } catch (err) {
1149
+ logger.error(`[Build] Route server element FAILED ${route.rawRoute}: ${err.message}`);
1150
+ }
1151
+ try {
1152
+ const hydrateResult = await compileRouteHydrateScript(
1153
+ route.jayHtmlPath,
1154
+ frontendRouteDir,
1155
+ options.projectRoot,
1156
+ options.tsConfigFilePath,
1157
+ options.minify ?? true
1158
+ );
1159
+ entry.routeHydratePath = path.relative(
1160
+ frontendDir,
1161
+ path.join(frontendRouteDir, hydrateResult.jsFile)
1162
+ );
1163
+ logger.important(`[Build] Route hydrate script: ${routeDir}`);
1164
+ } catch (err) {
1165
+ logger.error(`[Build] Route hydrate script FAILED ${route.rawRoute}: ${err.message}`);
1166
+ continue;
1167
+ }
1168
+ try {
1169
+ const ROUTE_HYDRATE_KEY = "jay-route-hydrate";
1170
+ const exportName = route.componentExport || "page";
1171
+ let pageModulePath = "";
1172
+ if (route.compPath) {
1173
+ if (route.componentExport) {
1174
+ const pkgName = route.packageName || route.compPath;
1175
+ pageModulePath = `${pkgName}/client`;
1176
+ } else {
1177
+ pageModulePath = "./" + path.relative(frontendRouteDir, route.compPath);
1178
+ }
1179
+ }
1180
+ const pageParts = await loadProductionPageParts(
1181
+ route,
1182
+ {},
1183
+ await fs.readFile(route.jayHtmlPath, "utf-8"),
1184
+ options.projectRoot,
1185
+ options.tsConfigFilePath,
1186
+ path.join(backendDir, "server")
1187
+ );
1188
+ entry.trackByMap = pageParts.serverTrackByMap || pageParts.clientTrackByMap;
1189
+ const entryPath = path.join(frontendRouteDir, "route.entry.ts");
1190
+ await generateRouteHydrationEntry({
1191
+ hydrateImport: ROUTE_HYDRATE_KEY,
1192
+ pageModulePath,
1193
+ pageExportName: exportName,
1194
+ trackByMap: pageParts.clientTrackByMap || {},
1195
+ outputPath: entryPath,
1196
+ keyedParts: pageParts.keyedPartModules,
1197
+ clientInits
1198
+ });
1199
+ const clientResult = await buildInstanceClient(
1200
+ entryPath,
1201
+ "route.client",
1202
+ frontendRouteDir,
1203
+ options.projectRoot,
1204
+ { tsConfigFilePath: options.tsConfigFilePath },
1205
+ options.minify ?? true,
1206
+ options.pagesRoot,
1207
+ buildDir
1208
+ );
1209
+ await fs.rm(entryPath, { force: true });
1210
+ entry.routeClientBundlePath = path.relative(
1211
+ frontendDir,
1212
+ path.join(frontendRouteDir, clientResult.jsFile)
1213
+ );
1214
+ logger.important(`[Build] Route client bundle: ${routeDir}`);
1215
+ } catch (err) {
1216
+ logger.error(`[Build] Route client bundle FAILED ${route.rawRoute}: ${err.message}`);
1217
+ }
1218
+ }
1392
1219
  for (const [info, paramsList] of byRoute) {
1393
1220
  const { route, entry } = info.routeEntry;
1394
1221
  let pageModule;
@@ -1405,7 +1232,16 @@ async function buildVersion(options) {
1405
1232
  }
1406
1233
  for (const params of paramsList) {
1407
1234
  try {
1408
- const result = await buildInstance(route, params, pageModule, instanceCtx);
1235
+ const result = await buildInstance(
1236
+ route,
1237
+ params,
1238
+ pageModule,
1239
+ instanceCtx,
1240
+ entry.serverElementPath,
1241
+ entry.routeCssPath,
1242
+ entry.routeHydratePath,
1243
+ entry.routeClientBundlePath
1244
+ );
1409
1245
  if (result.status === "success") {
1410
1246
  entry.instances.push(result.instanceEntry);
1411
1247
  if (result.contracts.length > 0 && !entry.contracts) {
@@ -1458,516 +1294,6 @@ async function buildVersion(options) {
1458
1294
  logger.important(`[Build] Done! ${instanceCount} instances built in ${buildDir}`);
1459
1295
  return manifest;
1460
1296
  }
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
1297
  function toFetchRequest(req) {
1972
1298
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
1973
1299
  const headers = new Headers();
@@ -2397,14 +1723,9 @@ function paramsMatch(instanceParams, targetParams) {
2397
1723
  return Object.entries(targetParams).every(([key, value]) => instanceParams[key] === value);
2398
1724
  }
2399
1725
  function collectInstanceFiles(instance) {
2400
- if (!instance.preRenderedPath)
1726
+ if (!instance.cachePath)
2401
1727
  return [];
2402
- const files = [
2403
- instance.preRenderedPath,
2404
- instance.preRenderedPath.replace(".jay-html", ".cache.json"),
2405
- instance.serverElementPath,
2406
- instance.clientBundlePath
2407
- ];
1728
+ const files = [instance.cachePath, instance.serverElementPath, instance.clientBundlePath];
2408
1729
  if (instance.clientCssPath)
2409
1730
  files.push(instance.clientCssPath);
2410
1731
  return files.filter(Boolean);
@@ -2579,11 +1900,13 @@ export {
2579
1900
  fetchPageRequest,
2580
1901
  fetchStaticFile,
2581
1902
  initializeServices,
1903
+ e as initializeServicesFromModules,
2582
1904
  isActionRequest,
2583
1905
  matchRequest,
2584
1906
  rebuild,
2585
1907
  rebuildContract,
2586
1908
  registerActionsFromManifest,
1909
+ b as registerActionsFromModules,
2587
1910
  resolveContractToRoutes,
2588
1911
  startMainServer,
2589
1912
  startRendererServer