@jay-framework/dev-server 0.10.0 → 0.11.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 +12 -0
  2. package/dist/index.js +508 -69
  3. package/package.json +13 -13
package/dist/index.d.ts CHANGED
@@ -9,8 +9,20 @@ interface DevServerOptions {
9
9
  publicBaseUrlPath?: string;
10
10
  projectRootFolder?: string;
11
11
  pagesRootFolder?: string;
12
+ /**
13
+ * Folder where build artifacts are stored.
14
+ * Pre-rendered jay-html files are written to `<buildFolder>/slow-render-cache/`.
15
+ * Defaults to `<projectRootFolder>/build`.
16
+ */
17
+ buildFolder?: string;
12
18
  jayRollupConfig: JayRollupConfig;
13
19
  dontCacheSlowly: boolean;
20
+ /**
21
+ * Disable automation integration.
22
+ * When false (default), pages are wrapped with automation API for dev tooling.
23
+ * The automation API is available at `window.__jay.automation` and via `AUTOMATION_CONTEXT`.
24
+ */
25
+ disableAutomation?: boolean;
14
26
  }
15
27
 
16
28
  /**
package/dist/index.js CHANGED
@@ -6,14 +6,16 @@ var __publicField = (obj, key, value) => {
6
6
  };
7
7
  import { createServer } from "vite";
8
8
  import { scanRoutes, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
9
- import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, DevSlowlyChangingPhase, preparePluginClientInits, loadPageParts, renderFastChangingData, getClientInitData, generateClientScript } from "@jay-framework/stack-server-runtime";
9
+ import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, loadPageParts, renderFastChangingData, generateClientScript, getClientInitData } from "@jay-framework/stack-server-runtime";
10
10
  import { jayRuntime } from "@jay-framework/vite-plugin";
11
11
  import { createRequire } from "module";
12
12
  import "@jay-framework/compiler-shared";
13
13
  import * as path from "node:path";
14
14
  import path__default from "node:path";
15
- import "@jay-framework/compiler-jay-html";
15
+ import { parseContract, slowRenderTransform } from "@jay-framework/compiler-jay-html";
16
+ import { createRequire as createRequire$1 } from "node:module";
16
17
  import * as fs from "node:fs";
18
+ import fs$1 from "node:fs/promises";
17
19
  import { pathToFileURL } from "node:url";
18
20
  const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c$1 = new Proxy(e$1, {
19
21
  get(t, r) {
@@ -1103,6 +1105,130 @@ function createImportChainTracker(options = {}) {
1103
1105
  }
1104
1106
  };
1105
1107
  }
1108
+ const require2 = createRequire$1(import.meta.url);
1109
+ function createDefaultPluginDetector() {
1110
+ const cache = /* @__PURE__ */ new Map();
1111
+ return {
1112
+ isJayPluginWithClientExport(packageName, projectRoot) {
1113
+ const cacheKey = `${packageName}:${projectRoot}`;
1114
+ if (cache.has(cacheKey)) {
1115
+ return cache.get(cacheKey);
1116
+ }
1117
+ let result = false;
1118
+ try {
1119
+ require2.resolve(`${packageName}/plugin.yaml`, { paths: [projectRoot] });
1120
+ try {
1121
+ require2.resolve(`${packageName}/client`, { paths: [projectRoot] });
1122
+ result = true;
1123
+ } catch {
1124
+ result = false;
1125
+ }
1126
+ } catch {
1127
+ result = false;
1128
+ }
1129
+ cache.set(cacheKey, result);
1130
+ return result;
1131
+ }
1132
+ };
1133
+ }
1134
+ function extractPackageName(source) {
1135
+ if (source.startsWith(".") || source.startsWith("/")) {
1136
+ return null;
1137
+ }
1138
+ if (source.startsWith("@")) {
1139
+ const parts2 = source.split("/");
1140
+ if (parts2.length >= 2) {
1141
+ return `${parts2[0]}/${parts2[1]}`;
1142
+ }
1143
+ return null;
1144
+ }
1145
+ const parts = source.split("/");
1146
+ return parts[0];
1147
+ }
1148
+ function isSubpathImport(source, packageName) {
1149
+ return source.length > packageName.length && source[packageName.length] === "/";
1150
+ }
1151
+ const IMPORT_REGEX = /import\s+(.+?)\s+from\s+(['"])([^'"]+)\2/g;
1152
+ const EXPORT_FROM_REGEX = /export\s+(.+?)\s+from\s+(['"])([^'"]+)\2/g;
1153
+ function transformImports(options) {
1154
+ const { code, projectRoot, filePath, pluginDetector, verbose = false } = options;
1155
+ let hasChanges = false;
1156
+ let result = code;
1157
+ result = result.replace(IMPORT_REGEX, (match, clause, quote, source) => {
1158
+ const packageName = extractPackageName(source);
1159
+ if (!packageName)
1160
+ return match;
1161
+ if (isSubpathImport(source, packageName))
1162
+ return match;
1163
+ if (!pluginDetector.isJayPluginWithClientExport(packageName, projectRoot))
1164
+ return match;
1165
+ hasChanges = true;
1166
+ const newSource = `${packageName}/client`;
1167
+ if (verbose) {
1168
+ console.log(
1169
+ `[plugin-client-import] Rewriting import ${source} -> ${newSource} (in ${path.basename(filePath)})`
1170
+ );
1171
+ }
1172
+ return `import ${clause} from ${quote}${newSource}${quote}`;
1173
+ });
1174
+ result = result.replace(EXPORT_FROM_REGEX, (match, clause, quote, source) => {
1175
+ const packageName = extractPackageName(source);
1176
+ if (!packageName)
1177
+ return match;
1178
+ if (isSubpathImport(source, packageName))
1179
+ return match;
1180
+ if (!pluginDetector.isJayPluginWithClientExport(packageName, projectRoot))
1181
+ return match;
1182
+ hasChanges = true;
1183
+ const newSource = `${packageName}/client`;
1184
+ if (verbose) {
1185
+ console.log(
1186
+ `[plugin-client-import] Rewriting export ${source} -> ${newSource} (in ${path.basename(filePath)})`
1187
+ );
1188
+ }
1189
+ return `export ${clause} from ${quote}${newSource}${quote}`;
1190
+ });
1191
+ return { code: result, hasChanges };
1192
+ }
1193
+ function createPluginClientImportResolver(options = {}) {
1194
+ const { verbose = false } = options;
1195
+ let projectRoot = options.projectRoot || process.cwd();
1196
+ let isSSRBuild = false;
1197
+ const pluginDetector = options.pluginDetector || createDefaultPluginDetector();
1198
+ return {
1199
+ name: "jay-stack:plugin-client-import",
1200
+ enforce: "pre",
1201
+ configResolved(config) {
1202
+ projectRoot = config.root || projectRoot;
1203
+ isSSRBuild = !!config.build?.ssr;
1204
+ },
1205
+ transform(code, id, transformOptions) {
1206
+ if (transformOptions?.ssr || isSSRBuild) {
1207
+ return null;
1208
+ }
1209
+ if (!id.endsWith(".ts") && !id.endsWith(".js") && !id.includes(".ts?") && !id.includes(".js?")) {
1210
+ return null;
1211
+ }
1212
+ if (id.includes("node_modules") && !id.includes("@jay-framework")) {
1213
+ return null;
1214
+ }
1215
+ if (!code.includes("@jay-framework/") && !code.includes("from '@") && !code.includes('from "@')) {
1216
+ return null;
1217
+ }
1218
+ const result = transformImports({
1219
+ code,
1220
+ projectRoot,
1221
+ filePath: id,
1222
+ pluginDetector,
1223
+ verbose
1224
+ });
1225
+ if (!result.hasChanges) {
1226
+ return null;
1227
+ }
1228
+ return { code: result.code };
1229
+ }
1230
+ };
1231
+ }
1106
1232
  function jayStackCompiler(options = {}) {
1107
1233
  const { trackImports, ...jayOptions } = options;
1108
1234
  const moduleCache = /* @__PURE__ */ new Map();
@@ -1112,6 +1238,7 @@ function jayStackCompiler(options = {}) {
1112
1238
  if (shouldTrackImports) {
1113
1239
  plugins.push(createImportChainTracker(trackerOptions));
1114
1240
  }
1241
+ plugins.push(createPluginClientImportResolver({ verbose: !!shouldTrackImports }));
1115
1242
  plugins.push(
1116
1243
  // First: Jay Stack code splitting transformation
1117
1244
  {
@@ -1172,6 +1299,13 @@ function jayStackCompiler(options = {}) {
1172
1299
  } else {
1173
1300
  return null;
1174
1301
  }
1302
+ } else if (resolvedPath.endsWith(".js") && !fs.existsSync(resolvedPath)) {
1303
+ const tsPath = resolvedPath.slice(0, -3) + ".ts";
1304
+ if (fs.existsSync(tsPath)) {
1305
+ resolvedPath = tsPath;
1306
+ } else {
1307
+ return null;
1308
+ }
1175
1309
  }
1176
1310
  return `\0jay-action:${resolvedPath}`;
1177
1311
  },
@@ -1630,11 +1764,13 @@ function defaults(options) {
1630
1764
  projectRootFolder,
1631
1765
  options.pagesRootFolder || "./src/pages"
1632
1766
  );
1767
+ const buildFolder = options.buildFolder || path__default.resolve(projectRootFolder, "./build");
1633
1768
  const tsConfigFilePath = options.jayRollupConfig.tsConfigFilePath || path__default.resolve(projectRootFolder, "./tsconfig.json");
1634
1769
  return {
1635
1770
  publicBaseUrlPath,
1636
1771
  pagesRootFolder,
1637
1772
  projectRootFolder,
1773
+ buildFolder,
1638
1774
  dontCacheSlowly: options.dontCacheSlowly,
1639
1775
  jayRollupConfig: {
1640
1776
  ...options.jayRollupConfig || {},
@@ -1650,82 +1786,96 @@ function handleOtherResponseCodes(res, renderedResult) {
1650
1786
  else
1651
1787
  res.status(renderedResult.status).end("redirect to " + renderedResult.location);
1652
1788
  }
1653
- function mkRoute(route, vite, slowlyPhase, options, projectInit, allPluginsWithInit = [], allPluginClientInits = []) {
1654
- const path2 = routeToExpressRoute(route);
1789
+ function filterPluginsForPage(allPluginClientInits, allPluginsWithInit, usedPackages) {
1790
+ const pluginsByPackage = /* @__PURE__ */ new Map();
1791
+ for (const plugin of allPluginsWithInit) {
1792
+ pluginsByPackage.set(plugin.packageName, plugin);
1793
+ }
1794
+ const expandedPackages = new Set(usedPackages);
1795
+ const toProcess = [...usedPackages];
1796
+ while (toProcess.length > 0) {
1797
+ const packageName = toProcess.pop();
1798
+ const plugin = pluginsByPackage.get(packageName);
1799
+ if (!plugin)
1800
+ continue;
1801
+ for (const dep of plugin.dependencies) {
1802
+ if (pluginsByPackage.has(dep) && !expandedPackages.has(dep)) {
1803
+ expandedPackages.add(dep);
1804
+ toProcess.push(dep);
1805
+ }
1806
+ }
1807
+ }
1808
+ return allPluginClientInits.filter((plugin) => {
1809
+ const pluginInfo = allPluginsWithInit.find((p) => p.name === plugin.name);
1810
+ return pluginInfo && expandedPackages.has(pluginInfo.packageName);
1811
+ });
1812
+ }
1813
+ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit, allPluginsWithInit = [], allPluginClientInits = []) {
1814
+ const routePath = routeToExpressRoute(route);
1655
1815
  const handler = async (req, res) => {
1656
1816
  try {
1657
1817
  const url = req.originalUrl.replace(options.publicBaseUrlPath, "");
1658
- const pageParams = req.params;
1818
+ const pageParams = { ...route.inferredParams, ...req.params };
1659
1819
  const pageProps = {
1660
1820
  language: "en",
1661
1821
  url
1662
1822
  };
1663
- let viewState, carryForward;
1664
- const pagePartsResult = await loadPageParts(
1665
- vite,
1666
- route,
1667
- options.pagesRootFolder,
1668
- options.projectRootFolder,
1669
- options.jayRollupConfig
1670
- );
1671
- if (pagePartsResult.val) {
1672
- const {
1673
- parts: pageParts,
1674
- serverTrackByMap,
1675
- clientTrackByMap,
1676
- usedPackages
1677
- } = pagePartsResult.val;
1678
- const pluginsForPage = allPluginClientInits.filter((plugin) => {
1679
- const pluginInfo = allPluginsWithInit.find((p) => p.name === plugin.name);
1680
- return pluginInfo && usedPackages.has(pluginInfo.packageName);
1681
- });
1682
- const renderedSlowly = await slowlyPhase.runSlowlyForPage(
1823
+ const useSlowRenderCache = !options.dontCacheSlowly;
1824
+ let cachedEntry = useSlowRenderCache ? slowRenderCache.get(route.jayHtmlPath, pageParams) : void 0;
1825
+ if (cachedEntry) {
1826
+ try {
1827
+ await fs$1.access(cachedEntry.preRenderedPath);
1828
+ } catch {
1829
+ console.log(
1830
+ `[SlowRender] Cached file missing, rebuilding: ${cachedEntry.preRenderedPath}`
1831
+ );
1832
+ await slowRenderCache.invalidate(route.jayHtmlPath);
1833
+ cachedEntry = void 0;
1834
+ }
1835
+ }
1836
+ if (cachedEntry) {
1837
+ await handleCachedRequest(
1838
+ vite,
1839
+ route,
1840
+ options,
1841
+ cachedEntry,
1683
1842
  pageParams,
1684
1843
  pageProps,
1685
- pageParts
1844
+ allPluginClientInits,
1845
+ allPluginsWithInit,
1846
+ projectInit,
1847
+ res,
1848
+ url
1849
+ );
1850
+ } else if (useSlowRenderCache) {
1851
+ await handlePreRenderRequest(
1852
+ vite,
1853
+ route,
1854
+ options,
1855
+ slowlyPhase,
1856
+ slowRenderCache,
1857
+ pageParams,
1858
+ pageProps,
1859
+ allPluginClientInits,
1860
+ allPluginsWithInit,
1861
+ projectInit,
1862
+ res,
1863
+ url
1686
1864
  );
1687
- if (renderedSlowly.kind === "PhaseOutput") {
1688
- const renderedFast = await renderFastChangingData(
1689
- pageParams,
1690
- pageProps,
1691
- renderedSlowly.carryForward,
1692
- pageParts
1693
- );
1694
- if (renderedFast.kind === "PhaseOutput") {
1695
- if (serverTrackByMap && Object.keys(serverTrackByMap).length > 0) {
1696
- viewState = deepMergeViewStates(
1697
- renderedSlowly.rendered,
1698
- renderedFast.rendered,
1699
- serverTrackByMap
1700
- );
1701
- } else {
1702
- viewState = { ...renderedSlowly.rendered, ...renderedFast.rendered };
1703
- }
1704
- carryForward = renderedFast.carryForward;
1705
- const pageHtml = generateClientScript(
1706
- viewState,
1707
- carryForward,
1708
- pageParts,
1709
- route.jayHtmlPath,
1710
- clientTrackByMap,
1711
- getClientInitData(),
1712
- projectInit,
1713
- pluginsForPage
1714
- );
1715
- const compiledPageHtml = await vite.transformIndexHtml(
1716
- !!url ? url : "/",
1717
- pageHtml
1718
- );
1719
- res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
1720
- } else {
1721
- handleOtherResponseCodes(res, renderedFast);
1722
- }
1723
- } else if (renderedSlowly.kind === "ClientError") {
1724
- handleOtherResponseCodes(res, renderedSlowly);
1725
- }
1726
1865
  } else {
1727
- console.log(pagePartsResult.validations.join("\n"));
1728
- res.status(500).end(pagePartsResult.validations.join("\n"));
1866
+ await handleDirectRequest(
1867
+ vite,
1868
+ route,
1869
+ options,
1870
+ slowlyPhase,
1871
+ pageParams,
1872
+ pageProps,
1873
+ allPluginClientInits,
1874
+ allPluginsWithInit,
1875
+ projectInit,
1876
+ res,
1877
+ url
1878
+ );
1729
1879
  }
1730
1880
  } catch (e2) {
1731
1881
  vite?.ssrFixStacktrace(e2);
@@ -1733,13 +1883,262 @@ function mkRoute(route, vite, slowlyPhase, options, projectInit, allPluginsWithI
1733
1883
  res.status(500).end(e2.stack);
1734
1884
  }
1735
1885
  };
1736
- return { path: path2, handler, fsRoute: route };
1886
+ return { path: routePath, handler, fsRoute: route };
1887
+ }
1888
+ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url) {
1889
+ const pagePartsResult = await loadPageParts(
1890
+ vite,
1891
+ route,
1892
+ options.pagesRootFolder,
1893
+ options.projectRootFolder,
1894
+ options.jayRollupConfig,
1895
+ { preRenderedPath: cachedEntry.preRenderedPath }
1896
+ );
1897
+ if (!pagePartsResult.val) {
1898
+ console.log(pagePartsResult.validations.join("\n"));
1899
+ res.status(500).end(pagePartsResult.validations.join("\n"));
1900
+ return;
1901
+ }
1902
+ const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
1903
+ const pluginsForPage = filterPluginsForPage(
1904
+ allPluginClientInits,
1905
+ allPluginsWithInit,
1906
+ usedPackages
1907
+ );
1908
+ const renderedFast = await renderFastChangingData(
1909
+ pageParams,
1910
+ pageProps,
1911
+ cachedEntry.carryForward,
1912
+ pageParts
1913
+ );
1914
+ if (renderedFast.kind !== "PhaseOutput") {
1915
+ handleOtherResponseCodes(res, renderedFast);
1916
+ return;
1917
+ }
1918
+ await sendResponse(
1919
+ vite,
1920
+ res,
1921
+ url,
1922
+ cachedEntry.preRenderedPath,
1923
+ pageParts,
1924
+ renderedFast.rendered,
1925
+ renderedFast.carryForward,
1926
+ clientTrackByMap,
1927
+ projectInit,
1928
+ pluginsForPage,
1929
+ options,
1930
+ cachedEntry.slowViewState
1931
+ );
1932
+ }
1933
+ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRenderCache, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url) {
1934
+ const initialPartsResult = await loadPageParts(
1935
+ vite,
1936
+ route,
1937
+ options.pagesRootFolder,
1938
+ options.projectRootFolder,
1939
+ options.jayRollupConfig
1940
+ );
1941
+ if (!initialPartsResult.val) {
1942
+ console.log(initialPartsResult.validations.join("\n"));
1943
+ res.status(500).end(initialPartsResult.validations.join("\n"));
1944
+ return;
1945
+ }
1946
+ const renderedSlowly = await slowlyPhase.runSlowlyForPage(
1947
+ pageParams,
1948
+ pageProps,
1949
+ initialPartsResult.val.parts
1950
+ );
1951
+ if (renderedSlowly.kind !== "PhaseOutput") {
1952
+ if (renderedSlowly.kind === "ClientError") {
1953
+ handleOtherResponseCodes(res, renderedSlowly);
1954
+ }
1955
+ return;
1956
+ }
1957
+ const preRenderedContent = await preRenderJayHtml(route, renderedSlowly.rendered);
1958
+ if (!preRenderedContent) {
1959
+ res.status(500).end("Failed to pre-render jay-html");
1960
+ return;
1961
+ }
1962
+ const preRenderedPath = await slowRenderCache.set(
1963
+ route.jayHtmlPath,
1964
+ pageParams,
1965
+ preRenderedContent,
1966
+ renderedSlowly.rendered,
1967
+ renderedSlowly.carryForward
1968
+ );
1969
+ console.log(`[SlowRender] Cached pre-rendered jay-html at ${preRenderedPath}`);
1970
+ const pagePartsResult = await loadPageParts(
1971
+ vite,
1972
+ route,
1973
+ options.pagesRootFolder,
1974
+ options.projectRootFolder,
1975
+ options.jayRollupConfig,
1976
+ { preRenderedPath }
1977
+ );
1978
+ if (!pagePartsResult.val) {
1979
+ console.log(pagePartsResult.validations.join("\n"));
1980
+ res.status(500).end(pagePartsResult.validations.join("\n"));
1981
+ return;
1982
+ }
1983
+ const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
1984
+ const pluginsForPage = filterPluginsForPage(
1985
+ allPluginClientInits,
1986
+ allPluginsWithInit,
1987
+ usedPackages
1988
+ );
1989
+ const renderedFast = await renderFastChangingData(
1990
+ pageParams,
1991
+ pageProps,
1992
+ renderedSlowly.carryForward,
1993
+ pageParts
1994
+ );
1995
+ if (renderedFast.kind !== "PhaseOutput") {
1996
+ handleOtherResponseCodes(res, renderedFast);
1997
+ return;
1998
+ }
1999
+ await sendResponse(
2000
+ vite,
2001
+ res,
2002
+ url,
2003
+ preRenderedPath,
2004
+ pageParts,
2005
+ renderedFast.rendered,
2006
+ renderedFast.carryForward,
2007
+ clientTrackByMap,
2008
+ projectInit,
2009
+ pluginsForPage,
2010
+ options,
2011
+ renderedSlowly.rendered
2012
+ );
2013
+ }
2014
+ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url) {
2015
+ const pagePartsResult = await loadPageParts(
2016
+ vite,
2017
+ route,
2018
+ options.pagesRootFolder,
2019
+ options.projectRootFolder,
2020
+ options.jayRollupConfig
2021
+ );
2022
+ if (!pagePartsResult.val) {
2023
+ console.log(pagePartsResult.validations.join("\n"));
2024
+ res.status(500).end(pagePartsResult.validations.join("\n"));
2025
+ return;
2026
+ }
2027
+ const {
2028
+ parts: pageParts,
2029
+ serverTrackByMap,
2030
+ clientTrackByMap,
2031
+ usedPackages
2032
+ } = pagePartsResult.val;
2033
+ const pluginsForPage = filterPluginsForPage(
2034
+ allPluginClientInits,
2035
+ allPluginsWithInit,
2036
+ usedPackages
2037
+ );
2038
+ const renderedSlowly = await slowlyPhase.runSlowlyForPage(pageParams, pageProps, pageParts);
2039
+ if (renderedSlowly.kind !== "PhaseOutput") {
2040
+ if (renderedSlowly.kind === "ClientError") {
2041
+ handleOtherResponseCodes(res, renderedSlowly);
2042
+ }
2043
+ return;
2044
+ }
2045
+ const renderedFast = await renderFastChangingData(
2046
+ pageParams,
2047
+ pageProps,
2048
+ renderedSlowly.carryForward,
2049
+ pageParts
2050
+ );
2051
+ if (renderedFast.kind !== "PhaseOutput") {
2052
+ handleOtherResponseCodes(res, renderedFast);
2053
+ return;
2054
+ }
2055
+ let viewState;
2056
+ if (serverTrackByMap && Object.keys(serverTrackByMap).length > 0) {
2057
+ viewState = deepMergeViewStates(
2058
+ renderedSlowly.rendered,
2059
+ renderedFast.rendered,
2060
+ serverTrackByMap
2061
+ );
2062
+ } else {
2063
+ viewState = { ...renderedSlowly.rendered, ...renderedFast.rendered };
2064
+ }
2065
+ await sendResponse(
2066
+ vite,
2067
+ res,
2068
+ url,
2069
+ route.jayHtmlPath,
2070
+ pageParts,
2071
+ viewState,
2072
+ renderedFast.carryForward,
2073
+ clientTrackByMap,
2074
+ projectInit,
2075
+ pluginsForPage,
2076
+ options
2077
+ );
2078
+ }
2079
+ async function sendResponse(vite, res, url, jayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState) {
2080
+ const pageHtml = generateClientScript(
2081
+ viewState,
2082
+ carryForward,
2083
+ pageParts,
2084
+ jayHtmlPath,
2085
+ clientTrackByMap,
2086
+ getClientInitData(),
2087
+ projectInit,
2088
+ pluginsForPage,
2089
+ {
2090
+ enableAutomation: !options.disableAutomation,
2091
+ slowViewState
2092
+ }
2093
+ );
2094
+ const compiledPageHtml = await vite.transformIndexHtml(!!url ? url : "/", pageHtml);
2095
+ res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
2096
+ }
2097
+ async function preRenderJayHtml(route, slowViewState) {
2098
+ const jayHtmlContent = await fs$1.readFile(route.jayHtmlPath, "utf-8");
2099
+ const contractPath = route.jayHtmlPath.replace(".jay-html", ".jay-contract");
2100
+ let contract;
2101
+ try {
2102
+ const contractContent = await fs$1.readFile(contractPath, "utf-8");
2103
+ const parseResult = parseContract(contractContent, path__default.basename(contractPath));
2104
+ if (parseResult.val) {
2105
+ contract = parseResult.val;
2106
+ } else if (parseResult.validations.length > 0) {
2107
+ console.error(
2108
+ `[SlowRender] Contract parse error for ${contractPath}:`,
2109
+ parseResult.validations
2110
+ );
2111
+ return void 0;
2112
+ }
2113
+ } catch (error) {
2114
+ if (error.code !== "ENOENT") {
2115
+ console.error(`[SlowRender] Error reading contract ${contractPath}:`, error);
2116
+ return void 0;
2117
+ }
2118
+ }
2119
+ const result = slowRenderTransform({
2120
+ jayHtmlContent,
2121
+ slowViewState,
2122
+ contract,
2123
+ sourceDir: path__default.dirname(route.jayHtmlPath)
2124
+ });
2125
+ if (result.val) {
2126
+ return result.val.preRenderedJayHtml;
2127
+ }
2128
+ if (result.validations.length > 0) {
2129
+ console.error(
2130
+ `[SlowRender] Transform failed for ${route.jayHtmlPath}:`,
2131
+ result.validations
2132
+ );
2133
+ }
2134
+ return void 0;
1737
2135
  }
1738
2136
  async function mkDevServer(options) {
1739
2137
  const {
1740
2138
  publicBaseUrlPath,
1741
2139
  pagesRootFolder,
1742
2140
  projectRootFolder,
2141
+ buildFolder,
1743
2142
  jayRollupConfig,
1744
2143
  dontCacheSlowly
1745
2144
  } = defaults(options);
@@ -1763,11 +2162,23 @@ async function mkDevServer(options) {
1763
2162
  setupActionRouter(vite);
1764
2163
  const routes = await initRoutes(pagesRootFolder);
1765
2164
  const slowlyPhase = new DevSlowlyChangingPhase(dontCacheSlowly);
2165
+ const slowRenderCacheDir = path__default.join(buildFolder, "slow-render-cache");
2166
+ const slowRenderCache = new SlowRenderCache(slowRenderCacheDir, pagesRootFolder);
2167
+ setupSlowRenderCacheInvalidation(vite, slowRenderCache, pagesRootFolder);
1766
2168
  const projectInit = lifecycleManager.getProjectInit() ?? void 0;
1767
2169
  const pluginsWithInit = lifecycleManager.getPluginsWithInit();
1768
2170
  const pluginClientInits = preparePluginClientInits(pluginsWithInit);
1769
2171
  const devServerRoutes = routes.map(
1770
- (route) => mkRoute(route, vite, slowlyPhase, options, projectInit, pluginsWithInit, pluginClientInits)
2172
+ (route) => mkRoute(
2173
+ route,
2174
+ vite,
2175
+ slowlyPhase,
2176
+ options,
2177
+ slowRenderCache,
2178
+ projectInit,
2179
+ pluginsWithInit,
2180
+ pluginClientInits
2181
+ )
1771
2182
  );
1772
2183
  return {
1773
2184
  server: vite.middlewares,
@@ -1812,6 +2223,34 @@ function setupActionRouter(vite) {
1812
2223
  vite.middlewares.use(ACTION_ENDPOINT_BASE, createActionRouter());
1813
2224
  console.log(`[Actions] Action router mounted at ${ACTION_ENDPOINT_BASE}`);
1814
2225
  }
2226
+ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2227
+ vite.watcher.on("change", (changedPath) => {
2228
+ if (!changedPath.startsWith(pagesRootFolder)) {
2229
+ return;
2230
+ }
2231
+ if (changedPath.endsWith(".jay-html")) {
2232
+ cache.invalidate(changedPath).then(() => {
2233
+ console.log(`[SlowRender] Cache invalidated for ${changedPath}`);
2234
+ });
2235
+ return;
2236
+ }
2237
+ if (changedPath.endsWith("page.ts")) {
2238
+ const dir = path__default.dirname(changedPath);
2239
+ const jayHtmlPath = path__default.join(dir, "page.jay-html");
2240
+ cache.invalidate(jayHtmlPath).then(() => {
2241
+ console.log(`[SlowRender] Cache invalidated for ${jayHtmlPath} (page.ts changed)`);
2242
+ });
2243
+ return;
2244
+ }
2245
+ if (changedPath.endsWith(".jay-contract")) {
2246
+ const jayHtmlPath = changedPath.replace(".jay-contract", ".jay-html");
2247
+ cache.invalidate(jayHtmlPath).then(() => {
2248
+ console.log(`[SlowRender] Cache invalidated for ${jayHtmlPath} (contract changed)`);
2249
+ });
2250
+ return;
2251
+ }
2252
+ });
2253
+ }
1815
2254
  export {
1816
2255
  ACTION_ENDPOINT_BASE,
1817
2256
  actionBodyParser,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/dev-server",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -22,21 +22,21 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@jay-framework/compiler-jay-stack": "^0.10.0",
26
- "@jay-framework/compiler-shared": "^0.10.0",
27
- "@jay-framework/component": "^0.10.0",
28
- "@jay-framework/fullstack-component": "^0.10.0",
29
- "@jay-framework/runtime": "^0.10.0",
30
- "@jay-framework/stack-client-runtime": "^0.10.0",
31
- "@jay-framework/stack-route-scanner": "^0.10.0",
32
- "@jay-framework/stack-server-runtime": "^0.10.0",
33
- "@jay-framework/view-state-merge": "^0.10.0",
25
+ "@jay-framework/compiler-jay-stack": "^0.11.0",
26
+ "@jay-framework/compiler-shared": "^0.11.0",
27
+ "@jay-framework/component": "^0.11.0",
28
+ "@jay-framework/fullstack-component": "^0.11.0",
29
+ "@jay-framework/runtime": "^0.11.0",
30
+ "@jay-framework/stack-client-runtime": "^0.11.0",
31
+ "@jay-framework/stack-route-scanner": "^0.11.0",
32
+ "@jay-framework/stack-server-runtime": "^0.11.0",
33
+ "@jay-framework/view-state-merge": "^0.11.0",
34
34
  "vite": "^5.0.11"
35
35
  },
36
36
  "devDependencies": {
37
- "@jay-framework/dev-environment": "^0.10.0",
38
- "@jay-framework/jay-cli": "^0.10.0",
39
- "@jay-framework/stack-client-runtime": "^0.10.0",
37
+ "@jay-framework/dev-environment": "^0.11.0",
38
+ "@jay-framework/jay-cli": "^0.11.0",
39
+ "@jay-framework/stack-client-runtime": "^0.11.0",
40
40
  "@types/express": "^5.0.2",
41
41
  "@types/node": "^22.15.21",
42
42
  "nodemon": "^3.0.3",