@jay-framework/dev-server 0.13.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 +209 -352
  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, validateForEachInstances, 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)
@@ -1841,7 +1841,7 @@ function defaults(options) {
1841
1841
  pagesRootFolder,
1842
1842
  projectRootFolder,
1843
1843
  buildFolder,
1844
- dontCacheSlowly: options.dontCacheSlowly,
1844
+ disableSSR: options.disableSSR,
1845
1845
  jayRollupConfig: {
1846
1846
  ...options.jayRollupConfig || {},
1847
1847
  tsConfigFilePath
@@ -1896,41 +1896,12 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1896
1896
  language: "en",
1897
1897
  url
1898
1898
  };
1899
- const useSlowRenderCache = !options.dontCacheSlowly;
1900
- let cachedEntry = useSlowRenderCache ? slowRenderCache.get(route.jayHtmlPath, pageParams) : void 0;
1901
- if (cachedEntry) {
1902
- try {
1903
- await fs$1.access(cachedEntry.preRenderedPath);
1904
- } catch {
1905
- getLogger().info(
1906
- `[SlowRender] Cached file missing, rebuilding: ${cachedEntry.preRenderedPath}`
1907
- );
1908
- await slowRenderCache.invalidate(route.jayHtmlPath);
1909
- cachedEntry = void 0;
1910
- }
1911
- }
1912
- if (cachedEntry) {
1913
- await handleCachedRequest(
1914
- vite,
1915
- route,
1916
- options,
1917
- cachedEntry,
1918
- pageParams,
1919
- pageProps,
1920
- allPluginClientInits,
1921
- allPluginsWithInit,
1922
- projectInit,
1923
- res,
1924
- url,
1925
- timing
1926
- );
1927
- } else if (useSlowRenderCache) {
1928
- await handlePreRenderRequest(
1899
+ if (options.disableSSR) {
1900
+ await handleClientOnlyRequest(
1929
1901
  vite,
1930
1902
  route,
1931
1903
  options,
1932
1904
  slowlyPhase,
1933
- slowRenderCache,
1934
1905
  pageParams,
1935
1906
  pageProps,
1936
1907
  allPluginClientInits,
@@ -1941,20 +1912,39 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1941
1912
  timing
1942
1913
  );
1943
1914
  } else {
1944
- await handleDirectRequest(
1945
- vite,
1946
- route,
1947
- options,
1948
- slowlyPhase,
1949
- pageParams,
1950
- pageProps,
1951
- allPluginClientInits,
1952
- allPluginsWithInit,
1953
- projectInit,
1954
- res,
1955
- url,
1956
- timing
1957
- );
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
+ }
1958
1948
  }
1959
1949
  } catch (e2) {
1960
1950
  vite?.ssrFixStacktrace(e2);
@@ -1973,7 +1963,10 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
1973
1963
  options.pagesRootFolder,
1974
1964
  options.projectRootFolder,
1975
1965
  options.jayRollupConfig,
1976
- { preRenderedPath: cachedEntry.preRenderedPath }
1966
+ {
1967
+ preRenderedPath: cachedEntry.preRenderedPath,
1968
+ preRenderedContent: cachedEntry.preRenderedContent
1969
+ }
1977
1970
  );
1978
1971
  timing?.recordViteSsr(Date.now() - loadStart);
1979
1972
  if (!pagePartsResult.val) {
@@ -1988,12 +1981,19 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
1988
1981
  allPluginsWithInit,
1989
1982
  usedPackages
1990
1983
  );
1984
+ const instancePhaseData = cachedEntry.carryForward?.__instances;
1985
+ const forEachInstances = instancePhaseData?.forEachInstances;
1986
+ const headlessComps = pagePartsResult.val.headlessInstanceComponents;
1991
1987
  const fastStart = Date.now();
1992
1988
  const renderedFast = await renderFastChangingData(
1993
1989
  pageParams,
1994
1990
  pageProps,
1995
1991
  cachedEntry.carryForward,
1996
- pageParts
1992
+ pageParts,
1993
+ instancePhaseData,
1994
+ forEachInstances,
1995
+ headlessComps,
1996
+ cachedEntry.slowViewState
1997
1997
  );
1998
1998
  timing?.recordFastRender(Date.now() - fastStart);
1999
1999
  if (renderedFast.kind !== "PhaseOutput") {
@@ -2001,45 +2001,14 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
2001
2001
  timing?.end();
2002
2002
  return;
2003
2003
  }
2004
- let fastViewState = renderedFast.rendered;
2005
- let fastCarryForward = renderedFast.carryForward;
2006
- const instancePhaseData = cachedEntry.carryForward?.__instances;
2007
- const headlessComps = pagePartsResult.val.headlessInstanceComponents;
2008
- if (instancePhaseData && headlessComps.length > 0) {
2009
- const instanceFastResult = await renderFastChangingDataForInstances(
2010
- instancePhaseData,
2011
- headlessComps
2012
- );
2013
- if (instanceFastResult) {
2014
- fastViewState = {
2015
- ...fastViewState,
2016
- __headlessInstances: instanceFastResult.viewStates
2017
- };
2018
- fastCarryForward = {
2019
- ...fastCarryForward,
2020
- __headlessInstances: instanceFastResult.carryForwards
2021
- };
2022
- }
2023
- if (instancePhaseData.forEachInstances && instancePhaseData.forEachInstances.length > 0) {
2024
- const forEachResult = await renderFastChangingDataForForEachInstances(
2025
- instancePhaseData.forEachInstances,
2026
- headlessComps,
2027
- fastViewState
2028
- );
2029
- if (forEachResult) {
2030
- const existingHeadless = fastViewState.__headlessInstances || {};
2031
- fastViewState = {
2032
- ...fastViewState,
2033
- __headlessInstances: { ...existingHeadless, ...forEachResult }
2034
- };
2035
- }
2036
- }
2037
- }
2004
+ const fastViewState = renderedFast.rendered;
2005
+ const fastCarryForward = renderedFast.carryForward;
2038
2006
  await sendResponse(
2039
2007
  vite,
2040
2008
  res,
2041
2009
  url,
2042
2010
  cachedEntry.preRenderedPath,
2011
+ route.jayHtmlPath,
2043
2012
  pageParts,
2044
2013
  fastViewState,
2045
2014
  fastCarryForward,
@@ -2048,7 +2017,8 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
2048
2017
  pluginsForPage,
2049
2018
  options,
2050
2019
  cachedEntry.slowViewState,
2051
- timing
2020
+ timing,
2021
+ cachedEntry.preRenderedContent
2052
2022
  );
2053
2023
  }
2054
2024
  async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRenderCache, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
@@ -2071,7 +2041,10 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
2071
2041
  const renderedSlowly = await slowlyPhase.runSlowlyForPage(
2072
2042
  pageParams,
2073
2043
  pageProps,
2074
- initialPartsResult.val.parts
2044
+ initialPartsResult.val.parts,
2045
+ void 0,
2046
+ void 0,
2047
+ route.jayHtmlPath
2075
2048
  );
2076
2049
  if (renderedSlowly.kind !== "PhaseOutput") {
2077
2050
  timing?.recordSlowRender(Date.now() - slowStart);
@@ -2107,98 +2080,30 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
2107
2080
  };
2108
2081
  }
2109
2082
  const carryForward = instancePhaseDataForCache ? { ...renderedSlowly.carryForward, __instances: instancePhaseDataForCache } : renderedSlowly.carryForward;
2110
- const preRenderedPath = await slowRenderCache.set(
2083
+ const cachedEntry = await slowRenderCache.set(
2111
2084
  route.jayHtmlPath,
2112
2085
  pageParams,
2113
2086
  preRenderResult.preRenderedJayHtml,
2114
2087
  renderedSlowly.rendered,
2115
2088
  carryForward
2116
2089
  );
2117
- getLogger().info(`[SlowRender] Cached pre-rendered jay-html at ${preRenderedPath}`);
2118
- const pagePartsResult = await loadPageParts(
2090
+ getLogger().info(`[SlowRender] Cached pre-rendered jay-html at ${cachedEntry.preRenderedPath}`);
2091
+ await handleCachedRequest(
2119
2092
  vite,
2120
2093
  route,
2121
- options.pagesRootFolder,
2122
- options.projectRootFolder,
2123
- options.jayRollupConfig,
2124
- { preRenderedPath }
2125
- );
2126
- if (!pagePartsResult.val) {
2127
- getLogger().info(pagePartsResult.validations.join("\n"));
2128
- res.status(500).end(pagePartsResult.validations.join("\n"));
2129
- timing?.end();
2130
- return;
2131
- }
2132
- const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
2133
- const pluginsForPage = filterPluginsForPage(
2134
- allPluginClientInits,
2135
- allPluginsWithInit,
2136
- usedPackages
2137
- );
2138
- const fastStart = Date.now();
2139
- const renderedFast = await renderFastChangingData(
2094
+ options,
2095
+ cachedEntry,
2140
2096
  pageParams,
2141
2097
  pageProps,
2142
- carryForward,
2143
- pageParts
2144
- );
2145
- timing?.recordFastRender(Date.now() - fastStart);
2146
- if (renderedFast.kind !== "PhaseOutput") {
2147
- handleOtherResponseCodes(res, renderedFast);
2148
- timing?.end();
2149
- return;
2150
- }
2151
- let fastViewState = renderedFast.rendered;
2152
- let fastCarryForward = renderedFast.carryForward;
2153
- const instancePhaseData = carryForward?.__instances;
2154
- const headlessComps = pagePartsResult.val.headlessInstanceComponents;
2155
- if (instancePhaseData && headlessComps.length > 0) {
2156
- const instanceFastResult = await renderFastChangingDataForInstances(
2157
- instancePhaseData,
2158
- headlessComps
2159
- );
2160
- if (instanceFastResult) {
2161
- fastViewState = {
2162
- ...fastViewState,
2163
- __headlessInstances: instanceFastResult.viewStates
2164
- };
2165
- fastCarryForward = {
2166
- ...fastCarryForward,
2167
- __headlessInstances: instanceFastResult.carryForwards
2168
- };
2169
- }
2170
- if (instancePhaseData.forEachInstances && instancePhaseData.forEachInstances.length > 0) {
2171
- const forEachResult = await renderFastChangingDataForForEachInstances(
2172
- instancePhaseData.forEachInstances,
2173
- headlessComps,
2174
- fastViewState
2175
- );
2176
- if (forEachResult) {
2177
- const existingHeadless = fastViewState.__headlessInstances || {};
2178
- fastViewState = {
2179
- ...fastViewState,
2180
- __headlessInstances: { ...existingHeadless, ...forEachResult }
2181
- };
2182
- }
2183
- }
2184
- }
2185
- await sendResponse(
2186
- vite,
2098
+ allPluginClientInits,
2099
+ allPluginsWithInit,
2100
+ projectInit,
2187
2101
  res,
2188
2102
  url,
2189
- preRenderedPath,
2190
- pageParts,
2191
- fastViewState,
2192
- fastCarryForward,
2193
- clientTrackByMap,
2194
- projectInit,
2195
- pluginsForPage,
2196
- options,
2197
- renderedSlowly.rendered,
2198
2103
  timing
2199
2104
  );
2200
2105
  }
2201
- 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) {
2202
2107
  const loadStart = Date.now();
2203
2108
  const pagePartsResult = await loadPageParts(
2204
2109
  vite,
@@ -2218,7 +2123,10 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2218
2123
  parts: pageParts,
2219
2124
  serverTrackByMap,
2220
2125
  clientTrackByMap,
2221
- usedPackages
2126
+ usedPackages,
2127
+ headlessInstanceComponents,
2128
+ discoveredInstances,
2129
+ forEachInstances
2222
2130
  } = pagePartsResult.val;
2223
2131
  const pluginsForPage = filterPluginsForPage(
2224
2132
  allPluginClientInits,
@@ -2226,7 +2134,14 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2226
2134
  usedPackages
2227
2135
  );
2228
2136
  const slowStart = Date.now();
2229
- 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
+ );
2230
2145
  if (renderedSlowly.kind !== "PhaseOutput") {
2231
2146
  timing?.recordSlowRender(Date.now() - slowStart);
2232
2147
  if (renderedSlowly.kind === "ClientError") {
@@ -2235,131 +2150,105 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2235
2150
  timing?.end();
2236
2151
  return;
2237
2152
  }
2238
- let instanceViewStates;
2239
- let instancePhaseDataForFast;
2240
- let forEachInstancesForFast;
2241
- const headlessInstanceComponents = pagePartsResult.val.headlessInstanceComponents ?? [];
2242
- if (headlessInstanceComponents.length > 0) {
2243
- const jayHtmlContent = await fs$1.readFile(route.jayHtmlPath, "utf-8");
2244
- const discoveryResult = discoverHeadlessInstances(jayHtmlContent);
2245
- if (discoveryResult.forEachInstances.length > 0) {
2246
- const validationErrors = validateForEachInstances(
2247
- discoveryResult.forEachInstances,
2248
- headlessInstanceComponents
2249
- );
2250
- if (validationErrors.length > 0) {
2251
- getLogger().error(
2252
- `[SlowRender] ForEach instance validation failed: ${validationErrors.join(", ")}`
2253
- );
2254
- res.status(500).end(validationErrors.join("\n"));
2255
- timing?.end();
2256
- return;
2257
- }
2258
- forEachInstancesForFast = discoveryResult.forEachInstances;
2259
- }
2260
- if (discoveryResult.instances.length > 0) {
2261
- const slowResult = await slowRenderInstances(
2262
- discoveryResult.instances,
2263
- headlessInstanceComponents
2264
- );
2265
- if (slowResult) {
2266
- instanceViewStates = { ...slowResult.slowViewStates };
2267
- instancePhaseDataForFast = slowResult.instancePhaseData;
2268
- }
2269
- }
2270
- }
2271
2153
  timing?.recordSlowRender(Date.now() - slowStart);
2154
+ const instancePhaseData = renderedSlowly.carryForward?.__instances;
2272
2155
  const fastStart = Date.now();
2273
2156
  const renderedFast = await renderFastChangingData(
2274
2157
  pageParams,
2275
2158
  pageProps,
2276
2159
  renderedSlowly.carryForward,
2277
- pageParts
2160
+ pageParts,
2161
+ instancePhaseData,
2162
+ forEachInstances,
2163
+ headlessInstanceComponents,
2164
+ renderedSlowly.rendered
2278
2165
  );
2279
- if (instancePhaseDataForFast && instanceViewStates) {
2280
- const instanceFastResult = await renderFastChangingDataForInstances(
2281
- instancePhaseDataForFast,
2282
- headlessInstanceComponents
2283
- );
2284
- if (instanceFastResult) {
2285
- for (const [coordKey, fastVS] of Object.entries(instanceFastResult.viewStates)) {
2286
- instanceViewStates[coordKey] = {
2287
- ...instanceViewStates[coordKey] || {},
2288
- ...fastVS
2289
- };
2290
- }
2291
- }
2292
- }
2293
- if (forEachInstancesForFast && renderedFast.kind === "PhaseOutput") {
2294
- const forEachResult = await renderFastChangingDataForForEachInstances(
2295
- forEachInstancesForFast,
2296
- headlessInstanceComponents,
2297
- { ...renderedSlowly.rendered, ...renderedFast.rendered }
2298
- );
2299
- if (forEachResult) {
2300
- if (!instanceViewStates)
2301
- instanceViewStates = {};
2302
- for (const [coordKey, fastVS] of Object.entries(forEachResult)) {
2303
- instanceViewStates[coordKey] = fastVS;
2304
- }
2305
- }
2306
- }
2307
2166
  timing?.recordFastRender(Date.now() - fastStart);
2308
2167
  if (renderedFast.kind !== "PhaseOutput") {
2309
2168
  handleOtherResponseCodes(res, renderedFast);
2310
2169
  timing?.end();
2311
2170
  return;
2312
2171
  }
2313
- let viewState;
2314
- if (serverTrackByMap && Object.keys(serverTrackByMap).length > 0) {
2315
- viewState = deepMergeViewStates(
2316
- renderedSlowly.rendered,
2317
- renderedFast.rendered,
2318
- serverTrackByMap
2319
- );
2320
- } else {
2321
- viewState = { ...renderedSlowly.rendered, ...renderedFast.rendered };
2322
- }
2323
- if (instanceViewStates && Object.keys(instanceViewStates).length > 0) {
2324
- viewState = {
2325
- ...viewState,
2326
- __headlessInstances: instanceViewStates
2327
- };
2328
- }
2329
- await sendResponse(
2330
- vite,
2331
- res,
2332
- url,
2333
- route.jayHtmlPath,
2334
- pageParts,
2335
- viewState,
2336
- renderedFast.carryForward,
2337
- clientTrackByMap,
2338
- projectInit,
2339
- pluginsForPage,
2340
- options,
2341
- void 0,
2342
- timing
2172
+ const viewState = deepMergeViewStates(
2173
+ renderedSlowly.rendered,
2174
+ renderedFast.rendered,
2175
+ serverTrackByMap || {}
2343
2176
  );
2344
- }
2345
- async function sendResponse(vite, res, url, jayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState, timing) {
2177
+ const fastCF = renderedFast.carryForward;
2346
2178
  const pageHtml = generateClientScript(
2347
2179
  viewState,
2348
- carryForward,
2180
+ fastCF,
2349
2181
  pageParts,
2350
- jayHtmlPath,
2182
+ route.jayHtmlPath,
2351
2183
  clientTrackByMap,
2352
2184
  getClientInitData(),
2353
2185
  projectInit,
2354
2186
  pluginsForPage,
2355
2187
  {
2356
- enableAutomation: !options.disableAutomation,
2357
- slowViewState
2188
+ enableAutomation: !options.disableAutomation
2358
2189
  }
2359
2190
  );
2360
2191
  if (options.buildFolder) {
2361
2192
  const pageName = !url || url === "/" ? "index" : url.replace(/^\//, "").replace(/\//g, "-");
2362
- 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");
2363
2252
  await fs$1.mkdir(clientScriptDir, { recursive: true });
2364
2253
  await fs$1.writeFile(path__default.join(clientScriptDir, `${pageName}.html`), pageHtml, "utf-8");
2365
2254
  }
@@ -2390,6 +2279,11 @@ async function preRenderJayHtml(route, slowViewState, headlessContracts, headles
2390
2279
  return void 0;
2391
2280
  }
2392
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
+ }
2393
2287
  const result = slowRenderTransform({
2394
2288
  jayHtmlContent,
2395
2289
  slowViewState,
@@ -2446,89 +2340,42 @@ async function preRenderJayHtml(route, slowViewState, headlessContracts, headles
2446
2340
  );
2447
2341
  }
2448
2342
  }
2449
- }
2450
- }
2451
- return { preRenderedJayHtml, instancePhaseData, forEachInstances };
2452
- }
2453
- async function renderFastChangingDataForInstances(instancePhaseData, headlessInstanceComponents) {
2454
- const componentByContractName = /* @__PURE__ */ new Map();
2455
- for (const comp of headlessInstanceComponents) {
2456
- componentByContractName.set(comp.contractName, comp);
2457
- }
2458
- const viewStates = {};
2459
- const carryForwards = {};
2460
- let hasResults = false;
2461
- for (const instance of instancePhaseData.discovered) {
2462
- const coordKey = instance.coordinate.join("/");
2463
- const comp = componentByContractName.get(instance.contractName);
2464
- if (!comp || !comp.compDefinition.fastRender) {
2465
- continue;
2466
- }
2467
- const instanceCarryForward = instancePhaseData.carryForwards[coordKey] || {};
2468
- const services = resolveServices(comp.compDefinition.services);
2469
- const fastResult = await comp.compDefinition.fastRender(
2470
- instance.props,
2471
- instanceCarryForward,
2472
- ...services
2473
- );
2474
- if (fastResult.kind === "PhaseOutput") {
2475
- viewStates[coordKey] = fastResult.rendered;
2476
- carryForwards[coordKey] = fastResult.carryForward;
2477
- hasResults = true;
2478
- }
2479
- }
2480
- return hasResults ? { viewStates, carryForwards } : void 0;
2481
- }
2482
- async function renderFastChangingDataForForEachInstances(forEachInstances, headlessInstanceComponents, mergedViewState) {
2483
- const componentByContractName = /* @__PURE__ */ new Map();
2484
- for (const comp of headlessInstanceComponents) {
2485
- componentByContractName.set(comp.contractName, comp);
2486
- }
2487
- const viewStates = {};
2488
- let hasResults = false;
2489
- for (const instance of forEachInstances) {
2490
- const comp = componentByContractName.get(instance.contractName);
2491
- if (!comp)
2492
- continue;
2493
- const items = resolvePathValue(mergedViewState, instance.forEachPath);
2494
- if (!Array.isArray(items))
2495
- continue;
2496
- for (const item of items) {
2497
- const trackByValue = String(item[instance.trackBy]);
2498
- const props = {};
2499
- for (const [propName, binding] of Object.entries(instance.propBindings)) {
2500
- props[propName] = resolveBinding(String(binding), item);
2501
- }
2502
- if (comp.compDefinition.fastRender) {
2503
- const services = resolveServices(comp.compDefinition.services);
2504
- const fastResult = await comp.compDefinition.fastRender(props, ...services);
2505
- if (fastResult.kind === "PhaseOutput") {
2506
- const coord = [trackByValue, instance.coordinateSuffix].toString();
2507
- viewStates[coord] = fastResult.rendered;
2508
- hasResults = true;
2343
+ if (!instancePhaseData) {
2344
+ const componentByContractName = /* @__PURE__ */ new Map();
2345
+ for (const comp of headlessInstanceComponents) {
2346
+ componentByContractName.set(comp.contractName, comp);
2509
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
+ };
2510
2367
  }
2511
2368
  }
2512
2369
  }
2513
- return hasResults ? viewStates : void 0;
2514
- }
2515
- function resolvePathValue(obj, path2) {
2516
- return path2.split(".").reduce((current, segment) => current?.[segment], obj);
2517
- }
2518
- function resolveBinding(binding, item) {
2519
- const match = binding.match(/^\{(.+)\}$/);
2520
- if (match) {
2521
- return String(item[match[1]] ?? "");
2522
- }
2523
- return binding;
2370
+ return { preRenderedJayHtml, instancePhaseData, forEachInstances };
2524
2371
  }
2525
- async function materializeDynamicContracts(projectRootFolder, buildFolder, viteServer) {
2372
+ async function materializeDynamicContracts(projectRootFolder, viteServer) {
2526
2373
  try {
2527
2374
  const services = getServiceRegistry();
2528
2375
  const result = await materializeContracts(
2529
2376
  {
2530
2377
  projectRoot: projectRootFolder,
2531
- outputDir: path__default.join(buildFolder, "materialized-contracts"),
2378
+ outputDir: path__default.join(projectRootFolder, "agent-kit", "materialized-contracts"),
2532
2379
  verbose: false,
2533
2380
  viteServer
2534
2381
  },
@@ -2544,14 +2391,19 @@ async function materializeDynamicContracts(projectRootFolder, buildFolder, viteS
2544
2391
  }
2545
2392
  async function mkDevServer(rawOptions) {
2546
2393
  const options = defaults(rawOptions);
2547
- const {
2548
- publicBaseUrlPath,
2549
- pagesRootFolder,
2550
- projectRootFolder,
2551
- buildFolder,
2552
- jayRollupConfig,
2553
- dontCacheSlowly
2554
- } = 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
+ }
2555
2407
  const viteLogLevel = options.logLevel === "silent" ? "silent" : options.logLevel === "verbose" ? "info" : "warn";
2556
2408
  const lifecycleManager = new ServiceLifecycleManager(projectRootFolder);
2557
2409
  setupGracefulShutdown(lifecycleManager);
@@ -2564,14 +2416,15 @@ async function mkDevServer(rawOptions) {
2564
2416
  });
2565
2417
  lifecycleManager.setViteServer(vite);
2566
2418
  await lifecycleManager.initialize();
2567
- await materializeDynamicContracts(projectRootFolder, buildFolder, vite);
2419
+ await materializeDynamicContracts(projectRootFolder, vite);
2568
2420
  setupServiceHotReload(vite, lifecycleManager);
2569
2421
  setupActionRouter(vite);
2570
- const routes = await initRoutes(pagesRootFolder);
2571
- const slowlyPhase = new DevSlowlyChangingPhase(dontCacheSlowly);
2572
- 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");
2573
2426
  const slowRenderCache = new SlowRenderCache(slowRenderCacheDir, pagesRootFolder);
2574
- setupSlowRenderCacheInvalidation(vite, slowRenderCache, pagesRootFolder);
2427
+ setupSlowRenderCacheInvalidation(vite, slowRenderCache, slowlyPhase, pagesRootFolder);
2575
2428
  const projectInit = lifecycleManager.getProjectInit() ?? void 0;
2576
2429
  const pluginsWithInit = lifecycleManager.getPluginsWithInit();
2577
2430
  const pluginClientInits = preparePluginClientInits(pluginsWithInit);
@@ -2630,12 +2483,14 @@ function setupActionRouter(vite) {
2630
2483
  vite.middlewares.use(ACTION_ENDPOINT_BASE, createActionRouter());
2631
2484
  getLogger().info(`[Actions] Action router mounted at ${ACTION_ENDPOINT_BASE}`);
2632
2485
  }
2633
- function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2486
+ function setupSlowRenderCacheInvalidation(vite, cache, slowlyPhase, pagesRootFolder) {
2634
2487
  vite.watcher.on("change", (changedPath) => {
2635
2488
  if (!changedPath.startsWith(pagesRootFolder)) {
2636
2489
  return;
2637
2490
  }
2638
2491
  if (changedPath.endsWith(".jay-html")) {
2492
+ invalidateServerElementCache(changedPath);
2493
+ slowlyPhase.invalidateLoadParamsCache(changedPath);
2639
2494
  cache.invalidate(changedPath).then(() => {
2640
2495
  getLogger().info(`[SlowRender] Cache invalidated for ${changedPath}`);
2641
2496
  });
@@ -2644,6 +2499,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2644
2499
  if (changedPath.endsWith("page.ts")) {
2645
2500
  const dir = path__default.dirname(changedPath);
2646
2501
  const jayHtmlPath = path__default.join(dir, "page.jay-html");
2502
+ slowlyPhase.invalidateLoadParamsCache(jayHtmlPath);
2647
2503
  cache.invalidate(jayHtmlPath).then(() => {
2648
2504
  getLogger().info(
2649
2505
  `[SlowRender] Cache invalidated for ${jayHtmlPath} (page.ts changed)`
@@ -2653,6 +2509,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2653
2509
  }
2654
2510
  if (changedPath.endsWith(".jay-contract")) {
2655
2511
  const jayHtmlPath = changedPath.replace(".jay-contract", ".jay-html");
2512
+ slowlyPhase.invalidateLoadParamsCache(jayHtmlPath);
2656
2513
  cache.invalidate(jayHtmlPath).then(() => {
2657
2514
  getLogger().info(
2658
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.13.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.13.0",
26
- "@jay-framework/compiler-shared": "^0.13.0",
27
- "@jay-framework/component": "^0.13.0",
28
- "@jay-framework/fullstack-component": "^0.13.0",
29
- "@jay-framework/logger": "^0.13.0",
30
- "@jay-framework/runtime": "^0.13.0",
31
- "@jay-framework/stack-client-runtime": "^0.13.0",
32
- "@jay-framework/stack-route-scanner": "^0.13.0",
33
- "@jay-framework/stack-server-runtime": "^0.13.0",
34
- "@jay-framework/view-state-merge": "^0.13.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.13.0",
39
- "@jay-framework/jay-cli": "^0.13.0",
40
- "@jay-framework/stack-client-runtime": "^0.13.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",