@jay-framework/dev-server 0.12.0 → 0.14.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +7 -3
  2. package/dist/index.js +246 -256
  3. package/package.json +18 -14
package/dist/index.d.ts CHANGED
@@ -13,12 +13,16 @@ interface DevServerOptions {
13
13
  pagesRootFolder?: string;
14
14
  /**
15
15
  * Folder where build artifacts are stored.
16
- * Pre-rendered jay-html files are written to `<buildFolder>/slow-render-cache/`.
16
+ * Pre-rendered jay-html files are written to `<buildFolder>/pre-rendered/`.
17
17
  * Defaults to `<projectRootFolder>/build`.
18
18
  */
19
19
  buildFolder?: string;
20
20
  jayRollupConfig: JayRollupConfig;
21
- dontCacheSlowly: boolean;
21
+ /**
22
+ * Disable server-side rendering. When true, pages are served as client-only
23
+ * (element target, no hydration). Useful for development without SSR overhead.
24
+ */
25
+ disableSSR?: boolean;
22
26
  /**
23
27
  * Disable automation integration.
24
28
  * When false (default), pages are wrapped with automation API for dev tooling.
@@ -191,7 +195,7 @@ declare function createViteServer(options: CreateViteServerOptions): Promise<Vit
191
195
  *
192
196
  * This is a convenience wrapper around createViteServer with CLI-appropriate defaults.
193
197
  * Disables dependency optimization and ignores the build/ folder to avoid errors
194
- * from stale build artifacts (e.g., build/client-scripts/ referencing build/slow-render-cache/).
198
+ * from stale build artifacts (e.g., build/debug/client-entry/ referencing build/pre-rendered/).
195
199
  */
196
200
  declare function createViteForCli(options: {
197
201
  projectRoot: string;
package/dist/index.js CHANGED
@@ -10,12 +10,12 @@ import { createRequire } from "module";
10
10
  import "@jay-framework/compiler-shared";
11
11
  import * as path from "node:path";
12
12
  import path__default from "node:path";
13
- import { discoverHeadlessInstances, parseContract, slowRenderTransform, JAY_IMPORT_RESOLVER, resolveHeadlessInstances } from "@jay-framework/compiler-jay-html";
13
+ import { parseContract, slowRenderTransform, JAY_IMPORT_RESOLVER, discoverHeadlessInstances, resolveHeadlessInstances } from "@jay-framework/compiler-jay-html";
14
14
  import { getLogger, getDevLogger } from "@jay-framework/logger";
15
15
  import { createRequire as createRequire$1 } from "node:module";
16
16
  import * as fs from "node:fs";
17
17
  import { scanRoutes, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
18
- import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, getServiceRegistry, materializeContracts, loadPageParts, renderFastChangingData, slowRenderInstances, generateClientScript, getClientInitData, resolveServices } from "@jay-framework/stack-server-runtime";
18
+ import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, invalidateServerElementCache, getServiceRegistry, materializeContracts, loadPageParts, renderFastChangingData, generateClientScript, getClientInitData, generateSSRPageHtml, validateForEachInstances, slowRenderInstances } from "@jay-framework/stack-server-runtime";
19
19
  import fs$1 from "node:fs/promises";
20
20
  import { pathToFileURL } from "node:url";
21
21
  const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c$1 = new Proxy(e$1, {
@@ -1366,7 +1366,7 @@ async function createViteServer(options) {
1366
1366
  } = options;
1367
1367
  const vite = await createServer({
1368
1368
  // Don't start HTTP server - we use middleware mode
1369
- server: { middlewareMode: true },
1369
+ server: { middlewareMode: true, watch: { ignored: ["**/build/**"] } },
1370
1370
  // Use Jay Stack compiler for .jay-html and other custom transforms
1371
1371
  plugins: [...jayStackCompiler(jayRollupConfig)],
1372
1372
  // Custom app type (no default middleware)
@@ -1401,9 +1401,7 @@ async function createViteForCli(options) {
1401
1401
  ignored: ["**/build/**"]
1402
1402
  }
1403
1403
  },
1404
- plugins: [
1405
- ...jayStackCompiler({ tsConfigFilePath })
1406
- ],
1404
+ plugins: [...jayStackCompiler({ tsConfigFilePath })],
1407
1405
  appType: "custom",
1408
1406
  root: projectRoot,
1409
1407
  ssr: {
@@ -1843,7 +1841,7 @@ function defaults(options) {
1843
1841
  pagesRootFolder,
1844
1842
  projectRootFolder,
1845
1843
  buildFolder,
1846
- dontCacheSlowly: options.dontCacheSlowly,
1844
+ disableSSR: options.disableSSR,
1847
1845
  jayRollupConfig: {
1848
1846
  ...options.jayRollupConfig || {},
1849
1847
  tsConfigFilePath
@@ -1864,7 +1862,12 @@ function filterPluginsForPage(allPluginClientInits, allPluginsWithInit, usedPack
1864
1862
  pluginsByPackage.set(plugin.packageName, plugin);
1865
1863
  }
1866
1864
  const expandedPackages = new Set(usedPackages);
1867
- const toProcess = [...usedPackages];
1865
+ for (const plugin of allPluginsWithInit) {
1866
+ if (plugin.global) {
1867
+ expandedPackages.add(plugin.packageName);
1868
+ }
1869
+ }
1870
+ const toProcess = [...expandedPackages];
1868
1871
  while (toProcess.length > 0) {
1869
1872
  const packageName = toProcess.pop();
1870
1873
  const plugin = pluginsByPackage.get(packageName);
@@ -1893,41 +1896,12 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1893
1896
  language: "en",
1894
1897
  url
1895
1898
  };
1896
- const useSlowRenderCache = !options.dontCacheSlowly;
1897
- let cachedEntry = useSlowRenderCache ? slowRenderCache.get(route.jayHtmlPath, pageParams) : void 0;
1898
- if (cachedEntry) {
1899
- try {
1900
- await fs$1.access(cachedEntry.preRenderedPath);
1901
- } catch {
1902
- getLogger().info(
1903
- `[SlowRender] Cached file missing, rebuilding: ${cachedEntry.preRenderedPath}`
1904
- );
1905
- await slowRenderCache.invalidate(route.jayHtmlPath);
1906
- cachedEntry = void 0;
1907
- }
1908
- }
1909
- if (cachedEntry) {
1910
- await handleCachedRequest(
1911
- vite,
1912
- route,
1913
- options,
1914
- cachedEntry,
1915
- pageParams,
1916
- pageProps,
1917
- allPluginClientInits,
1918
- allPluginsWithInit,
1919
- projectInit,
1920
- res,
1921
- url,
1922
- timing
1923
- );
1924
- } else if (useSlowRenderCache) {
1925
- await handlePreRenderRequest(
1899
+ if (options.disableSSR) {
1900
+ await handleClientOnlyRequest(
1926
1901
  vite,
1927
1902
  route,
1928
1903
  options,
1929
1904
  slowlyPhase,
1930
- slowRenderCache,
1931
1905
  pageParams,
1932
1906
  pageProps,
1933
1907
  allPluginClientInits,
@@ -1938,20 +1912,39 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1938
1912
  timing
1939
1913
  );
1940
1914
  } else {
1941
- await handleDirectRequest(
1942
- vite,
1943
- route,
1944
- options,
1945
- slowlyPhase,
1946
- pageParams,
1947
- pageProps,
1948
- allPluginClientInits,
1949
- allPluginsWithInit,
1950
- projectInit,
1951
- res,
1952
- url,
1953
- timing
1954
- );
1915
+ const cachedEntry = await slowRenderCache.get(route.jayHtmlPath, pageParams);
1916
+ if (cachedEntry) {
1917
+ await handleCachedRequest(
1918
+ vite,
1919
+ route,
1920
+ options,
1921
+ cachedEntry,
1922
+ pageParams,
1923
+ pageProps,
1924
+ allPluginClientInits,
1925
+ allPluginsWithInit,
1926
+ projectInit,
1927
+ res,
1928
+ url,
1929
+ timing
1930
+ );
1931
+ } else {
1932
+ await handlePreRenderRequest(
1933
+ vite,
1934
+ route,
1935
+ options,
1936
+ slowlyPhase,
1937
+ slowRenderCache,
1938
+ pageParams,
1939
+ pageProps,
1940
+ allPluginClientInits,
1941
+ allPluginsWithInit,
1942
+ projectInit,
1943
+ res,
1944
+ url,
1945
+ timing
1946
+ );
1947
+ }
1955
1948
  }
1956
1949
  } catch (e2) {
1957
1950
  vite?.ssrFixStacktrace(e2);
@@ -1970,7 +1963,10 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
1970
1963
  options.pagesRootFolder,
1971
1964
  options.projectRootFolder,
1972
1965
  options.jayRollupConfig,
1973
- { preRenderedPath: cachedEntry.preRenderedPath }
1966
+ {
1967
+ preRenderedPath: cachedEntry.preRenderedPath,
1968
+ preRenderedContent: cachedEntry.preRenderedContent
1969
+ }
1974
1970
  );
1975
1971
  timing?.recordViteSsr(Date.now() - loadStart);
1976
1972
  if (!pagePartsResult.val) {
@@ -1985,12 +1981,19 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
1985
1981
  allPluginsWithInit,
1986
1982
  usedPackages
1987
1983
  );
1984
+ const instancePhaseData = cachedEntry.carryForward?.__instances;
1985
+ const forEachInstances = instancePhaseData?.forEachInstances;
1986
+ const headlessComps = pagePartsResult.val.headlessInstanceComponents;
1988
1987
  const fastStart = Date.now();
1989
1988
  const renderedFast = await renderFastChangingData(
1990
1989
  pageParams,
1991
1990
  pageProps,
1992
1991
  cachedEntry.carryForward,
1993
- pageParts
1992
+ pageParts,
1993
+ instancePhaseData,
1994
+ forEachInstances,
1995
+ headlessComps,
1996
+ cachedEntry.slowViewState
1994
1997
  );
1995
1998
  timing?.recordFastRender(Date.now() - fastStart);
1996
1999
  if (renderedFast.kind !== "PhaseOutput") {
@@ -1998,30 +2001,14 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
1998
2001
  timing?.end();
1999
2002
  return;
2000
2003
  }
2001
- let fastViewState = renderedFast.rendered;
2002
- let fastCarryForward = renderedFast.carryForward;
2003
- const instancePhaseData = cachedEntry.carryForward?.__instances;
2004
- if (instancePhaseData && pagePartsResult.val.headlessInstanceComponents.length > 0) {
2005
- const instanceFastResult = await renderFastChangingDataForInstances(
2006
- instancePhaseData,
2007
- pagePartsResult.val.headlessInstanceComponents
2008
- );
2009
- if (instanceFastResult) {
2010
- fastViewState = {
2011
- ...fastViewState,
2012
- __headlessInstances: instanceFastResult.viewStates
2013
- };
2014
- fastCarryForward = {
2015
- ...fastCarryForward,
2016
- __headlessInstances: instanceFastResult.carryForwards
2017
- };
2018
- }
2019
- }
2004
+ const fastViewState = renderedFast.rendered;
2005
+ const fastCarryForward = renderedFast.carryForward;
2020
2006
  await sendResponse(
2021
2007
  vite,
2022
2008
  res,
2023
2009
  url,
2024
2010
  cachedEntry.preRenderedPath,
2011
+ route.jayHtmlPath,
2025
2012
  pageParts,
2026
2013
  fastViewState,
2027
2014
  fastCarryForward,
@@ -2030,7 +2017,8 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
2030
2017
  pluginsForPage,
2031
2018
  options,
2032
2019
  cachedEntry.slowViewState,
2033
- timing
2020
+ timing,
2021
+ cachedEntry.preRenderedContent
2034
2022
  );
2035
2023
  }
2036
2024
  async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRenderCache, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
@@ -2053,7 +2041,10 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
2053
2041
  const renderedSlowly = await slowlyPhase.runSlowlyForPage(
2054
2042
  pageParams,
2055
2043
  pageProps,
2056
- initialPartsResult.val.parts
2044
+ initialPartsResult.val.parts,
2045
+ void 0,
2046
+ void 0,
2047
+ route.jayHtmlPath
2057
2048
  );
2058
2049
  if (renderedSlowly.kind !== "PhaseOutput") {
2059
2050
  timing?.recordSlowRender(Date.now() - slowStart);
@@ -2075,84 +2066,44 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
2075
2066
  timing?.end();
2076
2067
  return;
2077
2068
  }
2078
- const carryForward = preRenderResult.instancePhaseData ? { ...renderedSlowly.carryForward, __instances: preRenderResult.instancePhaseData } : renderedSlowly.carryForward;
2079
- const preRenderedPath = await slowRenderCache.set(
2069
+ let instancePhaseDataForCache = preRenderResult.instancePhaseData;
2070
+ if (instancePhaseDataForCache && preRenderResult.forEachInstances) {
2071
+ instancePhaseDataForCache = {
2072
+ ...instancePhaseDataForCache,
2073
+ forEachInstances: preRenderResult.forEachInstances
2074
+ };
2075
+ } else if (preRenderResult.forEachInstances) {
2076
+ instancePhaseDataForCache = {
2077
+ discovered: [],
2078
+ carryForwards: {},
2079
+ forEachInstances: preRenderResult.forEachInstances
2080
+ };
2081
+ }
2082
+ const carryForward = instancePhaseDataForCache ? { ...renderedSlowly.carryForward, __instances: instancePhaseDataForCache } : renderedSlowly.carryForward;
2083
+ const cachedEntry = await slowRenderCache.set(
2080
2084
  route.jayHtmlPath,
2081
2085
  pageParams,
2082
2086
  preRenderResult.preRenderedJayHtml,
2083
2087
  renderedSlowly.rendered,
2084
2088
  carryForward
2085
2089
  );
2086
- getLogger().info(`[SlowRender] Cached pre-rendered jay-html at ${preRenderedPath}`);
2087
- const pagePartsResult = await loadPageParts(
2090
+ getLogger().info(`[SlowRender] Cached pre-rendered jay-html at ${cachedEntry.preRenderedPath}`);
2091
+ await handleCachedRequest(
2088
2092
  vite,
2089
2093
  route,
2090
- options.pagesRootFolder,
2091
- options.projectRootFolder,
2092
- options.jayRollupConfig,
2093
- { preRenderedPath }
2094
- );
2095
- if (!pagePartsResult.val) {
2096
- getLogger().info(pagePartsResult.validations.join("\n"));
2097
- res.status(500).end(pagePartsResult.validations.join("\n"));
2098
- timing?.end();
2099
- return;
2100
- }
2101
- const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
2102
- const pluginsForPage = filterPluginsForPage(
2103
- allPluginClientInits,
2104
- allPluginsWithInit,
2105
- usedPackages
2106
- );
2107
- const fastStart = Date.now();
2108
- const renderedFast = await renderFastChangingData(
2094
+ options,
2095
+ cachedEntry,
2109
2096
  pageParams,
2110
2097
  pageProps,
2111
- carryForward,
2112
- pageParts
2113
- );
2114
- timing?.recordFastRender(Date.now() - fastStart);
2115
- if (renderedFast.kind !== "PhaseOutput") {
2116
- handleOtherResponseCodes(res, renderedFast);
2117
- timing?.end();
2118
- return;
2119
- }
2120
- let fastViewState = renderedFast.rendered;
2121
- let fastCarryForward = renderedFast.carryForward;
2122
- const instancePhaseData = carryForward?.__instances;
2123
- if (instancePhaseData && pagePartsResult.val.headlessInstanceComponents.length > 0) {
2124
- const instanceFastResult = await renderFastChangingDataForInstances(
2125
- instancePhaseData,
2126
- pagePartsResult.val.headlessInstanceComponents
2127
- );
2128
- if (instanceFastResult) {
2129
- fastViewState = {
2130
- ...fastViewState,
2131
- __headlessInstances: instanceFastResult.viewStates
2132
- };
2133
- fastCarryForward = {
2134
- ...fastCarryForward,
2135
- __headlessInstances: instanceFastResult.carryForwards
2136
- };
2137
- }
2138
- }
2139
- await sendResponse(
2140
- vite,
2098
+ allPluginClientInits,
2099
+ allPluginsWithInit,
2100
+ projectInit,
2141
2101
  res,
2142
2102
  url,
2143
- preRenderedPath,
2144
- pageParts,
2145
- fastViewState,
2146
- fastCarryForward,
2147
- clientTrackByMap,
2148
- projectInit,
2149
- pluginsForPage,
2150
- options,
2151
- renderedSlowly.rendered,
2152
2103
  timing
2153
2104
  );
2154
2105
  }
2155
- async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
2106
+ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
2156
2107
  const loadStart = Date.now();
2157
2108
  const pagePartsResult = await loadPageParts(
2158
2109
  vite,
@@ -2172,7 +2123,10 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2172
2123
  parts: pageParts,
2173
2124
  serverTrackByMap,
2174
2125
  clientTrackByMap,
2175
- usedPackages
2126
+ usedPackages,
2127
+ headlessInstanceComponents,
2128
+ discoveredInstances,
2129
+ forEachInstances
2176
2130
  } = pagePartsResult.val;
2177
2131
  const pluginsForPage = filterPluginsForPage(
2178
2132
  allPluginClientInits,
@@ -2180,7 +2134,14 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2180
2134
  usedPackages
2181
2135
  );
2182
2136
  const slowStart = Date.now();
2183
- const renderedSlowly = await slowlyPhase.runSlowlyForPage(pageParams, pageProps, pageParts);
2137
+ const renderedSlowly = await slowlyPhase.runSlowlyForPage(
2138
+ pageParams,
2139
+ pageProps,
2140
+ pageParts,
2141
+ discoveredInstances,
2142
+ headlessInstanceComponents,
2143
+ route.jayHtmlPath
2144
+ );
2184
2145
  if (renderedSlowly.kind !== "PhaseOutput") {
2185
2146
  timing?.recordSlowRender(Date.now() - slowStart);
2186
2147
  if (renderedSlowly.kind === "ClientError") {
@@ -2189,101 +2150,105 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2189
2150
  timing?.end();
2190
2151
  return;
2191
2152
  }
2192
- let instanceViewStates;
2193
- let instancePhaseDataForFast;
2194
- const headlessInstanceComponents = pagePartsResult.val.headlessInstanceComponents ?? [];
2195
- if (headlessInstanceComponents.length > 0) {
2196
- const jayHtmlContent = await fs$1.readFile(route.jayHtmlPath, "utf-8");
2197
- const discoveryResult = discoverHeadlessInstances(jayHtmlContent);
2198
- if (discoveryResult.instances.length > 0) {
2199
- const slowResult = await slowRenderInstances(
2200
- discoveryResult.instances,
2201
- headlessInstanceComponents
2202
- );
2203
- if (slowResult) {
2204
- instanceViewStates = { ...slowResult.slowViewStates };
2205
- instancePhaseDataForFast = slowResult.instancePhaseData;
2206
- }
2207
- }
2208
- }
2209
2153
  timing?.recordSlowRender(Date.now() - slowStart);
2154
+ const instancePhaseData = renderedSlowly.carryForward?.__instances;
2210
2155
  const fastStart = Date.now();
2211
2156
  const renderedFast = await renderFastChangingData(
2212
2157
  pageParams,
2213
2158
  pageProps,
2214
2159
  renderedSlowly.carryForward,
2215
- pageParts
2160
+ pageParts,
2161
+ instancePhaseData,
2162
+ forEachInstances,
2163
+ headlessInstanceComponents,
2164
+ renderedSlowly.rendered
2216
2165
  );
2217
- if (instancePhaseDataForFast && instanceViewStates) {
2218
- const instanceFastResult = await renderFastChangingDataForInstances(
2219
- instancePhaseDataForFast,
2220
- headlessInstanceComponents
2221
- );
2222
- if (instanceFastResult) {
2223
- for (const [coordKey, fastVS] of Object.entries(instanceFastResult.viewStates)) {
2224
- instanceViewStates[coordKey] = {
2225
- ...instanceViewStates[coordKey] || {},
2226
- ...fastVS
2227
- };
2228
- }
2229
- }
2230
- }
2231
2166
  timing?.recordFastRender(Date.now() - fastStart);
2232
2167
  if (renderedFast.kind !== "PhaseOutput") {
2233
2168
  handleOtherResponseCodes(res, renderedFast);
2234
2169
  timing?.end();
2235
2170
  return;
2236
2171
  }
2237
- let viewState;
2238
- if (serverTrackByMap && Object.keys(serverTrackByMap).length > 0) {
2239
- viewState = deepMergeViewStates(
2240
- renderedSlowly.rendered,
2241
- renderedFast.rendered,
2242
- serverTrackByMap
2243
- );
2244
- } else {
2245
- viewState = { ...renderedSlowly.rendered, ...renderedFast.rendered };
2246
- }
2247
- if (instanceViewStates && Object.keys(instanceViewStates).length > 0) {
2248
- viewState = {
2249
- ...viewState,
2250
- __headlessInstances: instanceViewStates
2251
- };
2252
- }
2253
- await sendResponse(
2254
- vite,
2255
- res,
2256
- url,
2257
- route.jayHtmlPath,
2258
- pageParts,
2259
- viewState,
2260
- renderedFast.carryForward,
2261
- clientTrackByMap,
2262
- projectInit,
2263
- pluginsForPage,
2264
- options,
2265
- void 0,
2266
- timing
2172
+ const viewState = deepMergeViewStates(
2173
+ renderedSlowly.rendered,
2174
+ renderedFast.rendered,
2175
+ serverTrackByMap || {}
2267
2176
  );
2268
- }
2269
- async function sendResponse(vite, res, url, jayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState, timing) {
2177
+ const fastCF = renderedFast.carryForward;
2270
2178
  const pageHtml = generateClientScript(
2271
2179
  viewState,
2272
- carryForward,
2180
+ fastCF,
2273
2181
  pageParts,
2274
- jayHtmlPath,
2182
+ route.jayHtmlPath,
2275
2183
  clientTrackByMap,
2276
2184
  getClientInitData(),
2277
2185
  projectInit,
2278
2186
  pluginsForPage,
2279
2187
  {
2280
- enableAutomation: !options.disableAutomation,
2281
- slowViewState
2188
+ enableAutomation: !options.disableAutomation
2282
2189
  }
2283
2190
  );
2284
2191
  if (options.buildFolder) {
2285
2192
  const pageName = !url || url === "/" ? "index" : url.replace(/^\//, "").replace(/\//g, "-");
2286
- const clientScriptDir = path__default.join(options.buildFolder, "client-scripts");
2193
+ const clientScriptDir = path__default.join(options.buildFolder, "debug", "client-entry");
2194
+ await fs$1.mkdir(clientScriptDir, { recursive: true });
2195
+ await fs$1.writeFile(path__default.join(clientScriptDir, `${pageName}.html`), pageHtml, "utf-8");
2196
+ }
2197
+ const viteStart = Date.now();
2198
+ const compiledPageHtml = await vite.transformIndexHtml(!!url ? url : "/", pageHtml);
2199
+ timing?.recordViteClient(Date.now() - viteStart);
2200
+ res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
2201
+ timing?.end();
2202
+ }
2203
+ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState, timing, preLoadedContent) {
2204
+ let pageHtml;
2205
+ const routeDir = path__default.dirname(path__default.relative(options.pagesRootFolder, sourceJayHtmlPath));
2206
+ try {
2207
+ const jayHtmlContent = preLoadedContent ?? await fs$1.readFile(jayHtmlPath, "utf-8");
2208
+ const jayHtmlFilename = path__default.basename(jayHtmlPath);
2209
+ const jayHtmlDir = path__default.dirname(jayHtmlPath);
2210
+ pageHtml = await generateSSRPageHtml(
2211
+ vite,
2212
+ jayHtmlContent,
2213
+ jayHtmlFilename,
2214
+ jayHtmlDir,
2215
+ viewState,
2216
+ jayHtmlPath,
2217
+ pageParts,
2218
+ carryForward,
2219
+ clientTrackByMap,
2220
+ getClientInitData(),
2221
+ options.buildFolder,
2222
+ options.projectRootFolder,
2223
+ routeDir,
2224
+ options.jayRollupConfig?.tsConfigFilePath,
2225
+ projectInit,
2226
+ pluginsForPage,
2227
+ {
2228
+ enableAutomation: !options.disableAutomation,
2229
+ slowViewState
2230
+ }
2231
+ );
2232
+ } catch (err) {
2233
+ getLogger().warn(`[SSR] Failed, falling back to client rendering: ${err.message}`);
2234
+ pageHtml = generateClientScript(
2235
+ viewState,
2236
+ carryForward,
2237
+ pageParts,
2238
+ jayHtmlPath,
2239
+ clientTrackByMap,
2240
+ getClientInitData(),
2241
+ projectInit,
2242
+ pluginsForPage,
2243
+ {
2244
+ enableAutomation: !options.disableAutomation,
2245
+ slowViewState
2246
+ }
2247
+ );
2248
+ }
2249
+ if (options.buildFolder) {
2250
+ const pageName = !url || url === "/" ? "index" : url.replace(/^\//, "").replace(/\//g, "-");
2251
+ const clientScriptDir = path__default.join(options.buildFolder, "debug", "client-entry");
2287
2252
  await fs$1.mkdir(clientScriptDir, { recursive: true });
2288
2253
  await fs$1.writeFile(path__default.join(clientScriptDir, `${pageName}.html`), pageHtml, "utf-8");
2289
2254
  }
@@ -2314,6 +2279,11 @@ async function preRenderJayHtml(route, slowViewState, headlessContracts, headles
2314
2279
  return void 0;
2315
2280
  }
2316
2281
  }
2282
+ if (!contract && slowViewState && Object.keys(slowViewState).length > 0) {
2283
+ getLogger().warn(
2284
+ `[SlowRender] Page ${route.jayHtmlPath} has slow ViewState but no contract. Without a contract, slow bindings cannot be resolved. Move data to withFastRender or add a .jay-contract file with phase annotations.`
2285
+ );
2286
+ }
2317
2287
  const result = slowRenderTransform({
2318
2288
  jayHtmlContent,
2319
2289
  slowViewState,
@@ -2332,9 +2302,23 @@ async function preRenderJayHtml(route, slowViewState, headlessContracts, headles
2332
2302
  }
2333
2303
  let preRenderedJayHtml = result.val.preRenderedJayHtml;
2334
2304
  let instancePhaseData;
2305
+ let forEachInstances;
2335
2306
  if (headlessInstanceComponents.length > 0) {
2336
2307
  const discoveryResult = discoverHeadlessInstances(preRenderedJayHtml);
2337
2308
  preRenderedJayHtml = discoveryResult.preRenderedJayHtml;
2309
+ if (discoveryResult.forEachInstances.length > 0) {
2310
+ const validationErrors = validateForEachInstances(
2311
+ discoveryResult.forEachInstances,
2312
+ headlessInstanceComponents
2313
+ );
2314
+ if (validationErrors.length > 0) {
2315
+ getLogger().error(
2316
+ `[SlowRender] ForEach instance validation failed: ${validationErrors.join(", ")}`
2317
+ );
2318
+ return void 0;
2319
+ }
2320
+ forEachInstances = discoveryResult.forEachInstances;
2321
+ }
2338
2322
  if (discoveryResult.instances.length > 0) {
2339
2323
  const slowResult = await slowRenderInstances(
2340
2324
  discoveryResult.instances,
@@ -2356,46 +2340,42 @@ async function preRenderJayHtml(route, slowViewState, headlessContracts, headles
2356
2340
  );
2357
2341
  }
2358
2342
  }
2343
+ if (!instancePhaseData) {
2344
+ const componentByContractName = /* @__PURE__ */ new Map();
2345
+ for (const comp of headlessInstanceComponents) {
2346
+ componentByContractName.set(comp.contractName, comp);
2347
+ }
2348
+ instancePhaseData = {
2349
+ discovered: discoveryResult.instances.filter((i) => componentByContractName.has(i.contractName)).map((i) => {
2350
+ const comp = componentByContractName.get(i.contractName);
2351
+ const contractProps = comp.contract?.props ?? [];
2352
+ const normalizedProps = {};
2353
+ for (const [key, value] of Object.entries(i.props)) {
2354
+ const match = contractProps.find(
2355
+ (p) => p.name.toLowerCase() === key.toLowerCase()
2356
+ );
2357
+ normalizedProps[match ? match.name : key] = String(value);
2358
+ }
2359
+ return {
2360
+ contractName: i.contractName,
2361
+ props: normalizedProps,
2362
+ coordinate: i.coordinate
2363
+ };
2364
+ }),
2365
+ carryForwards: {}
2366
+ };
2367
+ }
2359
2368
  }
2360
2369
  }
2361
- return { preRenderedJayHtml, instancePhaseData };
2362
- }
2363
- async function renderFastChangingDataForInstances(instancePhaseData, headlessInstanceComponents) {
2364
- const componentByContractName = /* @__PURE__ */ new Map();
2365
- for (const comp of headlessInstanceComponents) {
2366
- componentByContractName.set(comp.contractName, comp);
2367
- }
2368
- const viewStates = {};
2369
- const carryForwards = {};
2370
- let hasResults = false;
2371
- for (const instance of instancePhaseData.discovered) {
2372
- const coordKey = instance.coordinate.join("/");
2373
- const comp = componentByContractName.get(instance.contractName);
2374
- if (!comp || !comp.compDefinition.fastRender) {
2375
- continue;
2376
- }
2377
- const instanceCarryForward = instancePhaseData.carryForwards[coordKey] || {};
2378
- const services = resolveServices(comp.compDefinition.services);
2379
- const fastResult = await comp.compDefinition.fastRender(
2380
- instance.props,
2381
- instanceCarryForward,
2382
- ...services
2383
- );
2384
- if (fastResult.kind === "PhaseOutput") {
2385
- viewStates[coordKey] = fastResult.rendered;
2386
- carryForwards[coordKey] = fastResult.carryForward;
2387
- hasResults = true;
2388
- }
2389
- }
2390
- return hasResults ? { viewStates, carryForwards } : void 0;
2370
+ return { preRenderedJayHtml, instancePhaseData, forEachInstances };
2391
2371
  }
2392
- async function materializeDynamicContracts(projectRootFolder, buildFolder, viteServer) {
2372
+ async function materializeDynamicContracts(projectRootFolder, viteServer) {
2393
2373
  try {
2394
2374
  const services = getServiceRegistry();
2395
2375
  const result = await materializeContracts(
2396
2376
  {
2397
2377
  projectRoot: projectRootFolder,
2398
- outputDir: path__default.join(buildFolder, "materialized-contracts"),
2378
+ outputDir: path__default.join(projectRootFolder, "agent-kit", "materialized-contracts"),
2399
2379
  verbose: false,
2400
2380
  viteServer
2401
2381
  },
@@ -2411,14 +2391,19 @@ async function materializeDynamicContracts(projectRootFolder, buildFolder, viteS
2411
2391
  }
2412
2392
  async function mkDevServer(rawOptions) {
2413
2393
  const options = defaults(rawOptions);
2414
- const {
2415
- publicBaseUrlPath,
2416
- pagesRootFolder,
2417
- projectRootFolder,
2418
- buildFolder,
2419
- jayRollupConfig,
2420
- dontCacheSlowly
2421
- } = options;
2394
+ const { publicBaseUrlPath, pagesRootFolder, projectRootFolder, buildFolder, jayRollupConfig } = options;
2395
+ if (buildFolder) {
2396
+ try {
2397
+ const entries = await fs$1.readdir(buildFolder);
2398
+ for (const entry of entries) {
2399
+ if (entry !== "pre-rendered") {
2400
+ await fs$1.rm(path__default.join(buildFolder, entry), { recursive: true, force: true }).catch(() => {
2401
+ });
2402
+ }
2403
+ }
2404
+ } catch {
2405
+ }
2406
+ }
2422
2407
  const viteLogLevel = options.logLevel === "silent" ? "silent" : options.logLevel === "verbose" ? "info" : "warn";
2423
2408
  const lifecycleManager = new ServiceLifecycleManager(projectRootFolder);
2424
2409
  setupGracefulShutdown(lifecycleManager);
@@ -2431,14 +2416,15 @@ async function mkDevServer(rawOptions) {
2431
2416
  });
2432
2417
  lifecycleManager.setViteServer(vite);
2433
2418
  await lifecycleManager.initialize();
2434
- await materializeDynamicContracts(projectRootFolder, buildFolder, vite);
2419
+ await materializeDynamicContracts(projectRootFolder, vite);
2435
2420
  setupServiceHotReload(vite, lifecycleManager);
2436
2421
  setupActionRouter(vite);
2437
- const routes = await initRoutes(pagesRootFolder);
2438
- const slowlyPhase = new DevSlowlyChangingPhase(dontCacheSlowly);
2439
- const slowRenderCacheDir = path__default.join(buildFolder, "slow-render-cache");
2422
+ const allRoutes = await initRoutes(pagesRootFolder);
2423
+ const routes = buildFolder ? allRoutes.filter((route) => !route.jayHtmlPath.startsWith(buildFolder)) : allRoutes;
2424
+ const slowlyPhase = new DevSlowlyChangingPhase();
2425
+ const slowRenderCacheDir = path__default.join(buildFolder, "pre-rendered");
2440
2426
  const slowRenderCache = new SlowRenderCache(slowRenderCacheDir, pagesRootFolder);
2441
- setupSlowRenderCacheInvalidation(vite, slowRenderCache, pagesRootFolder);
2427
+ setupSlowRenderCacheInvalidation(vite, slowRenderCache, slowlyPhase, pagesRootFolder);
2442
2428
  const projectInit = lifecycleManager.getProjectInit() ?? void 0;
2443
2429
  const pluginsWithInit = lifecycleManager.getPluginsWithInit();
2444
2430
  const pluginClientInits = preparePluginClientInits(pluginsWithInit);
@@ -2497,12 +2483,14 @@ function setupActionRouter(vite) {
2497
2483
  vite.middlewares.use(ACTION_ENDPOINT_BASE, createActionRouter());
2498
2484
  getLogger().info(`[Actions] Action router mounted at ${ACTION_ENDPOINT_BASE}`);
2499
2485
  }
2500
- function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2486
+ function setupSlowRenderCacheInvalidation(vite, cache, slowlyPhase, pagesRootFolder) {
2501
2487
  vite.watcher.on("change", (changedPath) => {
2502
2488
  if (!changedPath.startsWith(pagesRootFolder)) {
2503
2489
  return;
2504
2490
  }
2505
2491
  if (changedPath.endsWith(".jay-html")) {
2492
+ invalidateServerElementCache(changedPath);
2493
+ slowlyPhase.invalidateLoadParamsCache(changedPath);
2506
2494
  cache.invalidate(changedPath).then(() => {
2507
2495
  getLogger().info(`[SlowRender] Cache invalidated for ${changedPath}`);
2508
2496
  });
@@ -2511,6 +2499,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2511
2499
  if (changedPath.endsWith("page.ts")) {
2512
2500
  const dir = path__default.dirname(changedPath);
2513
2501
  const jayHtmlPath = path__default.join(dir, "page.jay-html");
2502
+ slowlyPhase.invalidateLoadParamsCache(jayHtmlPath);
2514
2503
  cache.invalidate(jayHtmlPath).then(() => {
2515
2504
  getLogger().info(
2516
2505
  `[SlowRender] Cache invalidated for ${jayHtmlPath} (page.ts changed)`
@@ -2520,6 +2509,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2520
2509
  }
2521
2510
  if (changedPath.endsWith(".jay-contract")) {
2522
2511
  const jayHtmlPath = changedPath.replace(".jay-contract", ".jay-html");
2512
+ slowlyPhase.invalidateLoadParamsCache(jayHtmlPath);
2523
2513
  cache.invalidate(jayHtmlPath).then(() => {
2524
2514
  getLogger().info(
2525
2515
  `[SlowRender] Cache invalidated for ${jayHtmlPath} (contract changed)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/dev-server",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -16,31 +16,35 @@
16
16
  "build:check-types": "tsc",
17
17
  "test-definitions": "jay-cli definitions test",
18
18
  "test-runtime": "jay-cli runtime test",
19
+ "serve-fixture": "npx tsx test/serve-fixture.ts",
19
20
  "clean": "rimraf dist",
20
21
  "confirm": "npm run clean && npm run build && npm run build:check-types && npm run test",
21
22
  "test": "vitest run",
22
23
  "test:watch": "vitest"
23
24
  },
24
25
  "dependencies": {
25
- "@jay-framework/compiler-jay-stack": "^0.12.0",
26
- "@jay-framework/compiler-shared": "^0.12.0",
27
- "@jay-framework/component": "^0.12.0",
28
- "@jay-framework/fullstack-component": "^0.12.0",
29
- "@jay-framework/logger": "^0.12.0",
30
- "@jay-framework/runtime": "^0.12.0",
31
- "@jay-framework/stack-client-runtime": "^0.12.0",
32
- "@jay-framework/stack-route-scanner": "^0.12.0",
33
- "@jay-framework/stack-server-runtime": "^0.12.0",
34
- "@jay-framework/view-state-merge": "^0.12.0",
26
+ "@jay-framework/compiler-jay-stack": "^0.14.0",
27
+ "@jay-framework/compiler-shared": "^0.14.0",
28
+ "@jay-framework/component": "^0.14.0",
29
+ "@jay-framework/fullstack-component": "^0.14.0",
30
+ "@jay-framework/logger": "^0.14.0",
31
+ "@jay-framework/runtime": "^0.14.0",
32
+ "@jay-framework/stack-client-runtime": "^0.14.0",
33
+ "@jay-framework/stack-route-scanner": "^0.14.0",
34
+ "@jay-framework/stack-server-runtime": "^0.14.0",
35
+ "@jay-framework/view-state-merge": "^0.14.0",
35
36
  "vite": "^5.0.11"
36
37
  },
37
38
  "devDependencies": {
38
- "@jay-framework/dev-environment": "^0.12.0",
39
- "@jay-framework/jay-cli": "^0.12.0",
40
- "@jay-framework/stack-client-runtime": "^0.12.0",
39
+ "@jay-framework/dev-environment": "^0.14.0",
40
+ "@jay-framework/jay-cli": "^0.14.0",
41
+ "@jay-framework/stack-client-runtime": "^0.14.0",
42
+ "@playwright/test": "^1.58.2",
41
43
  "@types/express": "^5.0.2",
42
44
  "@types/node": "^22.15.21",
45
+ "jsdom": "^23.2.0",
43
46
  "nodemon": "^3.0.3",
47
+ "playwright": "^1.58.2",
44
48
  "replace-in-file": "^7.1.0",
45
49
  "rimraf": "^5.0.5",
46
50
  "tsup": "^8.0.1",