@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.
- package/dist/index.d.ts +12 -0
- package/dist/index.js +508 -69
- 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,
|
|
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
|
|
1654
|
-
const
|
|
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
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1728
|
-
|
|
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:
|
|
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(
|
|
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.
|
|
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.
|
|
26
|
-
"@jay-framework/compiler-shared": "^0.
|
|
27
|
-
"@jay-framework/component": "^0.
|
|
28
|
-
"@jay-framework/fullstack-component": "^0.
|
|
29
|
-
"@jay-framework/runtime": "^0.
|
|
30
|
-
"@jay-framework/stack-client-runtime": "^0.
|
|
31
|
-
"@jay-framework/stack-route-scanner": "^0.
|
|
32
|
-
"@jay-framework/stack-server-runtime": "^0.
|
|
33
|
-
"@jay-framework/view-state-merge": "^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.
|
|
38
|
-
"@jay-framework/jay-cli": "^0.
|
|
39
|
-
"@jay-framework/stack-client-runtime": "^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",
|