@jay-framework/dev-server 0.15.5 → 0.16.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 +61 -1
- package/dist/index.js +457 -44
- package/package.json +14 -14
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { JayRollupConfig } from '@jay-framework/rollup-plugin';
|
|
|
5
5
|
import { LogLevel } from '@jay-framework/logger';
|
|
6
6
|
import { Server } from 'node:http';
|
|
7
7
|
import { ProjectClientInitInfo, PluginWithInit, ActionRegistry } from '@jay-framework/stack-server-runtime';
|
|
8
|
+
import * as _jay_framework_fullstack_component from '@jay-framework/fullstack-component';
|
|
8
9
|
import { RequestHandler as RequestHandler$1 } from 'express';
|
|
9
10
|
import { JayRollupConfig as JayRollupConfig$1 } from '@jay-framework/compiler-jay-stack';
|
|
10
11
|
|
|
@@ -103,6 +104,62 @@ declare class ServiceLifecycleManager {
|
|
|
103
104
|
isReady(): boolean;
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Page Freeze — Design Log #127
|
|
109
|
+
*
|
|
110
|
+
* Captures a page's ViewState at a point in time and serves it as a
|
|
111
|
+
* static SSR snapshot. Enables designers to create multiple frozen
|
|
112
|
+
* views of a page in different states for side-by-side comparison.
|
|
113
|
+
*/
|
|
114
|
+
interface FreezeEntry {
|
|
115
|
+
id: string;
|
|
116
|
+
name?: string;
|
|
117
|
+
/** The concrete URL path (e.g., /products/kitan) */
|
|
118
|
+
route: string;
|
|
119
|
+
/** The route pattern (e.g., /products/kitan{/:category}) */
|
|
120
|
+
routePattern?: string;
|
|
121
|
+
viewState: object;
|
|
122
|
+
createdAt: string;
|
|
123
|
+
}
|
|
124
|
+
declare class FreezeStore {
|
|
125
|
+
private readonly dir;
|
|
126
|
+
constructor(buildFolder: string);
|
|
127
|
+
save(route: string, viewState: object, routePattern?: string): Promise<FreezeEntry>;
|
|
128
|
+
get(id: string): Promise<FreezeEntry | undefined>;
|
|
129
|
+
list(route?: string): Promise<FreezeEntry[]>;
|
|
130
|
+
rename(id: string, name: string): Promise<boolean>;
|
|
131
|
+
delete(id: string): Promise<boolean>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Service marker for DevServerService.
|
|
136
|
+
* Use with `.withServices(DEV_SERVER_SERVICE)` in actions and components.
|
|
137
|
+
*/
|
|
138
|
+
declare const DEV_SERVER_SERVICE: _jay_framework_fullstack_component.ServiceMarker<DevServerService>;
|
|
139
|
+
interface RouteInfo {
|
|
140
|
+
path: string;
|
|
141
|
+
jayHtmlPath: string;
|
|
142
|
+
compPath: string;
|
|
143
|
+
}
|
|
144
|
+
declare class DevServerService {
|
|
145
|
+
private routes;
|
|
146
|
+
private vite;
|
|
147
|
+
private pagesBase;
|
|
148
|
+
private projectBase;
|
|
149
|
+
private jayRollupConfig;
|
|
150
|
+
private _freezeStore?;
|
|
151
|
+
constructor(routes: DevServerRoute[], vite: ViteDevServer, pagesBase: string, projectBase: string, jayRollupConfig: JayRollupConfig, _freezeStore?: FreezeStore);
|
|
152
|
+
get freezeStore(): FreezeStore | undefined;
|
|
153
|
+
/** List all page routes in the project. */
|
|
154
|
+
listRoutes(): RouteInfo[];
|
|
155
|
+
/**
|
|
156
|
+
* Run loadParams for a route, yielding param batches as an async generator.
|
|
157
|
+
* Loads all page parts (page component + keyed headless components) and
|
|
158
|
+
* calls loadParams on each one that defines it.
|
|
159
|
+
*/
|
|
160
|
+
loadRouteParams(routePath: string): AsyncGenerator<Record<string, string>[]>;
|
|
161
|
+
}
|
|
162
|
+
|
|
106
163
|
interface DevServerRoute {
|
|
107
164
|
path: string;
|
|
108
165
|
handler: RequestHandler;
|
|
@@ -113,6 +170,9 @@ interface DevServer {
|
|
|
113
170
|
viteServer: ViteDevServer;
|
|
114
171
|
routes: DevServerRoute[];
|
|
115
172
|
lifecycleManager: ServiceLifecycleManager;
|
|
173
|
+
freezeStore?: FreezeStore;
|
|
174
|
+
/** Public API for design board applications and CLI (DL#128) */
|
|
175
|
+
service: DevServerService;
|
|
116
176
|
}
|
|
117
177
|
declare function mkDevServer(rawOptions: DevServerOptions): Promise<DevServer>;
|
|
118
178
|
|
|
@@ -207,4 +267,4 @@ declare function createViteForCli(options: {
|
|
|
207
267
|
tsConfigFilePath?: string;
|
|
208
268
|
}): Promise<ViteDevServer>;
|
|
209
269
|
|
|
210
|
-
export { ACTION_ENDPOINT_BASE, type ActionRouterOptions, type CreateViteServerOptions, type DevServer, type DevServerOptions, type DevServerRoute, actionBodyParser, createActionRouter, createViteForCli, createViteServer, mkDevServer };
|
|
270
|
+
export { ACTION_ENDPOINT_BASE, type ActionRouterOptions, type CreateViteServerOptions, DEV_SERVER_SERVICE, type DevServer, type DevServerOptions, type DevServerRoute, DevServerService, type FreezeEntry, FreezeStore, type RouteInfo, actionBodyParser, createActionRouter, createViteForCli, createViteServer, mkDevServer };
|
package/dist/index.js
CHANGED
|
@@ -10,14 +10,18 @@ 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 { JAY_IMPORT_RESOLVER, injectHeadfullFSTemplates, parseContract, slowRenderTransform, discoverHeadlessInstances, resolveHeadlessInstances } from "@jay-framework/compiler-jay-html";
|
|
13
|
+
import { JAY_IMPORT_RESOLVER, injectHeadfullFSTemplates, parseContract, slowRenderTransform, discoverHeadlessInstances, assignCoordinatesToJayHtml, 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
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
17
|
+
import fs__default$1 from "node:fs";
|
|
18
|
+
import { scanRoutes, createRoute, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
|
|
19
|
+
import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, loadPageParts, runLoadParams, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, registerService, clearServerElementCache, scanPlugins, getServiceRegistry, materializeContracts, renderFastChangingData, mergeHeadTags, generateClientScript, getClientInitData, generateSSRPageHtml, generateFrozenPageHtml, validateForEachInstances, slowRenderInstances } from "@jay-framework/stack-server-runtime";
|
|
20
|
+
import * as fs$1 from "node:fs/promises";
|
|
21
|
+
import fs__default from "node:fs/promises";
|
|
20
22
|
import { pathToFileURL } from "node:url";
|
|
23
|
+
import { randomUUID } from "node:crypto";
|
|
24
|
+
import { createJayService } from "@jay-framework/fullstack-component";
|
|
21
25
|
const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c$1 = new Proxy(e$1, {
|
|
22
26
|
get(t, r) {
|
|
23
27
|
return t[r];
|
|
@@ -927,9 +931,16 @@ function extractActionFromExpression(node) {
|
|
|
927
931
|
}
|
|
928
932
|
if (c$1.isIdentifier(expr)) {
|
|
929
933
|
const funcName = expr.text;
|
|
930
|
-
if (funcName === "makeJayAction" || funcName === "makeJayQuery") {
|
|
934
|
+
if (funcName === "makeJayAction" || funcName === "makeJayQuery" || funcName === "makeJayStream") {
|
|
931
935
|
const nameArg = current.arguments[0];
|
|
932
936
|
if (nameArg && c$1.isStringLiteral(nameArg)) {
|
|
937
|
+
if (funcName === "makeJayStream") {
|
|
938
|
+
return {
|
|
939
|
+
actionName: nameArg.text,
|
|
940
|
+
method: "POST",
|
|
941
|
+
isStreaming: true
|
|
942
|
+
};
|
|
943
|
+
}
|
|
933
944
|
method = funcName === "makeJayQuery" ? "GET" : "POST";
|
|
934
945
|
if (explicitMethod) {
|
|
935
946
|
method = explicitMethod;
|
|
@@ -1200,7 +1211,7 @@ function createPluginClientImportResolver(options = {}) {
|
|
|
1200
1211
|
const pluginDetector = options.pluginDetector || createDefaultPluginDetector();
|
|
1201
1212
|
return {
|
|
1202
1213
|
name: "jay-stack:plugin-client-import",
|
|
1203
|
-
enforce: "
|
|
1214
|
+
enforce: "post",
|
|
1204
1215
|
configResolved(config) {
|
|
1205
1216
|
projectRoot = config.root || projectRoot;
|
|
1206
1217
|
isSSRBuild = !!config.build?.ssr;
|
|
@@ -1331,14 +1342,27 @@ function jayStackCompiler(options = {}) {
|
|
|
1331
1342
|
getLogger().warn(`[action-transform] No actions found in ${actualPath}`);
|
|
1332
1343
|
return null;
|
|
1333
1344
|
}
|
|
1345
|
+
const hasRegularActions = actions.some((a) => !a.isStreaming);
|
|
1346
|
+
const hasStreamActions = actions.some((a) => a.isStreaming);
|
|
1347
|
+
const importNames = [];
|
|
1348
|
+
if (hasRegularActions)
|
|
1349
|
+
importNames.push("createActionCaller");
|
|
1350
|
+
if (hasStreamActions)
|
|
1351
|
+
importNames.push("createStreamCaller");
|
|
1334
1352
|
const lines = [
|
|
1335
|
-
`import {
|
|
1353
|
+
`import { ${importNames.join(", ")} } from '@jay-framework/stack-client-runtime';`,
|
|
1336
1354
|
""
|
|
1337
1355
|
];
|
|
1338
1356
|
for (const action of actions) {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1357
|
+
if (action.isStreaming) {
|
|
1358
|
+
lines.push(
|
|
1359
|
+
`export const ${action.exportName} = createStreamCaller('${action.actionName}');`
|
|
1360
|
+
);
|
|
1361
|
+
} else {
|
|
1362
|
+
lines.push(
|
|
1363
|
+
`export const ${action.exportName} = createActionCaller('${action.actionName}', '${action.method}');`
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1342
1366
|
}
|
|
1343
1367
|
if (code.includes("ActionError")) {
|
|
1344
1368
|
lines.push(
|
|
@@ -1758,6 +1782,22 @@ function createActionRouter(options) {
|
|
|
1758
1782
|
requestMethod,
|
|
1759
1783
|
ACTION_ENDPOINT_BASE + "/" + actionName
|
|
1760
1784
|
);
|
|
1785
|
+
if (registry.isStreaming(actionName)) {
|
|
1786
|
+
res.setHeader("Content-Type", "application/x-ndjson");
|
|
1787
|
+
res.setHeader("Transfer-Encoding", "chunked");
|
|
1788
|
+
try {
|
|
1789
|
+
const generator = registry.executeStream(actionName, input);
|
|
1790
|
+
for await (const chunk of generator) {
|
|
1791
|
+
res.write(JSON.stringify({ chunk }) + "\n");
|
|
1792
|
+
}
|
|
1793
|
+
res.write(JSON.stringify({ done: true }) + "\n");
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
res.write(JSON.stringify({ error: err.message }) + "\n");
|
|
1796
|
+
}
|
|
1797
|
+
res.end();
|
|
1798
|
+
timing?.end();
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1761
1801
|
const result = await registry.execute(actionName, input);
|
|
1762
1802
|
if (requestMethod === "GET" && result.success) {
|
|
1763
1803
|
const cacheHeaders = registry.getCacheHeaders(actionName);
|
|
@@ -1831,12 +1871,208 @@ function actionBodyParser() {
|
|
|
1831
1871
|
});
|
|
1832
1872
|
};
|
|
1833
1873
|
}
|
|
1874
|
+
class FreezeStore {
|
|
1875
|
+
constructor(buildFolder) {
|
|
1876
|
+
__publicField(this, "dir");
|
|
1877
|
+
this.dir = path.join(buildFolder, "freezes");
|
|
1878
|
+
}
|
|
1879
|
+
async save(route, viewState, routePattern) {
|
|
1880
|
+
await fs$1.mkdir(this.dir, { recursive: true });
|
|
1881
|
+
const entry = {
|
|
1882
|
+
id: randomUUID().slice(0, 8),
|
|
1883
|
+
route,
|
|
1884
|
+
...routePattern && { routePattern },
|
|
1885
|
+
viewState,
|
|
1886
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1887
|
+
};
|
|
1888
|
+
await fs$1.writeFile(
|
|
1889
|
+
path.join(this.dir, `${entry.id}.json`),
|
|
1890
|
+
JSON.stringify(entry, null, 2),
|
|
1891
|
+
"utf-8"
|
|
1892
|
+
);
|
|
1893
|
+
return entry;
|
|
1894
|
+
}
|
|
1895
|
+
async get(id) {
|
|
1896
|
+
try {
|
|
1897
|
+
const content = await fs$1.readFile(path.join(this.dir, `${id}.json`), "utf-8");
|
|
1898
|
+
return JSON.parse(content);
|
|
1899
|
+
} catch {
|
|
1900
|
+
return void 0;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
async list(route) {
|
|
1904
|
+
try {
|
|
1905
|
+
const files = await fs$1.readdir(this.dir);
|
|
1906
|
+
const entries = [];
|
|
1907
|
+
for (const file of files) {
|
|
1908
|
+
if (!file.endsWith(".json"))
|
|
1909
|
+
continue;
|
|
1910
|
+
try {
|
|
1911
|
+
const content = await fs$1.readFile(path.join(this.dir, file), "utf-8");
|
|
1912
|
+
const entry = JSON.parse(content);
|
|
1913
|
+
if (!route || entry.routePattern === route || entry.route === route) {
|
|
1914
|
+
entries.push(entry);
|
|
1915
|
+
}
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
return entries.sort(
|
|
1920
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
1921
|
+
);
|
|
1922
|
+
} catch {
|
|
1923
|
+
return [];
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
async rename(id, name) {
|
|
1927
|
+
const entry = await this.get(id);
|
|
1928
|
+
if (!entry)
|
|
1929
|
+
return false;
|
|
1930
|
+
entry.name = name;
|
|
1931
|
+
await fs$1.writeFile(
|
|
1932
|
+
path.join(this.dir, `${id}.json`),
|
|
1933
|
+
JSON.stringify(entry, null, 2),
|
|
1934
|
+
"utf-8"
|
|
1935
|
+
);
|
|
1936
|
+
return true;
|
|
1937
|
+
}
|
|
1938
|
+
async delete(id) {
|
|
1939
|
+
try {
|
|
1940
|
+
await fs$1.unlink(path.join(this.dir, `${id}.json`));
|
|
1941
|
+
return true;
|
|
1942
|
+
} catch {
|
|
1943
|
+
return false;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
const DEV_SERVER_SERVICE = createJayService("DevServerService");
|
|
1948
|
+
class DevServerService {
|
|
1949
|
+
constructor(routes, vite, pagesBase, projectBase, jayRollupConfig, _freezeStore) {
|
|
1950
|
+
this.routes = routes;
|
|
1951
|
+
this.vite = vite;
|
|
1952
|
+
this.pagesBase = pagesBase;
|
|
1953
|
+
this.projectBase = projectBase;
|
|
1954
|
+
this.jayRollupConfig = jayRollupConfig;
|
|
1955
|
+
this._freezeStore = _freezeStore;
|
|
1956
|
+
}
|
|
1957
|
+
get freezeStore() {
|
|
1958
|
+
return this._freezeStore;
|
|
1959
|
+
}
|
|
1960
|
+
/** List all page routes in the project. */
|
|
1961
|
+
listRoutes() {
|
|
1962
|
+
return this.routes.map((r) => ({
|
|
1963
|
+
path: r.path,
|
|
1964
|
+
jayHtmlPath: r.fsRoute.jayHtmlPath,
|
|
1965
|
+
compPath: r.fsRoute.compPath
|
|
1966
|
+
}));
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Run loadParams for a route, yielding param batches as an async generator.
|
|
1970
|
+
* Loads all page parts (page component + keyed headless components) and
|
|
1971
|
+
* calls loadParams on each one that defines it.
|
|
1972
|
+
*/
|
|
1973
|
+
async *loadRouteParams(routePath) {
|
|
1974
|
+
const matched = this.routes.find((r) => r.path === routePath);
|
|
1975
|
+
if (!matched) {
|
|
1976
|
+
getLogger().error(`[loadRouteParams] Route [${routePath}] not found`);
|
|
1977
|
+
throw new Error(`Route "${routePath}" not found`);
|
|
1978
|
+
}
|
|
1979
|
+
const loaded = await loadPageParts(
|
|
1980
|
+
this.vite,
|
|
1981
|
+
matched.fsRoute,
|
|
1982
|
+
this.pagesBase,
|
|
1983
|
+
this.projectBase,
|
|
1984
|
+
this.jayRollupConfig
|
|
1985
|
+
);
|
|
1986
|
+
if (!loaded.val) {
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
yield* runLoadParams(loaded.val.parts);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
let _watchLinkedFiles = () => {
|
|
1993
|
+
};
|
|
1834
1994
|
async function initRoutes(pagesBaseFolder) {
|
|
1835
1995
|
return await scanRoutes(pagesBaseFolder, {
|
|
1836
1996
|
jayHtmlFilename: "page.jay-html",
|
|
1837
1997
|
compFilename: "page.ts"
|
|
1838
1998
|
});
|
|
1839
1999
|
}
|
|
2000
|
+
async function scanPluginRoutes(projectRoot, projectRoutes) {
|
|
2001
|
+
const plugins = await scanPlugins({ projectRoot, includeDevDeps: true });
|
|
2002
|
+
const projectPaths = new Set(projectRoutes.map((r) => r.rawRoute));
|
|
2003
|
+
const pluginRoutes = [];
|
|
2004
|
+
for (const [, plugin] of plugins) {
|
|
2005
|
+
if (!plugin.manifest.routes)
|
|
2006
|
+
continue;
|
|
2007
|
+
for (const route of plugin.manifest.routes) {
|
|
2008
|
+
if (projectPaths.has(route.path)) {
|
|
2009
|
+
getLogger().info(
|
|
2010
|
+
`[Routes] Plugin "${plugin.name}" route ${route.path} skipped — project route takes precedence`
|
|
2011
|
+
);
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
const jayHtmlPath = resolvePluginExport(plugin.pluginPath, route.jayHtml);
|
|
2015
|
+
if (!jayHtmlPath) {
|
|
2016
|
+
getLogger().warn(
|
|
2017
|
+
`[Routes] Plugin "${plugin.name}" route ${route.path}: jayHtml "${route.jayHtml}" not found`
|
|
2018
|
+
);
|
|
2019
|
+
continue;
|
|
2020
|
+
}
|
|
2021
|
+
const compPath = route.component.startsWith(".") ? path__default.resolve(plugin.pluginPath, route.component) : resolvePluginModule(plugin);
|
|
2022
|
+
pluginRoutes.push(createRoute(route.path, jayHtmlPath, compPath));
|
|
2023
|
+
getLogger().info(`[Routes] Plugin "${plugin.name}" provides route ${route.path}`);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
return pluginRoutes;
|
|
2027
|
+
}
|
|
2028
|
+
function resolvePluginExport(pluginPath, exportSubpath) {
|
|
2029
|
+
const normalized = exportSubpath.replace(/^\.\//, "");
|
|
2030
|
+
const packageJsonPath = path__default.join(pluginPath, "package.json");
|
|
2031
|
+
try {
|
|
2032
|
+
const packageJson = JSON.parse(fs__default$1.readFileSync(packageJsonPath, "utf-8"));
|
|
2033
|
+
if (packageJson.exports) {
|
|
2034
|
+
const exportKey = "./" + normalized;
|
|
2035
|
+
const exportValue = packageJson.exports[exportKey];
|
|
2036
|
+
if (exportValue) {
|
|
2037
|
+
const resolved = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
|
|
2038
|
+
if (resolved) {
|
|
2039
|
+
const fullPath = path__default.join(pluginPath, resolved);
|
|
2040
|
+
return fullPath;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
} catch {
|
|
2045
|
+
}
|
|
2046
|
+
for (const dir of ["dist", "lib", ""]) {
|
|
2047
|
+
const candidate = path__default.join(pluginPath, dir, normalized);
|
|
2048
|
+
try {
|
|
2049
|
+
fs__default$1.accessSync(candidate);
|
|
2050
|
+
return candidate;
|
|
2051
|
+
} catch {
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
return void 0;
|
|
2055
|
+
}
|
|
2056
|
+
function resolvePluginModule(plugin) {
|
|
2057
|
+
const modulePath = plugin.manifest.module || "index";
|
|
2058
|
+
for (const ext of [".ts", ".js", "/index.ts", "/index.js"]) {
|
|
2059
|
+
const candidate = path__default.join(plugin.pluginPath, modulePath + ext);
|
|
2060
|
+
try {
|
|
2061
|
+
fs__default$1.accessSync(candidate);
|
|
2062
|
+
return candidate;
|
|
2063
|
+
} catch {
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
for (const ext of [".ts", ".js"]) {
|
|
2067
|
+
const candidate = path__default.join(plugin.pluginPath, "lib", path__default.basename(modulePath) + ext);
|
|
2068
|
+
try {
|
|
2069
|
+
fs__default$1.accessSync(candidate);
|
|
2070
|
+
return candidate;
|
|
2071
|
+
} catch {
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
return path__default.join(plugin.pluginPath, modulePath);
|
|
2075
|
+
}
|
|
1840
2076
|
function defaults(options) {
|
|
1841
2077
|
const publicBaseUrlPath = options.publicBaseUrlPath || process.env.BASE || "/";
|
|
1842
2078
|
const projectRootFolder = options.projectRootFolder || ".";
|
|
@@ -1854,7 +2090,9 @@ function defaults(options) {
|
|
|
1854
2090
|
disableSSR: options.disableSSR,
|
|
1855
2091
|
jayRollupConfig: {
|
|
1856
2092
|
...options.jayRollupConfig || {},
|
|
1857
|
-
tsConfigFilePath
|
|
2093
|
+
tsConfigFilePath,
|
|
2094
|
+
pagesRoot: pagesRootFolder,
|
|
2095
|
+
buildFolder
|
|
1858
2096
|
},
|
|
1859
2097
|
httpServer: options.httpServer
|
|
1860
2098
|
};
|
|
@@ -1896,7 +2134,7 @@ function filterPluginsForPage(allPluginClientInits, allPluginsWithInit, usedPack
|
|
|
1896
2134
|
return pluginInfo && expandedPackages.has(pluginInfo.packageName);
|
|
1897
2135
|
});
|
|
1898
2136
|
}
|
|
1899
|
-
function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit, allPluginsWithInit = [], allPluginClientInits = []) {
|
|
2137
|
+
function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, freezeStore, projectInit, allPluginsWithInit = [], allPluginClientInits = []) {
|
|
1900
2138
|
const routePath = routeToExpressRoute(route);
|
|
1901
2139
|
const handler = async (req, res) => {
|
|
1902
2140
|
const timing = getDevLogger()?.startRequest(req.method, req.path);
|
|
@@ -1912,6 +2150,23 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
|
|
|
1912
2150
|
for (const [key, value] of urlObj.searchParams) {
|
|
1913
2151
|
query[key] = value;
|
|
1914
2152
|
}
|
|
2153
|
+
const freezeId = query["_jay_freeze"];
|
|
2154
|
+
if (freezeId && freezeStore) {
|
|
2155
|
+
timing?.annotate("[FROZEN]");
|
|
2156
|
+
await handleFrozenRequest(
|
|
2157
|
+
vite,
|
|
2158
|
+
route,
|
|
2159
|
+
options,
|
|
2160
|
+
freezeStore,
|
|
2161
|
+
slowRenderCache,
|
|
2162
|
+
freezeId,
|
|
2163
|
+
pageParams,
|
|
2164
|
+
query["format"] === "fragment" ? "fragment" : "page",
|
|
2165
|
+
res,
|
|
2166
|
+
timing
|
|
2167
|
+
);
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
1915
2170
|
if (options.disableSSR) {
|
|
1916
2171
|
await handleClientOnlyRequest(
|
|
1917
2172
|
vite,
|
|
@@ -1994,7 +2249,14 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
|
|
|
1994
2249
|
timing?.end();
|
|
1995
2250
|
return;
|
|
1996
2251
|
}
|
|
1997
|
-
const {
|
|
2252
|
+
const {
|
|
2253
|
+
parts: pageParts,
|
|
2254
|
+
clientTrackByMap,
|
|
2255
|
+
usedPackages,
|
|
2256
|
+
linkedCssFiles,
|
|
2257
|
+
linkedComponentFiles
|
|
2258
|
+
} = pagePartsResult.val;
|
|
2259
|
+
_watchLinkedFiles([...linkedCssFiles || [], ...linkedComponentFiles || []]);
|
|
1998
2260
|
const pluginsForPage = filterPluginsForPage(
|
|
1999
2261
|
allPluginClientInits,
|
|
2000
2262
|
allPluginsWithInit,
|
|
@@ -2023,6 +2285,7 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
|
|
|
2023
2285
|
}
|
|
2024
2286
|
const fastViewState = renderedFast.rendered;
|
|
2025
2287
|
const fastCarryForward = renderedFast.carryForward;
|
|
2288
|
+
const headTags = renderedFast.headTags ?? mergeHeadTags(cachedEntry.carryForward?.__slowHeadTags ?? []);
|
|
2026
2289
|
await sendResponse(
|
|
2027
2290
|
vite,
|
|
2028
2291
|
res,
|
|
@@ -2036,9 +2299,11 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
|
|
|
2036
2299
|
projectInit,
|
|
2037
2300
|
pluginsForPage,
|
|
2038
2301
|
options,
|
|
2302
|
+
routeToExpressRoute(route),
|
|
2039
2303
|
cachedEntry.slowViewState,
|
|
2040
2304
|
timing,
|
|
2041
|
-
cachedEntry.preRenderedContent
|
|
2305
|
+
cachedEntry.preRenderedContent,
|
|
2306
|
+
headTags
|
|
2042
2307
|
);
|
|
2043
2308
|
}
|
|
2044
2309
|
async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRenderCache, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing, query = {}) {
|
|
@@ -2057,6 +2322,8 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
|
|
|
2057
2322
|
timing?.end();
|
|
2058
2323
|
return;
|
|
2059
2324
|
}
|
|
2325
|
+
const { linkedCssFiles: initCss, linkedComponentFiles: initComps } = initialPartsResult.val;
|
|
2326
|
+
_watchLinkedFiles([...initCss || [], ...initComps || []]);
|
|
2060
2327
|
const slowStart = Date.now();
|
|
2061
2328
|
const renderedSlowly = await slowlyPhase.runSlowlyForPage(
|
|
2062
2329
|
pageParams,
|
|
@@ -2149,8 +2416,11 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
|
|
|
2149
2416
|
usedPackages,
|
|
2150
2417
|
headlessInstanceComponents,
|
|
2151
2418
|
discoveredInstances,
|
|
2152
|
-
forEachInstances
|
|
2419
|
+
forEachInstances,
|
|
2420
|
+
linkedCssFiles,
|
|
2421
|
+
linkedComponentFiles
|
|
2153
2422
|
} = pagePartsResult.val;
|
|
2423
|
+
_watchLinkedFiles([...linkedCssFiles || [], ...linkedComponentFiles || []]);
|
|
2154
2424
|
const pluginsForPage = filterPluginsForPage(
|
|
2155
2425
|
allPluginClientInits,
|
|
2156
2426
|
allPluginsWithInit,
|
|
@@ -2209,14 +2479,15 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
|
|
|
2209
2479
|
projectInit,
|
|
2210
2480
|
pluginsForPage,
|
|
2211
2481
|
{
|
|
2212
|
-
enableAutomation: !options.disableAutomation
|
|
2482
|
+
enableAutomation: !options.disableAutomation,
|
|
2483
|
+
routePattern: routeToExpressRoute(route)
|
|
2213
2484
|
}
|
|
2214
2485
|
);
|
|
2215
2486
|
if (options.buildFolder) {
|
|
2216
2487
|
const pageName = !url || url === "/" ? "index" : url.replace(/^\//, "").replace(/\//g, "-");
|
|
2217
2488
|
const clientScriptDir = path__default.join(options.buildFolder, "debug", "client-entry");
|
|
2218
|
-
await
|
|
2219
|
-
await
|
|
2489
|
+
await fs__default.mkdir(clientScriptDir, { recursive: true });
|
|
2490
|
+
await fs__default.writeFile(path__default.join(clientScriptDir, `${pageName}.html`), pageHtml, "utf-8");
|
|
2220
2491
|
}
|
|
2221
2492
|
const viteStart = Date.now();
|
|
2222
2493
|
const compiledPageHtml = await vite.transformIndexHtml(!!url ? url : "/", pageHtml);
|
|
@@ -2224,11 +2495,11 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
|
|
|
2224
2495
|
res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
|
|
2225
2496
|
timing?.end();
|
|
2226
2497
|
}
|
|
2227
|
-
async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState, timing, preLoadedContent) {
|
|
2498
|
+
async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, routePattern, slowViewState, timing, preLoadedContent, headTags) {
|
|
2228
2499
|
let pageHtml;
|
|
2229
2500
|
const routeDir = path__default.dirname(path__default.relative(options.pagesRootFolder, sourceJayHtmlPath));
|
|
2230
2501
|
try {
|
|
2231
|
-
let jayHtmlContent = preLoadedContent ?? await
|
|
2502
|
+
let jayHtmlContent = preLoadedContent ?? await fs__default.readFile(jayHtmlPath, "utf-8");
|
|
2232
2503
|
const jayHtmlFilename = path__default.basename(jayHtmlPath);
|
|
2233
2504
|
const jayHtmlDir = path__default.dirname(jayHtmlPath);
|
|
2234
2505
|
const sourceDir = path__default.dirname(sourceJayHtmlPath);
|
|
@@ -2252,8 +2523,12 @@ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, page
|
|
|
2252
2523
|
pluginsForPage,
|
|
2253
2524
|
{
|
|
2254
2525
|
enableAutomation: !options.disableAutomation,
|
|
2255
|
-
slowViewState
|
|
2256
|
-
|
|
2526
|
+
slowViewState,
|
|
2527
|
+
routePattern
|
|
2528
|
+
},
|
|
2529
|
+
// Pass source directory for headfull FS file resolution when using pre-rendered path
|
|
2530
|
+
jayHtmlDir !== sourceDir ? sourceDir : void 0,
|
|
2531
|
+
headTags
|
|
2257
2532
|
);
|
|
2258
2533
|
} catch (err) {
|
|
2259
2534
|
getLogger().warn(`[SSR] Failed, falling back to client rendering: ${err.message}`);
|
|
@@ -2268,15 +2543,16 @@ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, page
|
|
|
2268
2543
|
pluginsForPage,
|
|
2269
2544
|
{
|
|
2270
2545
|
enableAutomation: !options.disableAutomation,
|
|
2271
|
-
slowViewState
|
|
2546
|
+
slowViewState,
|
|
2547
|
+
routePattern
|
|
2272
2548
|
}
|
|
2273
2549
|
);
|
|
2274
2550
|
}
|
|
2275
2551
|
if (options.buildFolder) {
|
|
2276
2552
|
const pageName = !url || url === "/" ? "index" : url.replace(/^\//, "").replace(/\//g, "-");
|
|
2277
2553
|
const clientScriptDir = path__default.join(options.buildFolder, "debug", "client-entry");
|
|
2278
|
-
await
|
|
2279
|
-
await
|
|
2554
|
+
await fs__default.mkdir(clientScriptDir, { recursive: true });
|
|
2555
|
+
await fs__default.writeFile(path__default.join(clientScriptDir, `${pageName}.html`), pageHtml, "utf-8");
|
|
2280
2556
|
}
|
|
2281
2557
|
const viteStart = Date.now();
|
|
2282
2558
|
const compiledPageHtml = await vite.transformIndexHtml(!!url ? url : "/", pageHtml);
|
|
@@ -2284,12 +2560,62 @@ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, page
|
|
|
2284
2560
|
res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
|
|
2285
2561
|
timing?.end();
|
|
2286
2562
|
}
|
|
2563
|
+
async function handleFrozenRequest(vite, route, options, freezeStore, slowRenderCache, freezeId, pageParams, format, res, timing) {
|
|
2564
|
+
const entry = await freezeStore.get(freezeId);
|
|
2565
|
+
if (!entry) {
|
|
2566
|
+
getLogger().warn(`[Freeze] Freeze "${freezeId}" not found`);
|
|
2567
|
+
res.status(404).send(`Freeze "${freezeId}" not found`);
|
|
2568
|
+
timing?.end();
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
const label = entry.name ? `"${entry.name}" (${freezeId})` : freezeId;
|
|
2572
|
+
getLogger().info(`[Freeze] Serving frozen page ${label} for ${route.rawRoute} [${format}]`);
|
|
2573
|
+
try {
|
|
2574
|
+
const cachedEntry = await slowRenderCache.get(route.jayHtmlPath, pageParams);
|
|
2575
|
+
const jayHtmlPath = cachedEntry?.preRenderedPath ?? route.jayHtmlPath;
|
|
2576
|
+
const jayHtmlContent = cachedEntry?.preRenderedContent ?? await fs__default.readFile(jayHtmlPath, "utf-8");
|
|
2577
|
+
const jayHtmlFilename = path__default.basename(jayHtmlPath);
|
|
2578
|
+
const jayHtmlDir = path__default.dirname(jayHtmlPath);
|
|
2579
|
+
const sourceDir = path__default.dirname(route.jayHtmlPath);
|
|
2580
|
+
const routeDir = path__default.dirname(path__default.relative(options.pagesRootFolder, route.jayHtmlPath));
|
|
2581
|
+
const { injectHeadfullFSTemplates: injectHeadfullFSTemplates2 } = await import("@jay-framework/compiler-jay-html");
|
|
2582
|
+
const { JAY_IMPORT_RESOLVER: JAY_IMPORT_RESOLVER2 } = await import("@jay-framework/compiler-jay-html");
|
|
2583
|
+
const fullJayHtml = injectHeadfullFSTemplates2(
|
|
2584
|
+
jayHtmlContent,
|
|
2585
|
+
sourceDir,
|
|
2586
|
+
JAY_IMPORT_RESOLVER2
|
|
2587
|
+
);
|
|
2588
|
+
const html = await generateFrozenPageHtml(
|
|
2589
|
+
vite,
|
|
2590
|
+
fullJayHtml,
|
|
2591
|
+
jayHtmlFilename,
|
|
2592
|
+
jayHtmlDir,
|
|
2593
|
+
entry.viewState,
|
|
2594
|
+
options.buildFolder,
|
|
2595
|
+
options.projectRootFolder,
|
|
2596
|
+
routeDir,
|
|
2597
|
+
options.jayRollupConfig?.tsConfigFilePath,
|
|
2598
|
+
void 0,
|
|
2599
|
+
format,
|
|
2600
|
+
entry.name
|
|
2601
|
+
);
|
|
2602
|
+
const headers = { "Content-Type": "text/html" };
|
|
2603
|
+
if (format === "fragment") {
|
|
2604
|
+
headers["Access-Control-Allow-Origin"] = "*";
|
|
2605
|
+
}
|
|
2606
|
+
res.status(200).set(headers).send(html);
|
|
2607
|
+
} catch (err) {
|
|
2608
|
+
getLogger().warn(`[Freeze] Failed to render frozen page: ${err.message}`);
|
|
2609
|
+
res.status(500).send(`Failed to render frozen page: ${err.message}`);
|
|
2610
|
+
}
|
|
2611
|
+
timing?.end();
|
|
2612
|
+
}
|
|
2287
2613
|
async function preRenderJayHtml(route, slowViewState, headlessContracts, headlessInstanceComponents, partKeys = []) {
|
|
2288
|
-
const jayHtmlContent = await
|
|
2614
|
+
const jayHtmlContent = await fs__default.readFile(route.jayHtmlPath, "utf-8");
|
|
2289
2615
|
const contractPath = route.jayHtmlPath.replace(".jay-html", ".jay-contract");
|
|
2290
2616
|
let contract;
|
|
2291
2617
|
try {
|
|
2292
|
-
const contractContent = await
|
|
2618
|
+
const contractContent = await fs__default.readFile(contractPath, "utf-8");
|
|
2293
2619
|
const parseResult = parseContract(contractContent, path__default.basename(contractPath));
|
|
2294
2620
|
if (parseResult.val) {
|
|
2295
2621
|
contract = parseResult.val;
|
|
@@ -2338,10 +2664,15 @@ async function preRenderJayHtml(route, slowViewState, headlessContracts, headles
|
|
|
2338
2664
|
let forEachInstances;
|
|
2339
2665
|
if (headlessInstanceComponents.length > 0) {
|
|
2340
2666
|
const discoveryResult = discoverHeadlessInstances(preRenderedJayHtml);
|
|
2341
|
-
|
|
2342
|
-
|
|
2667
|
+
const htmlWithRefs = discoveryResult.preRenderedJayHtml;
|
|
2668
|
+
const headlessContractNameSet = new Set(
|
|
2669
|
+
headlessInstanceComponents.map((c2) => c2.contractName)
|
|
2670
|
+
);
|
|
2671
|
+
preRenderedJayHtml = assignCoordinatesToJayHtml(htmlWithRefs, headlessContractNameSet);
|
|
2672
|
+
const finalDiscovery = discoverHeadlessInstances(preRenderedJayHtml);
|
|
2673
|
+
if (finalDiscovery.forEachInstances.length > 0) {
|
|
2343
2674
|
const validationErrors = validateForEachInstances(
|
|
2344
|
-
|
|
2675
|
+
finalDiscovery.forEachInstances,
|
|
2345
2676
|
headlessInstanceComponents
|
|
2346
2677
|
);
|
|
2347
2678
|
if (validationErrors.length > 0) {
|
|
@@ -2350,11 +2681,11 @@ async function preRenderJayHtml(route, slowViewState, headlessContracts, headles
|
|
|
2350
2681
|
);
|
|
2351
2682
|
return void 0;
|
|
2352
2683
|
}
|
|
2353
|
-
forEachInstances =
|
|
2684
|
+
forEachInstances = finalDiscovery.forEachInstances;
|
|
2354
2685
|
}
|
|
2355
|
-
if (
|
|
2686
|
+
if (finalDiscovery.instances.length > 0) {
|
|
2356
2687
|
const slowResult = await slowRenderInstances(
|
|
2357
|
-
|
|
2688
|
+
finalDiscovery.instances,
|
|
2358
2689
|
headlessInstanceComponents
|
|
2359
2690
|
);
|
|
2360
2691
|
if (slowResult) {
|
|
@@ -2426,8 +2757,15 @@ async function mkDevServer(rawOptions) {
|
|
|
2426
2757
|
const options = defaults(rawOptions);
|
|
2427
2758
|
const { publicBaseUrlPath, pagesRootFolder, projectRootFolder, buildFolder, jayRollupConfig } = options;
|
|
2428
2759
|
if (buildFolder) {
|
|
2429
|
-
|
|
2430
|
-
|
|
2760
|
+
try {
|
|
2761
|
+
const entries = await fs__default.readdir(buildFolder).catch(() => []);
|
|
2762
|
+
for (const entry of entries) {
|
|
2763
|
+
if (entry === "freezes")
|
|
2764
|
+
continue;
|
|
2765
|
+
await fs__default.rm(path__default.join(buildFolder, entry), { recursive: true, force: true });
|
|
2766
|
+
}
|
|
2767
|
+
} catch {
|
|
2768
|
+
}
|
|
2431
2769
|
}
|
|
2432
2770
|
const viteLogLevel = options.logLevel === "silent" ? "silent" : options.logLevel === "verbose" ? "info" : "warn";
|
|
2433
2771
|
const lifecycleManager = new ServiceLifecycleManager(projectRootFolder);
|
|
@@ -2445,15 +2783,25 @@ async function mkDevServer(rawOptions) {
|
|
|
2445
2783
|
await materializeDynamicContracts(projectRootFolder, vite);
|
|
2446
2784
|
setupServiceHotReload(vite, lifecycleManager);
|
|
2447
2785
|
setupActionRouter(vite);
|
|
2448
|
-
const
|
|
2449
|
-
const
|
|
2786
|
+
const projectRoutes = await initRoutes(pagesRootFolder);
|
|
2787
|
+
const filteredProjectRoutes = buildFolder ? projectRoutes.filter((route) => !route.jayHtmlPath.startsWith(buildFolder)) : projectRoutes;
|
|
2788
|
+
const pluginRoutes = await scanPluginRoutes(projectRootFolder, filteredProjectRoutes);
|
|
2789
|
+
const routes = [...filteredProjectRoutes, ...pluginRoutes];
|
|
2450
2790
|
const slowlyPhase = new DevSlowlyChangingPhase();
|
|
2451
2791
|
const slowRenderCacheDir = path__default.join(buildFolder, "pre-rendered");
|
|
2452
2792
|
const slowRenderCache = new SlowRenderCache(slowRenderCacheDir, pagesRootFolder);
|
|
2453
|
-
setupSlowRenderCacheInvalidation(
|
|
2793
|
+
_watchLinkedFiles = setupSlowRenderCacheInvalidation(
|
|
2794
|
+
vite,
|
|
2795
|
+
slowRenderCache,
|
|
2796
|
+
pagesRootFolder
|
|
2797
|
+
);
|
|
2454
2798
|
const projectInit = lifecycleManager.getProjectInit() ?? void 0;
|
|
2455
2799
|
const pluginsWithInit = lifecycleManager.getPluginsWithInit();
|
|
2456
2800
|
const pluginClientInits = preparePluginClientInits(pluginsWithInit);
|
|
2801
|
+
const freezeStore = buildFolder ? new FreezeStore(buildFolder) : void 0;
|
|
2802
|
+
if (freezeStore) {
|
|
2803
|
+
setupFreezeEndpoint(vite, freezeStore);
|
|
2804
|
+
}
|
|
2457
2805
|
const devServerRoutes = routes.map(
|
|
2458
2806
|
(route) => mkRoute(
|
|
2459
2807
|
route,
|
|
@@ -2461,16 +2809,28 @@ async function mkDevServer(rawOptions) {
|
|
|
2461
2809
|
slowlyPhase,
|
|
2462
2810
|
options,
|
|
2463
2811
|
slowRenderCache,
|
|
2812
|
+
freezeStore,
|
|
2464
2813
|
projectInit,
|
|
2465
2814
|
pluginsWithInit,
|
|
2466
2815
|
pluginClientInits
|
|
2467
2816
|
)
|
|
2468
2817
|
);
|
|
2818
|
+
const service = new DevServerService(
|
|
2819
|
+
devServerRoutes,
|
|
2820
|
+
vite,
|
|
2821
|
+
options.pagesRootFolder,
|
|
2822
|
+
options.projectRootFolder,
|
|
2823
|
+
options.jayRollupConfig,
|
|
2824
|
+
freezeStore
|
|
2825
|
+
);
|
|
2826
|
+
registerService(DEV_SERVER_SERVICE, service);
|
|
2469
2827
|
return {
|
|
2470
2828
|
server: vite.middlewares,
|
|
2471
2829
|
viteServer: vite,
|
|
2472
2830
|
routes: devServerRoutes,
|
|
2473
|
-
lifecycleManager
|
|
2831
|
+
lifecycleManager,
|
|
2832
|
+
freezeStore,
|
|
2833
|
+
service
|
|
2474
2834
|
};
|
|
2475
2835
|
}
|
|
2476
2836
|
function setupGracefulShutdown(lifecycleManager) {
|
|
@@ -2509,19 +2869,68 @@ function setupActionRouter(vite) {
|
|
|
2509
2869
|
vite.middlewares.use(ACTION_ENDPOINT_BASE, createActionRouter());
|
|
2510
2870
|
getLogger().info(`[Actions] Action router mounted at ${ACTION_ENDPOINT_BASE}`);
|
|
2511
2871
|
}
|
|
2512
|
-
function
|
|
2872
|
+
function setupFreezeEndpoint(vite, freezeStore) {
|
|
2873
|
+
vite.middlewares.use((req, res, next) => {
|
|
2874
|
+
if (req.method === "POST" && (req.url === "/_jay/freeze" || req.originalUrl === "/_jay/freeze")) {
|
|
2875
|
+
let body = "";
|
|
2876
|
+
req.on("data", (chunk) => body += chunk);
|
|
2877
|
+
req.on("end", async () => {
|
|
2878
|
+
try {
|
|
2879
|
+
const { route, routePattern, viewState } = JSON.parse(body);
|
|
2880
|
+
if (!route || !viewState) {
|
|
2881
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2882
|
+
res.end(JSON.stringify({ error: "Missing route or viewState" }));
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const entry = await freezeStore.save(route, viewState, routePattern);
|
|
2886
|
+
getLogger().info(`[Freeze] Saved freeze "${entry.id}" for ${route}`);
|
|
2887
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2888
|
+
res.end(JSON.stringify(entry));
|
|
2889
|
+
} catch (err) {
|
|
2890
|
+
getLogger().warn(`[Freeze] Failed to save: ${err.message}`);
|
|
2891
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2892
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2895
|
+
} else {
|
|
2896
|
+
next();
|
|
2897
|
+
}
|
|
2898
|
+
});
|
|
2899
|
+
getLogger().info("[Freeze] Freeze endpoint mounted at /_jay/freeze");
|
|
2900
|
+
}
|
|
2901
|
+
function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder, projectRootFolder) {
|
|
2902
|
+
const watchedFiles = /* @__PURE__ */ new Set();
|
|
2903
|
+
const watchLinkedFiles = (files) => {
|
|
2904
|
+
for (const file of files) {
|
|
2905
|
+
if (watchedFiles.has(file))
|
|
2906
|
+
continue;
|
|
2907
|
+
watchedFiles.add(file);
|
|
2908
|
+
vite.watcher.add(file);
|
|
2909
|
+
getLogger().info(`[SlowRender] Watching: ${file}`);
|
|
2910
|
+
}
|
|
2911
|
+
};
|
|
2513
2912
|
vite.watcher.on("change", (changedPath) => {
|
|
2514
|
-
if (
|
|
2913
|
+
if (watchedFiles.has(changedPath)) {
|
|
2914
|
+
clearServerElementCache();
|
|
2915
|
+
cache.clear().then(() => {
|
|
2916
|
+
getLogger().info(
|
|
2917
|
+
`[SlowRender] Cache cleared (linked file changed: ${changedPath})`
|
|
2918
|
+
);
|
|
2919
|
+
vite.ws.send({ type: "full-reload" });
|
|
2920
|
+
});
|
|
2515
2921
|
return;
|
|
2516
2922
|
}
|
|
2517
|
-
if (changedPath.endsWith(".jay-html")) {
|
|
2923
|
+
if (changedPath.endsWith(".jay-html") && changedPath.startsWith(pagesRootFolder)) {
|
|
2518
2924
|
clearServerElementCache();
|
|
2519
|
-
cache.
|
|
2520
|
-
getLogger().info(`[SlowRender] Cache
|
|
2925
|
+
cache.clear().then(() => {
|
|
2926
|
+
getLogger().info(`[SlowRender] Cache cleared (jay-html changed: ${changedPath})`);
|
|
2521
2927
|
vite.ws.send({ type: "full-reload" });
|
|
2522
2928
|
});
|
|
2523
2929
|
return;
|
|
2524
2930
|
}
|
|
2931
|
+
if (!changedPath.startsWith(pagesRootFolder)) {
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2525
2934
|
if (changedPath.endsWith("page.ts")) {
|
|
2526
2935
|
const dir = path__default.dirname(changedPath);
|
|
2527
2936
|
const jayHtmlPath = path__default.join(dir, "page.jay-html");
|
|
@@ -2546,9 +2955,13 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
|
|
|
2546
2955
|
return;
|
|
2547
2956
|
}
|
|
2548
2957
|
});
|
|
2958
|
+
return watchLinkedFiles;
|
|
2549
2959
|
}
|
|
2550
2960
|
export {
|
|
2551
2961
|
ACTION_ENDPOINT_BASE,
|
|
2962
|
+
DEV_SERVER_SERVICE,
|
|
2963
|
+
DevServerService,
|
|
2964
|
+
FreezeStore,
|
|
2552
2965
|
actionBodyParser,
|
|
2553
2966
|
createActionRouter,
|
|
2554
2967
|
createViteForCli,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,22 +23,22 @@
|
|
|
23
23
|
"test:watch": "vitest"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@jay-framework/compiler-jay-stack": "^0.
|
|
27
|
-
"@jay-framework/compiler-shared": "^0.
|
|
28
|
-
"@jay-framework/component": "^0.
|
|
29
|
-
"@jay-framework/fullstack-component": "^0.
|
|
30
|
-
"@jay-framework/logger": "^0.
|
|
31
|
-
"@jay-framework/runtime": "^0.
|
|
32
|
-
"@jay-framework/stack-client-runtime": "^0.
|
|
33
|
-
"@jay-framework/stack-route-scanner": "^0.
|
|
34
|
-
"@jay-framework/stack-server-runtime": "^0.
|
|
35
|
-
"@jay-framework/view-state-merge": "^0.
|
|
26
|
+
"@jay-framework/compiler-jay-stack": "^0.16.0",
|
|
27
|
+
"@jay-framework/compiler-shared": "^0.16.0",
|
|
28
|
+
"@jay-framework/component": "^0.16.0",
|
|
29
|
+
"@jay-framework/fullstack-component": "^0.16.0",
|
|
30
|
+
"@jay-framework/logger": "^0.16.0",
|
|
31
|
+
"@jay-framework/runtime": "^0.16.0",
|
|
32
|
+
"@jay-framework/stack-client-runtime": "^0.16.0",
|
|
33
|
+
"@jay-framework/stack-route-scanner": "^0.16.0",
|
|
34
|
+
"@jay-framework/stack-server-runtime": "^0.16.0",
|
|
35
|
+
"@jay-framework/view-state-merge": "^0.16.0",
|
|
36
36
|
"vite": "^5.0.11"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@jay-framework/dev-environment": "^0.
|
|
40
|
-
"@jay-framework/jay-cli": "^0.
|
|
41
|
-
"@jay-framework/stack-client-runtime": "^0.
|
|
39
|
+
"@jay-framework/dev-environment": "^0.16.0",
|
|
40
|
+
"@jay-framework/jay-cli": "^0.16.0",
|
|
41
|
+
"@jay-framework/stack-client-runtime": "^0.16.0",
|
|
42
42
|
"@playwright/test": "^1.58.2",
|
|
43
43
|
"@types/express": "^5.0.2",
|
|
44
44
|
"@types/node": "^22.15.21",
|