@jay-framework/dev-server 0.10.0 → 0.12.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 -2
- package/dist/index.js +842 -123
- package/package.json +14 -13
package/dist/index.d.ts
CHANGED
|
@@ -2,15 +2,34 @@ import { ViteDevServer, Connect } from 'vite';
|
|
|
2
2
|
import { JayRoute } from '@jay-framework/stack-route-scanner';
|
|
3
3
|
import { RequestHandler } from 'express-serve-static-core';
|
|
4
4
|
import { JayRollupConfig } from '@jay-framework/rollup-plugin';
|
|
5
|
+
import { LogLevel } from '@jay-framework/logger';
|
|
5
6
|
import { ProjectClientInitInfo, PluginWithInit, ActionRegistry } from '@jay-framework/stack-server-runtime';
|
|
6
7
|
import { RequestHandler as RequestHandler$1 } from 'express';
|
|
8
|
+
import { JayRollupConfig as JayRollupConfig$1 } from '@jay-framework/compiler-jay-stack';
|
|
7
9
|
|
|
8
10
|
interface DevServerOptions {
|
|
9
11
|
publicBaseUrlPath?: string;
|
|
10
12
|
projectRootFolder?: string;
|
|
11
13
|
pagesRootFolder?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Folder where build artifacts are stored.
|
|
16
|
+
* Pre-rendered jay-html files are written to `<buildFolder>/slow-render-cache/`.
|
|
17
|
+
* Defaults to `<projectRootFolder>/build`.
|
|
18
|
+
*/
|
|
19
|
+
buildFolder?: string;
|
|
12
20
|
jayRollupConfig: JayRollupConfig;
|
|
13
21
|
dontCacheSlowly: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Disable automation integration.
|
|
24
|
+
* When false (default), pages are wrapped with automation API for dev tooling.
|
|
25
|
+
* The automation API is available at `window.__jay.automation` and via `AUTOMATION_CONTEXT`.
|
|
26
|
+
*/
|
|
27
|
+
disableAutomation?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Log level for dev server output.
|
|
30
|
+
* Controls both Jay logging and Vite logging.
|
|
31
|
+
*/
|
|
32
|
+
logLevel?: LogLevel;
|
|
14
33
|
}
|
|
15
34
|
|
|
16
35
|
/**
|
|
@@ -88,7 +107,7 @@ interface DevServer {
|
|
|
88
107
|
routes: DevServerRoute[];
|
|
89
108
|
lifecycleManager: ServiceLifecycleManager;
|
|
90
109
|
}
|
|
91
|
-
declare function mkDevServer(
|
|
110
|
+
declare function mkDevServer(rawOptions: DevServerOptions): Promise<DevServer>;
|
|
92
111
|
|
|
93
112
|
/**
|
|
94
113
|
* Action Router for Jay Stack dev server.
|
|
@@ -139,4 +158,44 @@ declare function createActionRouter(options?: ActionRouterOptions): RequestHandl
|
|
|
139
158
|
*/
|
|
140
159
|
declare function actionBodyParser(): RequestHandler$1;
|
|
141
160
|
|
|
142
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Vite Factory
|
|
163
|
+
*
|
|
164
|
+
* Single source of truth for creating Vite servers.
|
|
165
|
+
* Used by both the dev-server and CLI commands.
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
interface CreateViteServerOptions {
|
|
169
|
+
/** Project root directory */
|
|
170
|
+
projectRoot: string;
|
|
171
|
+
/** Root directory for pages (defaults to projectRoot) */
|
|
172
|
+
pagesRoot?: string;
|
|
173
|
+
/** Base URL path for public assets */
|
|
174
|
+
base?: string;
|
|
175
|
+
/** Jay Stack compiler config (optional, will use defaults if not provided) */
|
|
176
|
+
jayRollupConfig?: JayRollupConfig$1;
|
|
177
|
+
/** Log level (defaults to 'info' for dev-server, 'warn' for CLI) */
|
|
178
|
+
logLevel?: 'info' | 'warn' | 'error' | 'silent';
|
|
179
|
+
/** Whether to clear screen on rebuild */
|
|
180
|
+
clearScreen?: boolean;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Creates a Vite server configured for Jay Stack.
|
|
184
|
+
*
|
|
185
|
+
* This is the single source of truth for Vite configuration.
|
|
186
|
+
* Both dev-server and CLI use this function.
|
|
187
|
+
*/
|
|
188
|
+
declare function createViteServer(options: CreateViteServerOptions): Promise<ViteDevServer>;
|
|
189
|
+
/**
|
|
190
|
+
* Creates a minimal Vite server for CLI usage.
|
|
191
|
+
*
|
|
192
|
+
* This is a convenience wrapper around createViteServer with CLI-appropriate defaults.
|
|
193
|
+
* Disables dependency optimization and ignores the build/ folder to avoid errors
|
|
194
|
+
* from stale build artifacts (e.g., build/client-scripts/ referencing build/slow-render-cache/).
|
|
195
|
+
*/
|
|
196
|
+
declare function createViteForCli(options: {
|
|
197
|
+
projectRoot: string;
|
|
198
|
+
tsConfigFilePath?: string;
|
|
199
|
+
}): Promise<ViteDevServer>;
|
|
200
|
+
|
|
201
|
+
export { ACTION_ENDPOINT_BASE, type ActionRouterOptions, type CreateViteServerOptions, type DevServer, type DevServerOptions, type DevServerRoute, actionBodyParser, createActionRouter, createViteForCli, createViteServer, mkDevServer };
|
package/dist/index.js
CHANGED
|
@@ -5,15 +5,18 @@ var __publicField = (obj, key, value) => {
|
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
7
|
import { createServer } from "vite";
|
|
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";
|
|
10
8
|
import { jayRuntime } from "@jay-framework/vite-plugin";
|
|
11
9
|
import { createRequire } from "module";
|
|
12
10
|
import "@jay-framework/compiler-shared";
|
|
13
11
|
import * as path from "node:path";
|
|
14
12
|
import path__default from "node:path";
|
|
15
|
-
import "@jay-framework/compiler-jay-html";
|
|
13
|
+
import { discoverHeadlessInstances, parseContract, slowRenderTransform, JAY_IMPORT_RESOLVER, resolveHeadlessInstances } from "@jay-framework/compiler-jay-html";
|
|
14
|
+
import { getLogger, getDevLogger } from "@jay-framework/logger";
|
|
15
|
+
import { createRequire as createRequire$1 } from "node:module";
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
|
+
import { scanRoutes, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
|
|
18
|
+
import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, getServiceRegistry, materializeContracts, loadPageParts, renderFastChangingData, slowRenderInstances, generateClientScript, getClientInitData, resolveServices } from "@jay-framework/stack-server-runtime";
|
|
19
|
+
import fs$1 from "node:fs/promises";
|
|
17
20
|
import { pathToFileURL } from "node:url";
|
|
18
21
|
const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c$1 = new Proxy(e$1, {
|
|
19
22
|
get(t, r) {
|
|
@@ -1019,7 +1022,7 @@ function createImportChainTracker(options = {}) {
|
|
|
1019
1022
|
importChain.clear();
|
|
1020
1023
|
detectedServerModules.clear();
|
|
1021
1024
|
if (verbose) {
|
|
1022
|
-
|
|
1025
|
+
getLogger().info("[import-chain-tracker] Build started, tracking imports...");
|
|
1023
1026
|
}
|
|
1024
1027
|
},
|
|
1025
1028
|
resolveId(source, importer, options2) {
|
|
@@ -1031,7 +1034,7 @@ function createImportChainTracker(options = {}) {
|
|
|
1031
1034
|
}
|
|
1032
1035
|
if (importer) {
|
|
1033
1036
|
if (verbose) {
|
|
1034
|
-
|
|
1037
|
+
getLogger().info(
|
|
1035
1038
|
`[import-chain-tracker] ${shortenPath(importer)} imports ${source}`
|
|
1036
1039
|
);
|
|
1037
1040
|
}
|
|
@@ -1048,14 +1051,14 @@ function createImportChainTracker(options = {}) {
|
|
|
1048
1051
|
if (isServerOnlyModule(id)) {
|
|
1049
1052
|
detectedServerModules.add(id);
|
|
1050
1053
|
const chain = buildImportChain(id);
|
|
1051
|
-
|
|
1054
|
+
getLogger().error(
|
|
1052
1055
|
`
|
|
1053
1056
|
[import-chain-tracker] ⚠️ Server-only module detected in client build!`
|
|
1054
1057
|
);
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1058
|
+
getLogger().error(`Module: ${shortenPath(id)}`);
|
|
1059
|
+
getLogger().error(`Import chain:`);
|
|
1060
|
+
getLogger().error(formatChain(chain));
|
|
1061
|
+
getLogger().error("");
|
|
1059
1062
|
}
|
|
1060
1063
|
const importRegex = /import\s+(?:(?:\{[^}]*\}|[^{}\s,]+)\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
1061
1064
|
let match;
|
|
@@ -1065,16 +1068,18 @@ function createImportChainTracker(options = {}) {
|
|
|
1065
1068
|
if (isServerOnlyModule(importedModule)) {
|
|
1066
1069
|
if (!detectedServerModules.has(importedModule)) {
|
|
1067
1070
|
detectedServerModules.add(importedModule);
|
|
1068
|
-
|
|
1071
|
+
getLogger().error(
|
|
1069
1072
|
`
|
|
1070
1073
|
[import-chain-tracker] ⚠️ Server-only import detected in client build!`
|
|
1071
1074
|
);
|
|
1072
|
-
|
|
1075
|
+
getLogger().error(
|
|
1076
|
+
`Module "${importedModule}" imported by: ${shortenPath(id)}`
|
|
1077
|
+
);
|
|
1073
1078
|
const chain = buildImportChain(id);
|
|
1074
1079
|
chain.push(importedModule);
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1080
|
+
getLogger().error(`Import chain:`);
|
|
1081
|
+
getLogger().error(formatChain(chain));
|
|
1082
|
+
getLogger().error("");
|
|
1078
1083
|
}
|
|
1079
1084
|
}
|
|
1080
1085
|
}
|
|
@@ -1082,27 +1087,151 @@ function createImportChainTracker(options = {}) {
|
|
|
1082
1087
|
},
|
|
1083
1088
|
buildEnd() {
|
|
1084
1089
|
if (detectedServerModules.size > 0) {
|
|
1085
|
-
|
|
1090
|
+
getLogger().warn(
|
|
1086
1091
|
`
|
|
1087
1092
|
[import-chain-tracker] ⚠️ ${detectedServerModules.size} server-only module(s) detected during transform:`
|
|
1088
1093
|
);
|
|
1089
1094
|
for (const mod of detectedServerModules) {
|
|
1090
|
-
|
|
1095
|
+
getLogger().warn(` - ${mod}`);
|
|
1091
1096
|
}
|
|
1092
|
-
|
|
1097
|
+
getLogger().warn(
|
|
1093
1098
|
"\nNote: These may be stripped by the code-split transform if only used in .withServer()."
|
|
1094
1099
|
);
|
|
1095
|
-
|
|
1100
|
+
getLogger().warn(
|
|
1096
1101
|
'If build fails with "not exported" errors, check the import chains above.\n'
|
|
1097
1102
|
);
|
|
1098
1103
|
} else if (verbose) {
|
|
1099
|
-
|
|
1104
|
+
getLogger().info(
|
|
1100
1105
|
"[import-chain-tracker] ✅ No server-only modules detected in client build"
|
|
1101
1106
|
);
|
|
1102
1107
|
}
|
|
1103
1108
|
}
|
|
1104
1109
|
};
|
|
1105
1110
|
}
|
|
1111
|
+
const require2 = createRequire$1(import.meta.url);
|
|
1112
|
+
function createDefaultPluginDetector() {
|
|
1113
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1114
|
+
return {
|
|
1115
|
+
isJayPluginWithClientExport(packageName, projectRoot) {
|
|
1116
|
+
const cacheKey = `${packageName}:${projectRoot}`;
|
|
1117
|
+
if (cache.has(cacheKey)) {
|
|
1118
|
+
return cache.get(cacheKey);
|
|
1119
|
+
}
|
|
1120
|
+
let result = false;
|
|
1121
|
+
try {
|
|
1122
|
+
require2.resolve(`${packageName}/plugin.yaml`, { paths: [projectRoot] });
|
|
1123
|
+
try {
|
|
1124
|
+
require2.resolve(`${packageName}/client`, { paths: [projectRoot] });
|
|
1125
|
+
result = true;
|
|
1126
|
+
} catch {
|
|
1127
|
+
result = false;
|
|
1128
|
+
}
|
|
1129
|
+
} catch {
|
|
1130
|
+
result = false;
|
|
1131
|
+
}
|
|
1132
|
+
cache.set(cacheKey, result);
|
|
1133
|
+
return result;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function extractPackageName(source) {
|
|
1138
|
+
if (source.startsWith(".") || source.startsWith("/")) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
if (source.startsWith("@")) {
|
|
1142
|
+
const parts2 = source.split("/");
|
|
1143
|
+
if (parts2.length >= 2) {
|
|
1144
|
+
return `${parts2[0]}/${parts2[1]}`;
|
|
1145
|
+
}
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
const parts = source.split("/");
|
|
1149
|
+
return parts[0];
|
|
1150
|
+
}
|
|
1151
|
+
function isSubpathImport(source, packageName) {
|
|
1152
|
+
return source.length > packageName.length && source[packageName.length] === "/";
|
|
1153
|
+
}
|
|
1154
|
+
const IMPORT_REGEX = /import\s+(.+?)\s+from\s+(['"])([^'"]+)\2/g;
|
|
1155
|
+
const EXPORT_FROM_REGEX = /export\s+(.+?)\s+from\s+(['"])([^'"]+)\2/g;
|
|
1156
|
+
function transformImports(options) {
|
|
1157
|
+
const { code, projectRoot, filePath, pluginDetector, verbose = false } = options;
|
|
1158
|
+
let hasChanges = false;
|
|
1159
|
+
let result = code;
|
|
1160
|
+
result = result.replace(IMPORT_REGEX, (match, clause, quote, source) => {
|
|
1161
|
+
const packageName = extractPackageName(source);
|
|
1162
|
+
if (!packageName)
|
|
1163
|
+
return match;
|
|
1164
|
+
if (isSubpathImport(source, packageName))
|
|
1165
|
+
return match;
|
|
1166
|
+
if (!pluginDetector.isJayPluginWithClientExport(packageName, projectRoot))
|
|
1167
|
+
return match;
|
|
1168
|
+
hasChanges = true;
|
|
1169
|
+
const newSource = `${packageName}/client`;
|
|
1170
|
+
if (verbose) {
|
|
1171
|
+
getLogger().info(
|
|
1172
|
+
`[plugin-client-import] Rewriting import ${source} -> ${newSource} (in ${path.basename(filePath)})`
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
return `import ${clause} from ${quote}${newSource}${quote}`;
|
|
1176
|
+
});
|
|
1177
|
+
result = result.replace(EXPORT_FROM_REGEX, (match, clause, quote, source) => {
|
|
1178
|
+
const packageName = extractPackageName(source);
|
|
1179
|
+
if (!packageName)
|
|
1180
|
+
return match;
|
|
1181
|
+
if (isSubpathImport(source, packageName))
|
|
1182
|
+
return match;
|
|
1183
|
+
if (!pluginDetector.isJayPluginWithClientExport(packageName, projectRoot))
|
|
1184
|
+
return match;
|
|
1185
|
+
hasChanges = true;
|
|
1186
|
+
const newSource = `${packageName}/client`;
|
|
1187
|
+
if (verbose) {
|
|
1188
|
+
getLogger().info(
|
|
1189
|
+
`[plugin-client-import] Rewriting export ${source} -> ${newSource} (in ${path.basename(filePath)})`
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
return `export ${clause} from ${quote}${newSource}${quote}`;
|
|
1193
|
+
});
|
|
1194
|
+
return { code: result, hasChanges };
|
|
1195
|
+
}
|
|
1196
|
+
function createPluginClientImportResolver(options = {}) {
|
|
1197
|
+
const { verbose = false } = options;
|
|
1198
|
+
let projectRoot = options.projectRoot || process.cwd();
|
|
1199
|
+
let isSSRBuild = false;
|
|
1200
|
+
const pluginDetector = options.pluginDetector || createDefaultPluginDetector();
|
|
1201
|
+
return {
|
|
1202
|
+
name: "jay-stack:plugin-client-import",
|
|
1203
|
+
enforce: "pre",
|
|
1204
|
+
configResolved(config) {
|
|
1205
|
+
projectRoot = config.root || projectRoot;
|
|
1206
|
+
isSSRBuild = !!config.build?.ssr;
|
|
1207
|
+
},
|
|
1208
|
+
transform(code, id, transformOptions) {
|
|
1209
|
+
if (transformOptions?.ssr || isSSRBuild) {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
if (!id.endsWith(".ts") && !id.endsWith(".js") && !id.includes(".ts?") && !id.includes(".js?")) {
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
if (id.includes("node_modules") && !id.includes("@jay-framework")) {
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
if (!code.includes("@jay-framework/") && !code.includes("from '@") && !code.includes('from "@')) {
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
const result = transformImports({
|
|
1222
|
+
code,
|
|
1223
|
+
projectRoot,
|
|
1224
|
+
filePath: id,
|
|
1225
|
+
pluginDetector,
|
|
1226
|
+
verbose
|
|
1227
|
+
});
|
|
1228
|
+
if (!result.hasChanges) {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
return { code: result.code };
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1106
1235
|
function jayStackCompiler(options = {}) {
|
|
1107
1236
|
const { trackImports, ...jayOptions } = options;
|
|
1108
1237
|
const moduleCache = /* @__PURE__ */ new Map();
|
|
@@ -1112,6 +1241,7 @@ function jayStackCompiler(options = {}) {
|
|
|
1112
1241
|
if (shouldTrackImports) {
|
|
1113
1242
|
plugins.push(createImportChainTracker(trackerOptions));
|
|
1114
1243
|
}
|
|
1244
|
+
plugins.push(createPluginClientImportResolver({ verbose: !!shouldTrackImports }));
|
|
1115
1245
|
plugins.push(
|
|
1116
1246
|
// First: Jay Stack code splitting transformation
|
|
1117
1247
|
{
|
|
@@ -1131,7 +1261,7 @@ function jayStackCompiler(options = {}) {
|
|
|
1131
1261
|
try {
|
|
1132
1262
|
return transformJayStackBuilder(code, id, environment);
|
|
1133
1263
|
} catch (error) {
|
|
1134
|
-
|
|
1264
|
+
getLogger().error(`[jay-stack:code-split] Error transforming ${id}: ${error}`);
|
|
1135
1265
|
return null;
|
|
1136
1266
|
}
|
|
1137
1267
|
}
|
|
@@ -1172,6 +1302,13 @@ function jayStackCompiler(options = {}) {
|
|
|
1172
1302
|
} else {
|
|
1173
1303
|
return null;
|
|
1174
1304
|
}
|
|
1305
|
+
} else if (resolvedPath.endsWith(".js") && !fs.existsSync(resolvedPath)) {
|
|
1306
|
+
const tsPath = resolvedPath.slice(0, -3) + ".ts";
|
|
1307
|
+
if (fs.existsSync(tsPath)) {
|
|
1308
|
+
resolvedPath = tsPath;
|
|
1309
|
+
} else {
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1175
1312
|
}
|
|
1176
1313
|
return `\0jay-action:${resolvedPath}`;
|
|
1177
1314
|
},
|
|
@@ -1184,12 +1321,14 @@ function jayStackCompiler(options = {}) {
|
|
|
1184
1321
|
try {
|
|
1185
1322
|
code = await fs.promises.readFile(actualPath, "utf-8");
|
|
1186
1323
|
} catch (err) {
|
|
1187
|
-
|
|
1324
|
+
getLogger().error(
|
|
1325
|
+
`[action-transform] Could not read ${actualPath}: ${err}`
|
|
1326
|
+
);
|
|
1188
1327
|
return null;
|
|
1189
1328
|
}
|
|
1190
1329
|
const actions = extractActionsFromSource(code, actualPath);
|
|
1191
1330
|
if (actions.length === 0) {
|
|
1192
|
-
|
|
1331
|
+
getLogger().warn(`[action-transform] No actions found in ${actualPath}`);
|
|
1193
1332
|
return null;
|
|
1194
1333
|
}
|
|
1195
1334
|
const lines = [
|
|
@@ -1216,6 +1355,69 @@ function jayStackCompiler(options = {}) {
|
|
|
1216
1355
|
);
|
|
1217
1356
|
return plugins;
|
|
1218
1357
|
}
|
|
1358
|
+
async function createViteServer(options) {
|
|
1359
|
+
const {
|
|
1360
|
+
projectRoot,
|
|
1361
|
+
pagesRoot = projectRoot,
|
|
1362
|
+
base,
|
|
1363
|
+
jayRollupConfig = { tsConfigFilePath: path__default.join(projectRoot, "tsconfig.json") },
|
|
1364
|
+
logLevel = "info",
|
|
1365
|
+
clearScreen = true
|
|
1366
|
+
} = options;
|
|
1367
|
+
const vite = await createServer({
|
|
1368
|
+
// Don't start HTTP server - we use middleware mode
|
|
1369
|
+
server: { middlewareMode: true },
|
|
1370
|
+
// Use Jay Stack compiler for .jay-html and other custom transforms
|
|
1371
|
+
plugins: [...jayStackCompiler(jayRollupConfig)],
|
|
1372
|
+
// Custom app type (no default middleware)
|
|
1373
|
+
appType: "custom",
|
|
1374
|
+
// Base URL path
|
|
1375
|
+
base,
|
|
1376
|
+
// Root directory for module resolution
|
|
1377
|
+
root: pagesRoot,
|
|
1378
|
+
// SSR configuration
|
|
1379
|
+
ssr: {
|
|
1380
|
+
// Mark jay-framework packages as external so Vite uses Node's require
|
|
1381
|
+
// This ensures all packages share the same module instances (Symbol identity)
|
|
1382
|
+
external: ["@jay-framework/stack-server-runtime", "@jay-framework/fullstack-component"]
|
|
1383
|
+
},
|
|
1384
|
+
// Disable automatic entry point discovery for pre-bundling —
|
|
1385
|
+
// we run in middleware mode with no HTML files, so Vite can't auto-detect entries
|
|
1386
|
+
optimizeDeps: {
|
|
1387
|
+
entries: []
|
|
1388
|
+
},
|
|
1389
|
+
// Logging
|
|
1390
|
+
logLevel,
|
|
1391
|
+
clearScreen
|
|
1392
|
+
});
|
|
1393
|
+
return vite;
|
|
1394
|
+
}
|
|
1395
|
+
async function createViteForCli(options) {
|
|
1396
|
+
const { projectRoot, tsConfigFilePath = path__default.join(projectRoot, "tsconfig.json") } = options;
|
|
1397
|
+
const vite = await createServer({
|
|
1398
|
+
server: {
|
|
1399
|
+
middlewareMode: true,
|
|
1400
|
+
watch: {
|
|
1401
|
+
ignored: ["**/build/**"]
|
|
1402
|
+
}
|
|
1403
|
+
},
|
|
1404
|
+
plugins: [
|
|
1405
|
+
...jayStackCompiler({ tsConfigFilePath })
|
|
1406
|
+
],
|
|
1407
|
+
appType: "custom",
|
|
1408
|
+
root: projectRoot,
|
|
1409
|
+
ssr: {
|
|
1410
|
+
external: ["@jay-framework/stack-server-runtime", "@jay-framework/fullstack-component"]
|
|
1411
|
+
},
|
|
1412
|
+
// Disable dependency optimization — CLI only uses SSR, no browser bundles
|
|
1413
|
+
optimizeDeps: {
|
|
1414
|
+
entries: []
|
|
1415
|
+
},
|
|
1416
|
+
logLevel: "warn",
|
|
1417
|
+
clearScreen: false
|
|
1418
|
+
});
|
|
1419
|
+
return vite;
|
|
1420
|
+
}
|
|
1219
1421
|
class ServiceLifecycleManager {
|
|
1220
1422
|
constructor(projectRoot, sourceBase = "src") {
|
|
1221
1423
|
/** Path to project's lib/init.ts (makeJayInit pattern) */
|
|
@@ -1254,8 +1456,9 @@ class ServiceLifecycleManager {
|
|
|
1254
1456
|
* 4. Auto-discovering and registering actions
|
|
1255
1457
|
*/
|
|
1256
1458
|
async initialize() {
|
|
1459
|
+
const log = getLogger();
|
|
1257
1460
|
if (this.isInitialized) {
|
|
1258
|
-
|
|
1461
|
+
log.warn("[Services] Already initialized, skipping...");
|
|
1259
1462
|
return;
|
|
1260
1463
|
}
|
|
1261
1464
|
this.projectInitFilePath = this.findProjectInitFile();
|
|
@@ -1265,18 +1468,18 @@ class ServiceLifecycleManager {
|
|
|
1265
1468
|
});
|
|
1266
1469
|
this.pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
|
|
1267
1470
|
if (this.pluginsWithInit.length > 0) {
|
|
1268
|
-
|
|
1471
|
+
log.info(
|
|
1269
1472
|
`[Services] Found ${this.pluginsWithInit.length} plugin(s) with init: ${this.pluginsWithInit.map((p) => p.name).join(", ")}`
|
|
1270
1473
|
);
|
|
1271
1474
|
}
|
|
1272
1475
|
await executePluginServerInits(this.pluginsWithInit, this.viteServer ?? void 0, true);
|
|
1273
1476
|
if (this.projectInitFilePath) {
|
|
1274
|
-
|
|
1477
|
+
log.info("[DevServer] Loading project init: src/init.ts");
|
|
1275
1478
|
try {
|
|
1276
1479
|
if (this.viteServer) {
|
|
1277
1480
|
const module = await this.viteServer.ssrLoadModule(this.projectInitFilePath);
|
|
1278
1481
|
if (module.init?._serverInit) {
|
|
1279
|
-
|
|
1482
|
+
log.info("[DevServer] Running server init: project");
|
|
1280
1483
|
const { setClientInitData } = await import("@jay-framework/stack-server-runtime");
|
|
1281
1484
|
const clientData = await module.init._serverInit();
|
|
1282
1485
|
if (clientData !== void 0 && clientData !== null) {
|
|
@@ -1288,14 +1491,14 @@ class ServiceLifecycleManager {
|
|
|
1288
1491
|
await import(fileUrl);
|
|
1289
1492
|
}
|
|
1290
1493
|
} catch (error) {
|
|
1291
|
-
|
|
1494
|
+
log.error(`[Services] Failed to load project init: ${error}`);
|
|
1292
1495
|
throw error;
|
|
1293
1496
|
}
|
|
1294
1497
|
} else {
|
|
1295
|
-
|
|
1498
|
+
log.info("[Services] No init.ts found, skipping project initialization");
|
|
1296
1499
|
}
|
|
1297
1500
|
await runInitCallbacks();
|
|
1298
|
-
|
|
1501
|
+
log.info("[Services] Initialization complete");
|
|
1299
1502
|
await this.discoverActions();
|
|
1300
1503
|
this.isInitialized = true;
|
|
1301
1504
|
}
|
|
@@ -1303,6 +1506,7 @@ class ServiceLifecycleManager {
|
|
|
1303
1506
|
* Auto-discovers and registers actions from project and plugins.
|
|
1304
1507
|
*/
|
|
1305
1508
|
async discoverActions() {
|
|
1509
|
+
const log = getLogger();
|
|
1306
1510
|
let totalActions = 0;
|
|
1307
1511
|
try {
|
|
1308
1512
|
const result = await discoverAndRegisterActions({
|
|
@@ -1314,7 +1518,7 @@ class ServiceLifecycleManager {
|
|
|
1314
1518
|
});
|
|
1315
1519
|
totalActions += result.actionCount;
|
|
1316
1520
|
} catch (error) {
|
|
1317
|
-
|
|
1521
|
+
log.error(`[Actions] Failed to auto-discover project actions: ${error}`);
|
|
1318
1522
|
}
|
|
1319
1523
|
try {
|
|
1320
1524
|
const pluginActions = await discoverAllPluginActions({
|
|
@@ -1325,20 +1529,21 @@ class ServiceLifecycleManager {
|
|
|
1325
1529
|
});
|
|
1326
1530
|
totalActions += pluginActions.length;
|
|
1327
1531
|
} catch (error) {
|
|
1328
|
-
|
|
1532
|
+
log.error(`[Actions] Failed to auto-discover plugin actions: ${error}`);
|
|
1329
1533
|
}
|
|
1330
1534
|
if (totalActions > 0) {
|
|
1331
|
-
|
|
1535
|
+
log.info(`[Actions] Auto-registered ${totalActions} action(s) total`);
|
|
1332
1536
|
}
|
|
1333
1537
|
}
|
|
1334
1538
|
/**
|
|
1335
1539
|
* Shuts down services gracefully with timeout
|
|
1336
1540
|
*/
|
|
1337
1541
|
async shutdown(timeoutMs = 5e3) {
|
|
1542
|
+
const log = getLogger();
|
|
1338
1543
|
if (!this.isInitialized) {
|
|
1339
1544
|
return;
|
|
1340
1545
|
}
|
|
1341
|
-
|
|
1546
|
+
log.info("[Services] Shutting down...");
|
|
1342
1547
|
try {
|
|
1343
1548
|
await Promise.race([
|
|
1344
1549
|
runShutdownCallbacks(),
|
|
@@ -1346,12 +1551,12 @@ class ServiceLifecycleManager {
|
|
|
1346
1551
|
(_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), timeoutMs)
|
|
1347
1552
|
)
|
|
1348
1553
|
]);
|
|
1349
|
-
|
|
1554
|
+
log.info("[Services] Shutdown complete");
|
|
1350
1555
|
} catch (error) {
|
|
1351
1556
|
if (error.message === "Shutdown timeout") {
|
|
1352
|
-
|
|
1557
|
+
log.warn(`[Services] Shutdown timed out after ${timeoutMs}ms`);
|
|
1353
1558
|
} else {
|
|
1354
|
-
|
|
1559
|
+
log.error(`[Services] Shutdown error: ${error}`);
|
|
1355
1560
|
}
|
|
1356
1561
|
} finally {
|
|
1357
1562
|
this.isInitialized = false;
|
|
@@ -1361,7 +1566,8 @@ class ServiceLifecycleManager {
|
|
|
1361
1566
|
* Hot reload: shutdown, clear caches, re-import, and re-initialize
|
|
1362
1567
|
*/
|
|
1363
1568
|
async reload() {
|
|
1364
|
-
|
|
1569
|
+
const log = getLogger();
|
|
1570
|
+
log.info("[Services] Reloading services...");
|
|
1365
1571
|
await this.shutdown();
|
|
1366
1572
|
clearLifecycleCallbacks();
|
|
1367
1573
|
clearServiceRegistry();
|
|
@@ -1377,7 +1583,7 @@ class ServiceLifecycleManager {
|
|
|
1377
1583
|
}
|
|
1378
1584
|
this.isInitialized = false;
|
|
1379
1585
|
await this.initialize();
|
|
1380
|
-
|
|
1586
|
+
log.info("[Services] Reload complete");
|
|
1381
1587
|
}
|
|
1382
1588
|
/**
|
|
1383
1589
|
* Returns the path to the init file if found.
|
|
@@ -1630,11 +1836,13 @@ function defaults(options) {
|
|
|
1630
1836
|
projectRootFolder,
|
|
1631
1837
|
options.pagesRootFolder || "./src/pages"
|
|
1632
1838
|
);
|
|
1839
|
+
const buildFolder = options.buildFolder || path__default.resolve(projectRootFolder, "./build");
|
|
1633
1840
|
const tsConfigFilePath = options.jayRollupConfig.tsConfigFilePath || path__default.resolve(projectRootFolder, "./tsconfig.json");
|
|
1634
1841
|
return {
|
|
1635
1842
|
publicBaseUrlPath,
|
|
1636
1843
|
pagesRootFolder,
|
|
1637
1844
|
projectRootFolder,
|
|
1845
|
+
buildFolder,
|
|
1638
1846
|
dontCacheSlowly: options.dontCacheSlowly,
|
|
1639
1847
|
jayRollupConfig: {
|
|
1640
1848
|
...options.jayRollupConfig || {},
|
|
@@ -1650,124 +1858,601 @@ function handleOtherResponseCodes(res, renderedResult) {
|
|
|
1650
1858
|
else
|
|
1651
1859
|
res.status(renderedResult.status).end("redirect to " + renderedResult.location);
|
|
1652
1860
|
}
|
|
1653
|
-
function
|
|
1654
|
-
const
|
|
1861
|
+
function filterPluginsForPage(allPluginClientInits, allPluginsWithInit, usedPackages) {
|
|
1862
|
+
const pluginsByPackage = /* @__PURE__ */ new Map();
|
|
1863
|
+
for (const plugin of allPluginsWithInit) {
|
|
1864
|
+
pluginsByPackage.set(plugin.packageName, plugin);
|
|
1865
|
+
}
|
|
1866
|
+
const expandedPackages = new Set(usedPackages);
|
|
1867
|
+
const toProcess = [...usedPackages];
|
|
1868
|
+
while (toProcess.length > 0) {
|
|
1869
|
+
const packageName = toProcess.pop();
|
|
1870
|
+
const plugin = pluginsByPackage.get(packageName);
|
|
1871
|
+
if (!plugin)
|
|
1872
|
+
continue;
|
|
1873
|
+
for (const dep of plugin.dependencies) {
|
|
1874
|
+
if (pluginsByPackage.has(dep) && !expandedPackages.has(dep)) {
|
|
1875
|
+
expandedPackages.add(dep);
|
|
1876
|
+
toProcess.push(dep);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return allPluginClientInits.filter((plugin) => {
|
|
1881
|
+
const pluginInfo = allPluginsWithInit.find((p) => p.name === plugin.name);
|
|
1882
|
+
return pluginInfo && expandedPackages.has(pluginInfo.packageName);
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit, allPluginsWithInit = [], allPluginClientInits = []) {
|
|
1886
|
+
const routePath = routeToExpressRoute(route);
|
|
1655
1887
|
const handler = async (req, res) => {
|
|
1888
|
+
const timing = getDevLogger()?.startRequest(req.method, req.path);
|
|
1656
1889
|
try {
|
|
1657
1890
|
const url = req.originalUrl.replace(options.publicBaseUrlPath, "");
|
|
1658
|
-
const pageParams = req.params;
|
|
1891
|
+
const pageParams = { ...route.inferredParams, ...req.params };
|
|
1659
1892
|
const pageProps = {
|
|
1660
1893
|
language: "en",
|
|
1661
1894
|
url
|
|
1662
1895
|
};
|
|
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(
|
|
1896
|
+
const useSlowRenderCache = !options.dontCacheSlowly;
|
|
1897
|
+
let cachedEntry = useSlowRenderCache ? slowRenderCache.get(route.jayHtmlPath, pageParams) : void 0;
|
|
1898
|
+
if (cachedEntry) {
|
|
1899
|
+
try {
|
|
1900
|
+
await fs$1.access(cachedEntry.preRenderedPath);
|
|
1901
|
+
} catch {
|
|
1902
|
+
getLogger().info(
|
|
1903
|
+
`[SlowRender] Cached file missing, rebuilding: ${cachedEntry.preRenderedPath}`
|
|
1904
|
+
);
|
|
1905
|
+
await slowRenderCache.invalidate(route.jayHtmlPath);
|
|
1906
|
+
cachedEntry = void 0;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (cachedEntry) {
|
|
1910
|
+
await handleCachedRequest(
|
|
1911
|
+
vite,
|
|
1912
|
+
route,
|
|
1913
|
+
options,
|
|
1914
|
+
cachedEntry,
|
|
1683
1915
|
pageParams,
|
|
1684
1916
|
pageProps,
|
|
1685
|
-
|
|
1917
|
+
allPluginClientInits,
|
|
1918
|
+
allPluginsWithInit,
|
|
1919
|
+
projectInit,
|
|
1920
|
+
res,
|
|
1921
|
+
url,
|
|
1922
|
+
timing
|
|
1923
|
+
);
|
|
1924
|
+
} else if (useSlowRenderCache) {
|
|
1925
|
+
await handlePreRenderRequest(
|
|
1926
|
+
vite,
|
|
1927
|
+
route,
|
|
1928
|
+
options,
|
|
1929
|
+
slowlyPhase,
|
|
1930
|
+
slowRenderCache,
|
|
1931
|
+
pageParams,
|
|
1932
|
+
pageProps,
|
|
1933
|
+
allPluginClientInits,
|
|
1934
|
+
allPluginsWithInit,
|
|
1935
|
+
projectInit,
|
|
1936
|
+
res,
|
|
1937
|
+
url,
|
|
1938
|
+
timing
|
|
1686
1939
|
);
|
|
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
1940
|
} else {
|
|
1727
|
-
|
|
1728
|
-
|
|
1941
|
+
await handleDirectRequest(
|
|
1942
|
+
vite,
|
|
1943
|
+
route,
|
|
1944
|
+
options,
|
|
1945
|
+
slowlyPhase,
|
|
1946
|
+
pageParams,
|
|
1947
|
+
pageProps,
|
|
1948
|
+
allPluginClientInits,
|
|
1949
|
+
allPluginsWithInit,
|
|
1950
|
+
projectInit,
|
|
1951
|
+
res,
|
|
1952
|
+
url,
|
|
1953
|
+
timing
|
|
1954
|
+
);
|
|
1729
1955
|
}
|
|
1730
1956
|
} catch (e2) {
|
|
1731
1957
|
vite?.ssrFixStacktrace(e2);
|
|
1732
|
-
|
|
1958
|
+
getLogger().error(e2.stack);
|
|
1733
1959
|
res.status(500).end(e2.stack);
|
|
1960
|
+
timing?.end();
|
|
1734
1961
|
}
|
|
1735
1962
|
};
|
|
1736
|
-
return { path:
|
|
1963
|
+
return { path: routePath, handler, fsRoute: route };
|
|
1737
1964
|
}
|
|
1738
|
-
async function
|
|
1965
|
+
async function handleCachedRequest(vite, route, options, cachedEntry, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
|
|
1966
|
+
const loadStart = Date.now();
|
|
1967
|
+
const pagePartsResult = await loadPageParts(
|
|
1968
|
+
vite,
|
|
1969
|
+
route,
|
|
1970
|
+
options.pagesRootFolder,
|
|
1971
|
+
options.projectRootFolder,
|
|
1972
|
+
options.jayRollupConfig,
|
|
1973
|
+
{ preRenderedPath: cachedEntry.preRenderedPath }
|
|
1974
|
+
);
|
|
1975
|
+
timing?.recordViteSsr(Date.now() - loadStart);
|
|
1976
|
+
if (!pagePartsResult.val) {
|
|
1977
|
+
getLogger().info(pagePartsResult.validations.join("\n"));
|
|
1978
|
+
res.status(500).end(pagePartsResult.validations.join("\n"));
|
|
1979
|
+
timing?.end();
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
|
|
1983
|
+
const pluginsForPage = filterPluginsForPage(
|
|
1984
|
+
allPluginClientInits,
|
|
1985
|
+
allPluginsWithInit,
|
|
1986
|
+
usedPackages
|
|
1987
|
+
);
|
|
1988
|
+
const fastStart = Date.now();
|
|
1989
|
+
const renderedFast = await renderFastChangingData(
|
|
1990
|
+
pageParams,
|
|
1991
|
+
pageProps,
|
|
1992
|
+
cachedEntry.carryForward,
|
|
1993
|
+
pageParts
|
|
1994
|
+
);
|
|
1995
|
+
timing?.recordFastRender(Date.now() - fastStart);
|
|
1996
|
+
if (renderedFast.kind !== "PhaseOutput") {
|
|
1997
|
+
handleOtherResponseCodes(res, renderedFast);
|
|
1998
|
+
timing?.end();
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
let fastViewState = renderedFast.rendered;
|
|
2002
|
+
let fastCarryForward = renderedFast.carryForward;
|
|
2003
|
+
const instancePhaseData = cachedEntry.carryForward?.__instances;
|
|
2004
|
+
if (instancePhaseData && pagePartsResult.val.headlessInstanceComponents.length > 0) {
|
|
2005
|
+
const instanceFastResult = await renderFastChangingDataForInstances(
|
|
2006
|
+
instancePhaseData,
|
|
2007
|
+
pagePartsResult.val.headlessInstanceComponents
|
|
2008
|
+
);
|
|
2009
|
+
if (instanceFastResult) {
|
|
2010
|
+
fastViewState = {
|
|
2011
|
+
...fastViewState,
|
|
2012
|
+
__headlessInstances: instanceFastResult.viewStates
|
|
2013
|
+
};
|
|
2014
|
+
fastCarryForward = {
|
|
2015
|
+
...fastCarryForward,
|
|
2016
|
+
__headlessInstances: instanceFastResult.carryForwards
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
await sendResponse(
|
|
2021
|
+
vite,
|
|
2022
|
+
res,
|
|
2023
|
+
url,
|
|
2024
|
+
cachedEntry.preRenderedPath,
|
|
2025
|
+
pageParts,
|
|
2026
|
+
fastViewState,
|
|
2027
|
+
fastCarryForward,
|
|
2028
|
+
clientTrackByMap,
|
|
2029
|
+
projectInit,
|
|
2030
|
+
pluginsForPage,
|
|
2031
|
+
options,
|
|
2032
|
+
cachedEntry.slowViewState,
|
|
2033
|
+
timing
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRenderCache, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
|
|
2037
|
+
const loadStart = Date.now();
|
|
2038
|
+
const initialPartsResult = await loadPageParts(
|
|
2039
|
+
vite,
|
|
2040
|
+
route,
|
|
2041
|
+
options.pagesRootFolder,
|
|
2042
|
+
options.projectRootFolder,
|
|
2043
|
+
options.jayRollupConfig
|
|
2044
|
+
);
|
|
2045
|
+
timing?.recordViteSsr(Date.now() - loadStart);
|
|
2046
|
+
if (!initialPartsResult.val) {
|
|
2047
|
+
getLogger().info(initialPartsResult.validations.join("\n"));
|
|
2048
|
+
res.status(500).end(initialPartsResult.validations.join("\n"));
|
|
2049
|
+
timing?.end();
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
const slowStart = Date.now();
|
|
2053
|
+
const renderedSlowly = await slowlyPhase.runSlowlyForPage(
|
|
2054
|
+
pageParams,
|
|
2055
|
+
pageProps,
|
|
2056
|
+
initialPartsResult.val.parts
|
|
2057
|
+
);
|
|
2058
|
+
if (renderedSlowly.kind !== "PhaseOutput") {
|
|
2059
|
+
timing?.recordSlowRender(Date.now() - slowStart);
|
|
2060
|
+
if (renderedSlowly.kind === "ClientError") {
|
|
2061
|
+
handleOtherResponseCodes(res, renderedSlowly);
|
|
2062
|
+
}
|
|
2063
|
+
timing?.end();
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
const preRenderResult = await preRenderJayHtml(
|
|
2067
|
+
route,
|
|
2068
|
+
renderedSlowly.rendered,
|
|
2069
|
+
initialPartsResult.val.headlessContracts,
|
|
2070
|
+
initialPartsResult.val.headlessInstanceComponents
|
|
2071
|
+
);
|
|
2072
|
+
timing?.recordSlowRender(Date.now() - slowStart);
|
|
2073
|
+
if (!preRenderResult) {
|
|
2074
|
+
res.status(500).end("Failed to pre-render jay-html");
|
|
2075
|
+
timing?.end();
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const carryForward = preRenderResult.instancePhaseData ? { ...renderedSlowly.carryForward, __instances: preRenderResult.instancePhaseData } : renderedSlowly.carryForward;
|
|
2079
|
+
const preRenderedPath = await slowRenderCache.set(
|
|
2080
|
+
route.jayHtmlPath,
|
|
2081
|
+
pageParams,
|
|
2082
|
+
preRenderResult.preRenderedJayHtml,
|
|
2083
|
+
renderedSlowly.rendered,
|
|
2084
|
+
carryForward
|
|
2085
|
+
);
|
|
2086
|
+
getLogger().info(`[SlowRender] Cached pre-rendered jay-html at ${preRenderedPath}`);
|
|
2087
|
+
const pagePartsResult = await loadPageParts(
|
|
2088
|
+
vite,
|
|
2089
|
+
route,
|
|
2090
|
+
options.pagesRootFolder,
|
|
2091
|
+
options.projectRootFolder,
|
|
2092
|
+
options.jayRollupConfig,
|
|
2093
|
+
{ preRenderedPath }
|
|
2094
|
+
);
|
|
2095
|
+
if (!pagePartsResult.val) {
|
|
2096
|
+
getLogger().info(pagePartsResult.validations.join("\n"));
|
|
2097
|
+
res.status(500).end(pagePartsResult.validations.join("\n"));
|
|
2098
|
+
timing?.end();
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
|
|
2102
|
+
const pluginsForPage = filterPluginsForPage(
|
|
2103
|
+
allPluginClientInits,
|
|
2104
|
+
allPluginsWithInit,
|
|
2105
|
+
usedPackages
|
|
2106
|
+
);
|
|
2107
|
+
const fastStart = Date.now();
|
|
2108
|
+
const renderedFast = await renderFastChangingData(
|
|
2109
|
+
pageParams,
|
|
2110
|
+
pageProps,
|
|
2111
|
+
carryForward,
|
|
2112
|
+
pageParts
|
|
2113
|
+
);
|
|
2114
|
+
timing?.recordFastRender(Date.now() - fastStart);
|
|
2115
|
+
if (renderedFast.kind !== "PhaseOutput") {
|
|
2116
|
+
handleOtherResponseCodes(res, renderedFast);
|
|
2117
|
+
timing?.end();
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
let fastViewState = renderedFast.rendered;
|
|
2121
|
+
let fastCarryForward = renderedFast.carryForward;
|
|
2122
|
+
const instancePhaseData = carryForward?.__instances;
|
|
2123
|
+
if (instancePhaseData && pagePartsResult.val.headlessInstanceComponents.length > 0) {
|
|
2124
|
+
const instanceFastResult = await renderFastChangingDataForInstances(
|
|
2125
|
+
instancePhaseData,
|
|
2126
|
+
pagePartsResult.val.headlessInstanceComponents
|
|
2127
|
+
);
|
|
2128
|
+
if (instanceFastResult) {
|
|
2129
|
+
fastViewState = {
|
|
2130
|
+
...fastViewState,
|
|
2131
|
+
__headlessInstances: instanceFastResult.viewStates
|
|
2132
|
+
};
|
|
2133
|
+
fastCarryForward = {
|
|
2134
|
+
...fastCarryForward,
|
|
2135
|
+
__headlessInstances: instanceFastResult.carryForwards
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
await sendResponse(
|
|
2140
|
+
vite,
|
|
2141
|
+
res,
|
|
2142
|
+
url,
|
|
2143
|
+
preRenderedPath,
|
|
2144
|
+
pageParts,
|
|
2145
|
+
fastViewState,
|
|
2146
|
+
fastCarryForward,
|
|
2147
|
+
clientTrackByMap,
|
|
2148
|
+
projectInit,
|
|
2149
|
+
pluginsForPage,
|
|
2150
|
+
options,
|
|
2151
|
+
renderedSlowly.rendered,
|
|
2152
|
+
timing
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
|
|
2156
|
+
const loadStart = Date.now();
|
|
2157
|
+
const pagePartsResult = await loadPageParts(
|
|
2158
|
+
vite,
|
|
2159
|
+
route,
|
|
2160
|
+
options.pagesRootFolder,
|
|
2161
|
+
options.projectRootFolder,
|
|
2162
|
+
options.jayRollupConfig
|
|
2163
|
+
);
|
|
2164
|
+
timing?.recordViteSsr(Date.now() - loadStart);
|
|
2165
|
+
if (!pagePartsResult.val) {
|
|
2166
|
+
getLogger().info(pagePartsResult.validations.join("\n"));
|
|
2167
|
+
res.status(500).end(pagePartsResult.validations.join("\n"));
|
|
2168
|
+
timing?.end();
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
const {
|
|
2172
|
+
parts: pageParts,
|
|
2173
|
+
serverTrackByMap,
|
|
2174
|
+
clientTrackByMap,
|
|
2175
|
+
usedPackages
|
|
2176
|
+
} = pagePartsResult.val;
|
|
2177
|
+
const pluginsForPage = filterPluginsForPage(
|
|
2178
|
+
allPluginClientInits,
|
|
2179
|
+
allPluginsWithInit,
|
|
2180
|
+
usedPackages
|
|
2181
|
+
);
|
|
2182
|
+
const slowStart = Date.now();
|
|
2183
|
+
const renderedSlowly = await slowlyPhase.runSlowlyForPage(pageParams, pageProps, pageParts);
|
|
2184
|
+
if (renderedSlowly.kind !== "PhaseOutput") {
|
|
2185
|
+
timing?.recordSlowRender(Date.now() - slowStart);
|
|
2186
|
+
if (renderedSlowly.kind === "ClientError") {
|
|
2187
|
+
handleOtherResponseCodes(res, renderedSlowly);
|
|
2188
|
+
}
|
|
2189
|
+
timing?.end();
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
let instanceViewStates;
|
|
2193
|
+
let instancePhaseDataForFast;
|
|
2194
|
+
const headlessInstanceComponents = pagePartsResult.val.headlessInstanceComponents ?? [];
|
|
2195
|
+
if (headlessInstanceComponents.length > 0) {
|
|
2196
|
+
const jayHtmlContent = await fs$1.readFile(route.jayHtmlPath, "utf-8");
|
|
2197
|
+
const discoveryResult = discoverHeadlessInstances(jayHtmlContent);
|
|
2198
|
+
if (discoveryResult.instances.length > 0) {
|
|
2199
|
+
const slowResult = await slowRenderInstances(
|
|
2200
|
+
discoveryResult.instances,
|
|
2201
|
+
headlessInstanceComponents
|
|
2202
|
+
);
|
|
2203
|
+
if (slowResult) {
|
|
2204
|
+
instanceViewStates = { ...slowResult.slowViewStates };
|
|
2205
|
+
instancePhaseDataForFast = slowResult.instancePhaseData;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
timing?.recordSlowRender(Date.now() - slowStart);
|
|
2210
|
+
const fastStart = Date.now();
|
|
2211
|
+
const renderedFast = await renderFastChangingData(
|
|
2212
|
+
pageParams,
|
|
2213
|
+
pageProps,
|
|
2214
|
+
renderedSlowly.carryForward,
|
|
2215
|
+
pageParts
|
|
2216
|
+
);
|
|
2217
|
+
if (instancePhaseDataForFast && instanceViewStates) {
|
|
2218
|
+
const instanceFastResult = await renderFastChangingDataForInstances(
|
|
2219
|
+
instancePhaseDataForFast,
|
|
2220
|
+
headlessInstanceComponents
|
|
2221
|
+
);
|
|
2222
|
+
if (instanceFastResult) {
|
|
2223
|
+
for (const [coordKey, fastVS] of Object.entries(instanceFastResult.viewStates)) {
|
|
2224
|
+
instanceViewStates[coordKey] = {
|
|
2225
|
+
...instanceViewStates[coordKey] || {},
|
|
2226
|
+
...fastVS
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
timing?.recordFastRender(Date.now() - fastStart);
|
|
2232
|
+
if (renderedFast.kind !== "PhaseOutput") {
|
|
2233
|
+
handleOtherResponseCodes(res, renderedFast);
|
|
2234
|
+
timing?.end();
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
let viewState;
|
|
2238
|
+
if (serverTrackByMap && Object.keys(serverTrackByMap).length > 0) {
|
|
2239
|
+
viewState = deepMergeViewStates(
|
|
2240
|
+
renderedSlowly.rendered,
|
|
2241
|
+
renderedFast.rendered,
|
|
2242
|
+
serverTrackByMap
|
|
2243
|
+
);
|
|
2244
|
+
} else {
|
|
2245
|
+
viewState = { ...renderedSlowly.rendered, ...renderedFast.rendered };
|
|
2246
|
+
}
|
|
2247
|
+
if (instanceViewStates && Object.keys(instanceViewStates).length > 0) {
|
|
2248
|
+
viewState = {
|
|
2249
|
+
...viewState,
|
|
2250
|
+
__headlessInstances: instanceViewStates
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
await sendResponse(
|
|
2254
|
+
vite,
|
|
2255
|
+
res,
|
|
2256
|
+
url,
|
|
2257
|
+
route.jayHtmlPath,
|
|
2258
|
+
pageParts,
|
|
2259
|
+
viewState,
|
|
2260
|
+
renderedFast.carryForward,
|
|
2261
|
+
clientTrackByMap,
|
|
2262
|
+
projectInit,
|
|
2263
|
+
pluginsForPage,
|
|
2264
|
+
options,
|
|
2265
|
+
void 0,
|
|
2266
|
+
timing
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
async function sendResponse(vite, res, url, jayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState, timing) {
|
|
2270
|
+
const pageHtml = generateClientScript(
|
|
2271
|
+
viewState,
|
|
2272
|
+
carryForward,
|
|
2273
|
+
pageParts,
|
|
2274
|
+
jayHtmlPath,
|
|
2275
|
+
clientTrackByMap,
|
|
2276
|
+
getClientInitData(),
|
|
2277
|
+
projectInit,
|
|
2278
|
+
pluginsForPage,
|
|
2279
|
+
{
|
|
2280
|
+
enableAutomation: !options.disableAutomation,
|
|
2281
|
+
slowViewState
|
|
2282
|
+
}
|
|
2283
|
+
);
|
|
2284
|
+
if (options.buildFolder) {
|
|
2285
|
+
const pageName = !url || url === "/" ? "index" : url.replace(/^\//, "").replace(/\//g, "-");
|
|
2286
|
+
const clientScriptDir = path__default.join(options.buildFolder, "client-scripts");
|
|
2287
|
+
await fs$1.mkdir(clientScriptDir, { recursive: true });
|
|
2288
|
+
await fs$1.writeFile(path__default.join(clientScriptDir, `${pageName}.html`), pageHtml, "utf-8");
|
|
2289
|
+
}
|
|
2290
|
+
const viteStart = Date.now();
|
|
2291
|
+
const compiledPageHtml = await vite.transformIndexHtml(!!url ? url : "/", pageHtml);
|
|
2292
|
+
timing?.recordViteClient(Date.now() - viteStart);
|
|
2293
|
+
res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
|
|
2294
|
+
timing?.end();
|
|
2295
|
+
}
|
|
2296
|
+
async function preRenderJayHtml(route, slowViewState, headlessContracts, headlessInstanceComponents) {
|
|
2297
|
+
const jayHtmlContent = await fs$1.readFile(route.jayHtmlPath, "utf-8");
|
|
2298
|
+
const contractPath = route.jayHtmlPath.replace(".jay-html", ".jay-contract");
|
|
2299
|
+
let contract;
|
|
2300
|
+
try {
|
|
2301
|
+
const contractContent = await fs$1.readFile(contractPath, "utf-8");
|
|
2302
|
+
const parseResult = parseContract(contractContent, path__default.basename(contractPath));
|
|
2303
|
+
if (parseResult.val) {
|
|
2304
|
+
contract = parseResult.val;
|
|
2305
|
+
} else if (parseResult.validations.length > 0) {
|
|
2306
|
+
getLogger().error(
|
|
2307
|
+
`[SlowRender] Contract parse error for ${contractPath}: ${parseResult.validations.join(", ")}`
|
|
2308
|
+
);
|
|
2309
|
+
return void 0;
|
|
2310
|
+
}
|
|
2311
|
+
} catch (error) {
|
|
2312
|
+
if (error.code !== "ENOENT") {
|
|
2313
|
+
getLogger().error(`[SlowRender] Error reading contract ${contractPath}: ${error}`);
|
|
2314
|
+
return void 0;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
const result = slowRenderTransform({
|
|
2318
|
+
jayHtmlContent,
|
|
2319
|
+
slowViewState,
|
|
2320
|
+
contract,
|
|
2321
|
+
headlessContracts,
|
|
2322
|
+
sourceDir: path__default.dirname(route.jayHtmlPath),
|
|
2323
|
+
importResolver: JAY_IMPORT_RESOLVER
|
|
2324
|
+
});
|
|
2325
|
+
if (!result.val) {
|
|
2326
|
+
if (result.validations.length > 0) {
|
|
2327
|
+
getLogger().error(
|
|
2328
|
+
`[SlowRender] Transform failed for ${route.jayHtmlPath}: ${result.validations.join(", ")}`
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
return void 0;
|
|
2332
|
+
}
|
|
2333
|
+
let preRenderedJayHtml = result.val.preRenderedJayHtml;
|
|
2334
|
+
let instancePhaseData;
|
|
2335
|
+
if (headlessInstanceComponents.length > 0) {
|
|
2336
|
+
const discoveryResult = discoverHeadlessInstances(preRenderedJayHtml);
|
|
2337
|
+
preRenderedJayHtml = discoveryResult.preRenderedJayHtml;
|
|
2338
|
+
if (discoveryResult.instances.length > 0) {
|
|
2339
|
+
const slowResult = await slowRenderInstances(
|
|
2340
|
+
discoveryResult.instances,
|
|
2341
|
+
headlessInstanceComponents
|
|
2342
|
+
);
|
|
2343
|
+
if (slowResult) {
|
|
2344
|
+
instancePhaseData = slowResult.instancePhaseData;
|
|
2345
|
+
const pass2Result = resolveHeadlessInstances(
|
|
2346
|
+
preRenderedJayHtml,
|
|
2347
|
+
slowResult.resolvedData,
|
|
2348
|
+
JAY_IMPORT_RESOLVER
|
|
2349
|
+
);
|
|
2350
|
+
if (pass2Result.val) {
|
|
2351
|
+
preRenderedJayHtml = pass2Result.val;
|
|
2352
|
+
}
|
|
2353
|
+
if (pass2Result.validations.length > 0) {
|
|
2354
|
+
getLogger().error(
|
|
2355
|
+
`[SlowRender] Instance resolution warnings for ${route.jayHtmlPath}: ${pass2Result.validations.join(", ")}`
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
return { preRenderedJayHtml, instancePhaseData };
|
|
2362
|
+
}
|
|
2363
|
+
async function renderFastChangingDataForInstances(instancePhaseData, headlessInstanceComponents) {
|
|
2364
|
+
const componentByContractName = /* @__PURE__ */ new Map();
|
|
2365
|
+
for (const comp of headlessInstanceComponents) {
|
|
2366
|
+
componentByContractName.set(comp.contractName, comp);
|
|
2367
|
+
}
|
|
2368
|
+
const viewStates = {};
|
|
2369
|
+
const carryForwards = {};
|
|
2370
|
+
let hasResults = false;
|
|
2371
|
+
for (const instance of instancePhaseData.discovered) {
|
|
2372
|
+
const coordKey = instance.coordinate.join("/");
|
|
2373
|
+
const comp = componentByContractName.get(instance.contractName);
|
|
2374
|
+
if (!comp || !comp.compDefinition.fastRender) {
|
|
2375
|
+
continue;
|
|
2376
|
+
}
|
|
2377
|
+
const instanceCarryForward = instancePhaseData.carryForwards[coordKey] || {};
|
|
2378
|
+
const services = resolveServices(comp.compDefinition.services);
|
|
2379
|
+
const fastResult = await comp.compDefinition.fastRender(
|
|
2380
|
+
instance.props,
|
|
2381
|
+
instanceCarryForward,
|
|
2382
|
+
...services
|
|
2383
|
+
);
|
|
2384
|
+
if (fastResult.kind === "PhaseOutput") {
|
|
2385
|
+
viewStates[coordKey] = fastResult.rendered;
|
|
2386
|
+
carryForwards[coordKey] = fastResult.carryForward;
|
|
2387
|
+
hasResults = true;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return hasResults ? { viewStates, carryForwards } : void 0;
|
|
2391
|
+
}
|
|
2392
|
+
async function materializeDynamicContracts(projectRootFolder, buildFolder, viteServer) {
|
|
2393
|
+
try {
|
|
2394
|
+
const services = getServiceRegistry();
|
|
2395
|
+
const result = await materializeContracts(
|
|
2396
|
+
{
|
|
2397
|
+
projectRoot: projectRootFolder,
|
|
2398
|
+
outputDir: path__default.join(buildFolder, "materialized-contracts"),
|
|
2399
|
+
verbose: false,
|
|
2400
|
+
viteServer
|
|
2401
|
+
},
|
|
2402
|
+
services
|
|
2403
|
+
);
|
|
2404
|
+
const dynamicCount = result.dynamicCount;
|
|
2405
|
+
if (dynamicCount > 0) {
|
|
2406
|
+
getLogger().info(`[Contracts] Materialized ${dynamicCount} dynamic contract(s)`);
|
|
2407
|
+
}
|
|
2408
|
+
} catch (error) {
|
|
2409
|
+
getLogger().warn(`[Contracts] Failed to materialize dynamic contracts: ${error.message}`);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
async function mkDevServer(rawOptions) {
|
|
2413
|
+
const options = defaults(rawOptions);
|
|
1739
2414
|
const {
|
|
1740
2415
|
publicBaseUrlPath,
|
|
1741
2416
|
pagesRootFolder,
|
|
1742
2417
|
projectRootFolder,
|
|
2418
|
+
buildFolder,
|
|
1743
2419
|
jayRollupConfig,
|
|
1744
2420
|
dontCacheSlowly
|
|
1745
|
-
} =
|
|
2421
|
+
} = options;
|
|
2422
|
+
const viteLogLevel = options.logLevel === "silent" ? "silent" : options.logLevel === "verbose" ? "info" : "warn";
|
|
1746
2423
|
const lifecycleManager = new ServiceLifecycleManager(projectRootFolder);
|
|
1747
2424
|
setupGracefulShutdown(lifecycleManager);
|
|
1748
|
-
const vite = await
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
appType: "custom",
|
|
2425
|
+
const vite = await createViteServer({
|
|
2426
|
+
projectRoot: projectRootFolder,
|
|
2427
|
+
pagesRoot: pagesRootFolder,
|
|
1752
2428
|
base: publicBaseUrlPath,
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
// Mark stack-server-runtime as external so Vite uses Node's require
|
|
1756
|
-
// This ensures lib/init.ts and dev-server share the same module instance
|
|
1757
|
-
external: ["@jay-framework/stack-server-runtime"]
|
|
1758
|
-
}
|
|
2429
|
+
jayRollupConfig,
|
|
2430
|
+
logLevel: viteLogLevel
|
|
1759
2431
|
});
|
|
1760
2432
|
lifecycleManager.setViteServer(vite);
|
|
1761
2433
|
await lifecycleManager.initialize();
|
|
2434
|
+
await materializeDynamicContracts(projectRootFolder, buildFolder, vite);
|
|
1762
2435
|
setupServiceHotReload(vite, lifecycleManager);
|
|
1763
2436
|
setupActionRouter(vite);
|
|
1764
2437
|
const routes = await initRoutes(pagesRootFolder);
|
|
1765
2438
|
const slowlyPhase = new DevSlowlyChangingPhase(dontCacheSlowly);
|
|
2439
|
+
const slowRenderCacheDir = path__default.join(buildFolder, "slow-render-cache");
|
|
2440
|
+
const slowRenderCache = new SlowRenderCache(slowRenderCacheDir, pagesRootFolder);
|
|
2441
|
+
setupSlowRenderCacheInvalidation(vite, slowRenderCache, pagesRootFolder);
|
|
1766
2442
|
const projectInit = lifecycleManager.getProjectInit() ?? void 0;
|
|
1767
2443
|
const pluginsWithInit = lifecycleManager.getPluginsWithInit();
|
|
1768
2444
|
const pluginClientInits = preparePluginClientInits(pluginsWithInit);
|
|
1769
2445
|
const devServerRoutes = routes.map(
|
|
1770
|
-
(route) => mkRoute(
|
|
2446
|
+
(route) => mkRoute(
|
|
2447
|
+
route,
|
|
2448
|
+
vite,
|
|
2449
|
+
slowlyPhase,
|
|
2450
|
+
options,
|
|
2451
|
+
slowRenderCache,
|
|
2452
|
+
projectInit,
|
|
2453
|
+
pluginsWithInit,
|
|
2454
|
+
pluginClientInits
|
|
2455
|
+
)
|
|
1771
2456
|
);
|
|
1772
2457
|
return {
|
|
1773
2458
|
server: vite.middlewares,
|
|
@@ -1778,7 +2463,7 @@ async function mkDevServer(options) {
|
|
|
1778
2463
|
}
|
|
1779
2464
|
function setupGracefulShutdown(lifecycleManager) {
|
|
1780
2465
|
const shutdown = async (signal) => {
|
|
1781
|
-
|
|
2466
|
+
getLogger().important(`
|
|
1782
2467
|
${signal} received, shutting down gracefully...`);
|
|
1783
2468
|
await lifecycleManager.shutdown();
|
|
1784
2469
|
process.exit(0);
|
|
@@ -1794,7 +2479,7 @@ function setupServiceHotReload(vite, lifecycleManager) {
|
|
|
1794
2479
|
vite.watcher.add(initFilePath);
|
|
1795
2480
|
vite.watcher.on("change", async (changedPath) => {
|
|
1796
2481
|
if (changedPath === initFilePath) {
|
|
1797
|
-
|
|
2482
|
+
getLogger().info("[Services] lib/init.ts changed, reloading services...");
|
|
1798
2483
|
try {
|
|
1799
2484
|
await lifecycleManager.reload();
|
|
1800
2485
|
vite.ws.send({
|
|
@@ -1802,7 +2487,7 @@ function setupServiceHotReload(vite, lifecycleManager) {
|
|
|
1802
2487
|
path: "*"
|
|
1803
2488
|
});
|
|
1804
2489
|
} catch (error) {
|
|
1805
|
-
|
|
2490
|
+
getLogger().error(`[Services] Failed to reload services: ${error}`);
|
|
1806
2491
|
}
|
|
1807
2492
|
}
|
|
1808
2493
|
});
|
|
@@ -1810,11 +2495,45 @@ function setupServiceHotReload(vite, lifecycleManager) {
|
|
|
1810
2495
|
function setupActionRouter(vite) {
|
|
1811
2496
|
vite.middlewares.use(actionBodyParser());
|
|
1812
2497
|
vite.middlewares.use(ACTION_ENDPOINT_BASE, createActionRouter());
|
|
1813
|
-
|
|
2498
|
+
getLogger().info(`[Actions] Action router mounted at ${ACTION_ENDPOINT_BASE}`);
|
|
2499
|
+
}
|
|
2500
|
+
function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
|
|
2501
|
+
vite.watcher.on("change", (changedPath) => {
|
|
2502
|
+
if (!changedPath.startsWith(pagesRootFolder)) {
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
if (changedPath.endsWith(".jay-html")) {
|
|
2506
|
+
cache.invalidate(changedPath).then(() => {
|
|
2507
|
+
getLogger().info(`[SlowRender] Cache invalidated for ${changedPath}`);
|
|
2508
|
+
});
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
if (changedPath.endsWith("page.ts")) {
|
|
2512
|
+
const dir = path__default.dirname(changedPath);
|
|
2513
|
+
const jayHtmlPath = path__default.join(dir, "page.jay-html");
|
|
2514
|
+
cache.invalidate(jayHtmlPath).then(() => {
|
|
2515
|
+
getLogger().info(
|
|
2516
|
+
`[SlowRender] Cache invalidated for ${jayHtmlPath} (page.ts changed)`
|
|
2517
|
+
);
|
|
2518
|
+
});
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
if (changedPath.endsWith(".jay-contract")) {
|
|
2522
|
+
const jayHtmlPath = changedPath.replace(".jay-contract", ".jay-html");
|
|
2523
|
+
cache.invalidate(jayHtmlPath).then(() => {
|
|
2524
|
+
getLogger().info(
|
|
2525
|
+
`[SlowRender] Cache invalidated for ${jayHtmlPath} (contract changed)`
|
|
2526
|
+
);
|
|
2527
|
+
});
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
});
|
|
1814
2531
|
}
|
|
1815
2532
|
export {
|
|
1816
2533
|
ACTION_ENDPOINT_BASE,
|
|
1817
2534
|
actionBodyParser,
|
|
1818
2535
|
createActionRouter,
|
|
2536
|
+
createViteForCli,
|
|
2537
|
+
createViteServer,
|
|
1819
2538
|
mkDevServer
|
|
1820
2539
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,21 +22,22 @@
|
|
|
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/
|
|
30
|
-
"@jay-framework/
|
|
31
|
-
"@jay-framework/stack-
|
|
32
|
-
"@jay-framework/stack-
|
|
33
|
-
"@jay-framework/
|
|
25
|
+
"@jay-framework/compiler-jay-stack": "^0.12.0",
|
|
26
|
+
"@jay-framework/compiler-shared": "^0.12.0",
|
|
27
|
+
"@jay-framework/component": "^0.12.0",
|
|
28
|
+
"@jay-framework/fullstack-component": "^0.12.0",
|
|
29
|
+
"@jay-framework/logger": "^0.12.0",
|
|
30
|
+
"@jay-framework/runtime": "^0.12.0",
|
|
31
|
+
"@jay-framework/stack-client-runtime": "^0.12.0",
|
|
32
|
+
"@jay-framework/stack-route-scanner": "^0.12.0",
|
|
33
|
+
"@jay-framework/stack-server-runtime": "^0.12.0",
|
|
34
|
+
"@jay-framework/view-state-merge": "^0.12.0",
|
|
34
35
|
"vite": "^5.0.11"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@jay-framework/dev-environment": "^0.
|
|
38
|
-
"@jay-framework/jay-cli": "^0.
|
|
39
|
-
"@jay-framework/stack-client-runtime": "^0.
|
|
38
|
+
"@jay-framework/dev-environment": "^0.12.0",
|
|
39
|
+
"@jay-framework/jay-cli": "^0.12.0",
|
|
40
|
+
"@jay-framework/stack-client-runtime": "^0.12.0",
|
|
40
41
|
"@types/express": "^5.0.2",
|
|
41
42
|
"@types/node": "^22.15.21",
|
|
42
43
|
"nodemon": "^3.0.3",
|