evolit 0.1.0-alpha.16 → 0.1.0-alpha.18
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/README.md +9 -3
- package/package.json +1 -1
- package/src/build.js +22 -4
- package/src/cli.js +16 -14
- package/src/client-assets.js +73 -12
- package/src/compiler.js +9 -9
- package/src/deployment-runtime.js +22 -4
- package/src/development-events.js +87 -0
- package/src/render.js +7 -5
- package/src/route-config.js +3 -1
- package/src/server.js +4 -51
package/README.md
CHANGED
|
@@ -104,12 +104,18 @@ export const routeConfig = {
|
|
|
104
104
|
- `static`: prerender in `build` and serve from the response cache in `start`
|
|
105
105
|
- `revalidate`: cache the HTML response for `N` seconds and regenerate on expiry
|
|
106
106
|
|
|
107
|
+
Pages without `routeConfig.cache` default to `{ revalidate: 60 }`. This caches a normal SSR
|
|
108
|
+
render by pathname and query string while keeping content fresh without requiring a cache
|
|
109
|
+
declaration on every catalog or CMS page. Declare `cache: "static"` for fully static pages or
|
|
110
|
+
`cache: "dynamic"` when a page must always render per request.
|
|
111
|
+
|
|
107
112
|
The same semantics work in local development and in production runtimes. Only the backing cache
|
|
108
113
|
store changes.
|
|
109
114
|
|
|
110
|
-
The default cache key includes the pathname and query string
|
|
111
|
-
headers, cookies, or `requestUrl()` makes
|
|
112
|
-
|
|
115
|
+
The default cache key includes the pathname and query string, so `params` and `searchParams` are
|
|
116
|
+
cacheable by URL. Reading the `request` prop, request headers, cookies, or `requestUrl()` makes
|
|
117
|
+
the completed render dynamic; it is never stored in the HTML response cache. `routeConfig.cache`
|
|
118
|
+
is the sole authority for HTML caching; setting a
|
|
113
119
|
`Cache-Control` response header does not alter that policy.
|
|
114
120
|
|
|
115
121
|
## Request APIs
|
package/package.json
CHANGED
package/src/build.js
CHANGED
|
@@ -255,17 +255,35 @@ export async function buildProject(projectRoot) {
|
|
|
255
255
|
...styleUrls.map((href) => `<link rel="stylesheet" href="${href}">`),
|
|
256
256
|
].join("\n");
|
|
257
257
|
},
|
|
258
|
-
resolveBootstrap({ result }) {
|
|
258
|
+
resolveBootstrap({ routeResult, result }) {
|
|
259
|
+
const routeClientImports = resolveRouteClientImports(
|
|
260
|
+
routeResult,
|
|
261
|
+
projectRoot,
|
|
262
|
+
clientAssets,
|
|
263
|
+
);
|
|
259
264
|
return createHydrationBootstrap({
|
|
260
|
-
hydrationData: normalizeHydrationDataForClient(
|
|
265
|
+
hydrationData: normalizeHydrationDataForClient(
|
|
266
|
+
result.hydrationData,
|
|
267
|
+
projectRoot,
|
|
268
|
+
routeClientImports,
|
|
269
|
+
),
|
|
261
270
|
assetResolver,
|
|
262
271
|
hydrationModuleUrl,
|
|
263
272
|
});
|
|
264
273
|
},
|
|
265
|
-
transformDocument({ result, document }) {
|
|
274
|
+
transformDocument({ routeResult, result, document }) {
|
|
275
|
+
const routeClientImports = resolveRouteClientImports(
|
|
276
|
+
routeResult,
|
|
277
|
+
projectRoot,
|
|
278
|
+
clientAssets,
|
|
279
|
+
);
|
|
266
280
|
return rewriteHydrationDataScript(
|
|
267
281
|
document,
|
|
268
|
-
normalizeHydrationDataForClient(
|
|
282
|
+
normalizeHydrationDataForClient(
|
|
283
|
+
result.hydrationData,
|
|
284
|
+
projectRoot,
|
|
285
|
+
routeClientImports,
|
|
286
|
+
),
|
|
269
287
|
);
|
|
270
288
|
},
|
|
271
289
|
});
|
package/src/cli.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { scaffoldSite } from "./scaffold.js";
|
|
6
|
-
import {
|
|
6
|
+
import { createDevelopmentEventReporter } from "./development-events.js";
|
|
7
|
+
|
|
8
|
+
const reportEvent = createDevelopmentEventReporter();
|
|
7
9
|
|
|
8
10
|
function parseArguments(argv) {
|
|
9
11
|
const [command, ...rest] = argv;
|
|
@@ -25,9 +27,7 @@ function parseArguments(argv) {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
function printUsage() {
|
|
28
|
-
|
|
29
|
-
console.log(` evolit ${terminal.cyan("init")} <directory>`);
|
|
30
|
-
console.log(` evolit ${terminal.cyan("<dev|build|start>")} [--port 3000]`);
|
|
30
|
+
reportEvent({ type: "usage" });
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async function createSite(projectRoot, targetDirectory) {
|
|
@@ -36,7 +36,7 @@ async function createSite(projectRoot, targetDirectory) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const createdDirectory = await scaffoldSite(path.resolve(projectRoot, targetDirectory));
|
|
39
|
-
|
|
39
|
+
reportEvent({ type: "site-created", directory: createdDirectory });
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
async function run() {
|
|
@@ -56,25 +56,29 @@ async function run() {
|
|
|
56
56
|
if (command === "build") {
|
|
57
57
|
const { buildProject } = await import("./build.js");
|
|
58
58
|
const manifestPath = await buildProject(projectRoot);
|
|
59
|
-
|
|
59
|
+
reportEvent({
|
|
60
|
+
type: "build-ready",
|
|
61
|
+
manifestPath: path.relative(projectRoot, manifestPath),
|
|
62
|
+
});
|
|
60
63
|
return;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
if (command === "start") {
|
|
64
67
|
const { createStartServer } = await import("./server.js");
|
|
65
|
-
|
|
68
|
+
reportEvent({ type: "server-starting", mode: "start" });
|
|
66
69
|
const server = await createStartServer(projectRoot, options);
|
|
67
70
|
await server.listen();
|
|
68
|
-
|
|
71
|
+
reportEvent({ type: "server-ready", port: server.port });
|
|
69
72
|
return;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
if (command === "dev") {
|
|
73
76
|
const { createDevServer } = await import("./server.js");
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
const server = await createDevServer(projectRoot, {
|
|
78
|
+
...options,
|
|
79
|
+
onDevelopmentEvent: reportEvent,
|
|
80
|
+
});
|
|
76
81
|
await server.listen();
|
|
77
|
-
console.log(`${terminal.green("✓")} Ready on ${terminal.cyan(`http://localhost:${server.port}`)}`);
|
|
78
82
|
return;
|
|
79
83
|
}
|
|
80
84
|
|
|
@@ -82,8 +86,6 @@ async function run() {
|
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
run().catch((error) => {
|
|
85
|
-
|
|
86
|
-
`${terminal.red("[evolit]")} ${terminal.red("✖")} ${error instanceof Error ? error.stack ?? error.message : String(error)}`,
|
|
87
|
-
);
|
|
89
|
+
reportEvent({ type: "fatal-error", error });
|
|
88
90
|
process.exitCode = 1;
|
|
89
91
|
});
|
package/src/client-assets.js
CHANGED
|
@@ -934,6 +934,55 @@ function toCompiledClientModuleRelativePath(relativePath) {
|
|
|
934
934
|
: `${relativePath}.mjs`;
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
+
function toCatchAllClientModuleRelativePath(clientModule) {
|
|
938
|
+
return clientModule
|
|
939
|
+
.split(/[\\/]/)
|
|
940
|
+
.map((segment) => {
|
|
941
|
+
if (segment.startsWith("[[...") && segment.endsWith("]]")) {
|
|
942
|
+
return `**...${segment.slice(5, -2)}**`;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
|
946
|
+
return `*...${segment.slice(4, -1)}*`;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return segment;
|
|
950
|
+
})
|
|
951
|
+
.join("/");
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function toRollupSafeClientModuleRelativePath(clientModule) {
|
|
955
|
+
return clientModule.replaceAll(/[\[\]]/g, "_");
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function getClientModuleCandidates(clientModule) {
|
|
959
|
+
const normalizedClientModule = clientModule.split(path.sep).join("/");
|
|
960
|
+
return [...new Set([
|
|
961
|
+
normalizedClientModule,
|
|
962
|
+
toCatchAllClientModuleRelativePath(normalizedClientModule),
|
|
963
|
+
toRollupSafeClientModuleRelativePath(normalizedClientModule),
|
|
964
|
+
])];
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function getClientModulePublicUrl(assetManifest, clientModule) {
|
|
968
|
+
for (const candidate of getClientModuleCandidates(clientModule)) {
|
|
969
|
+
const publicUrl = assetManifest.byClientModule[candidate];
|
|
970
|
+
if (typeof publicUrl === "string" && publicUrl.length > 0) {
|
|
971
|
+
return publicUrl;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const candidates = new Set(getClientModuleCandidates(clientModule));
|
|
976
|
+
const asset = assetManifest.assets.find((entry) =>
|
|
977
|
+
candidates.has(entry?.clientModule) && typeof entry?.publicUrl === "string",
|
|
978
|
+
);
|
|
979
|
+
if (asset) {
|
|
980
|
+
return asset.publicUrl;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
|
|
937
986
|
function toPublicHydrationModuleId(projectRoot, moduleId) {
|
|
938
987
|
if (typeof moduleId !== "string" || moduleId.length === 0) {
|
|
939
988
|
return null;
|
|
@@ -1001,10 +1050,7 @@ export function createAssetResolver(projectRoot, options = {}) {
|
|
|
1001
1050
|
}
|
|
1002
1051
|
|
|
1003
1052
|
if (assetManifest) {
|
|
1004
|
-
return
|
|
1005
|
-
assetManifest,
|
|
1006
|
-
relativeClientModule,
|
|
1007
|
-
)?.publicUrl ?? null;
|
|
1053
|
+
return getClientModulePublicUrl(assetManifest, relativeClientModule);
|
|
1008
1054
|
}
|
|
1009
1055
|
|
|
1010
1056
|
return null;
|
|
@@ -1029,8 +1075,8 @@ export function createHydrationBootstrap({
|
|
|
1029
1075
|
hydrationModuleUrl = "@litsx/ssr/hydration",
|
|
1030
1076
|
}) {
|
|
1031
1077
|
const normalizedHydrationData = normalizeHydrationDataForClient(hydrationData);
|
|
1032
|
-
const moduleUrls = [...new Set(
|
|
1033
|
-
(normalizedHydrationData?.roots ?? [])
|
|
1078
|
+
const moduleUrls = [...new Set([
|
|
1079
|
+
...(normalizedHydrationData?.roots ?? [])
|
|
1034
1080
|
.map((root) => {
|
|
1035
1081
|
if (typeof root?.moduleId !== "string" || root.moduleId.length === 0) {
|
|
1036
1082
|
return null;
|
|
@@ -1039,7 +1085,10 @@ export function createHydrationBootstrap({
|
|
|
1039
1085
|
return assetResolver?.(root.moduleId) ?? null;
|
|
1040
1086
|
})
|
|
1041
1087
|
.filter((moduleUrl) => typeof moduleUrl === "string" && moduleUrl.length > 0),
|
|
1042
|
-
|
|
1088
|
+
...(Array.isArray(normalizedHydrationData?.clientImports)
|
|
1089
|
+
? normalizedHydrationData.clientImports
|
|
1090
|
+
: []),
|
|
1091
|
+
].filter((moduleUrl) => typeof moduleUrl === "string" && moduleUrl.length > 0))];
|
|
1043
1092
|
|
|
1044
1093
|
if (moduleUrls.length === 0) {
|
|
1045
1094
|
return "";
|
|
@@ -1063,7 +1112,11 @@ ${moduleLoaders}
|
|
|
1063
1112
|
return escapeInlineScriptText(source);
|
|
1064
1113
|
}
|
|
1065
1114
|
|
|
1066
|
-
export function normalizeHydrationDataForClient(
|
|
1115
|
+
export function normalizeHydrationDataForClient(
|
|
1116
|
+
hydrationData,
|
|
1117
|
+
projectRoot = null,
|
|
1118
|
+
additionalClientImports = [],
|
|
1119
|
+
) {
|
|
1067
1120
|
if (!hydrationData || typeof hydrationData !== "object") {
|
|
1068
1121
|
return hydrationData ?? null;
|
|
1069
1122
|
}
|
|
@@ -1096,7 +1149,10 @@ export function normalizeHydrationDataForClient(hydrationData, projectRoot = nul
|
|
|
1096
1149
|
roots,
|
|
1097
1150
|
};
|
|
1098
1151
|
const payload = hydrationData.payload;
|
|
1099
|
-
const clientImports =
|
|
1152
|
+
const clientImports = [...new Set([
|
|
1153
|
+
...(Array.isArray(hydrationData.clientImports) ? hydrationData.clientImports : []),
|
|
1154
|
+
...(Array.isArray(additionalClientImports) ? additionalClientImports : []),
|
|
1155
|
+
].filter((value) => typeof value === "string" && value.length > 0))];
|
|
1100
1156
|
|
|
1101
1157
|
Object.defineProperties(normalizedHydrationData, {
|
|
1102
1158
|
payload: {
|
|
@@ -1728,9 +1784,14 @@ async function bundleClientAssets(projectRoot, options = {}) {
|
|
|
1728
1784
|
|
|
1729
1785
|
const outputPath = path.join(staticRoot, chunk.fileName);
|
|
1730
1786
|
const publicUrl = toStaticPublicUrl(chunk.fileName);
|
|
1731
|
-
const
|
|
1787
|
+
const entryClientModule = clientModuleByEntryName.get(chunk.name)
|
|
1788
|
+
?? [...clientModuleByEntryName.entries()].find(([entryName]) =>
|
|
1789
|
+
toRollupSafeClientModuleRelativePath(entryName) === chunk.name,
|
|
1790
|
+
)?.[1]
|
|
1791
|
+
?? null;
|
|
1792
|
+
const isNamedEntry = chunk.isEntry && entryClientModule != null;
|
|
1732
1793
|
const clientModule = isNamedEntry
|
|
1733
|
-
?
|
|
1794
|
+
? entryClientModule
|
|
1734
1795
|
: chunk.fileName;
|
|
1735
1796
|
const transitiveMetadata = isNamedEntry
|
|
1736
1797
|
? await collectTransitiveClientModuleMetadata(projectRoot, mode, clientModule)
|
|
@@ -1939,7 +2000,7 @@ export function resolveRouteClientImports(routeResult, projectRoot, assetManifes
|
|
|
1939
2000
|
const clientModule = extension
|
|
1940
2001
|
? `${relativePath.slice(0, -extension.length)}.mjs`
|
|
1941
2002
|
: `${relativePath}.mjs`;
|
|
1942
|
-
return normalizedManifest
|
|
2003
|
+
return getClientModulePublicUrl(normalizedManifest, clientModule);
|
|
1943
2004
|
}).filter((publicUrl) => typeof publicUrl === "string"))];
|
|
1944
2005
|
}
|
|
1945
2006
|
|
package/src/compiler.js
CHANGED
|
@@ -25,7 +25,7 @@ const developmentGraphCache = new Map();
|
|
|
25
25
|
const developmentGraphDependencies = new Map();
|
|
26
26
|
const developmentModuleNamespaceCache = new Map();
|
|
27
27
|
const developmentGraphVersions = new Map();
|
|
28
|
-
const
|
|
28
|
+
const developmentUnmanagedImportWarnings = new Set();
|
|
29
29
|
|
|
30
30
|
function isRelativeSpecifier(specifier) {
|
|
31
31
|
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
@@ -70,15 +70,15 @@ function isPathOutsideProject(projectRoot, filePath) {
|
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function
|
|
73
|
+
function reportDevelopmentUnmanagedImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent) {
|
|
74
74
|
const warningKey = `${path.resolve(projectRoot)}::${sourcePath}::${resolvedImportPath}`;
|
|
75
|
-
if (
|
|
75
|
+
if (developmentUnmanagedImportWarnings.has(warningKey)) {
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
developmentUnmanagedImportWarnings.add(warningKey);
|
|
80
80
|
onDevelopmentEvent?.({
|
|
81
|
-
type: "
|
|
81
|
+
type: "unmanaged-import",
|
|
82
82
|
sourcePath,
|
|
83
83
|
resolvedImportPath,
|
|
84
84
|
});
|
|
@@ -159,7 +159,7 @@ function toOutputRelativePath(projectRoot, sourcePath) {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
return path.join(
|
|
162
|
-
"
|
|
162
|
+
"__unmanaged__",
|
|
163
163
|
...relativePath.split(path.sep).map((segment) => segment === ".." ? "__up__" : segment),
|
|
164
164
|
);
|
|
165
165
|
}
|
|
@@ -211,7 +211,7 @@ async function rewriteRelativeSpecifiers({
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
if (mode === "development" && isPathOutsideProject(projectRoot, resolvedImportPath)) {
|
|
214
|
-
|
|
214
|
+
reportDevelopmentUnmanagedImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
const importerOutputPath = toOutputPath(projectRoot, outputRoot, sourcePath);
|
|
@@ -339,9 +339,9 @@ export function invalidateDevelopmentCompilationCache(projectRoot, changedPaths
|
|
|
339
339
|
let invalidated = false;
|
|
340
340
|
|
|
341
341
|
if (normalizedChangedPaths == null) {
|
|
342
|
-
for (const warningKey of
|
|
342
|
+
for (const warningKey of developmentUnmanagedImportWarnings) {
|
|
343
343
|
if (warningKey.startsWith(prefix)) {
|
|
344
|
-
|
|
344
|
+
developmentUnmanagedImportWarnings.delete(warningKey);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
}
|
|
@@ -293,19 +293,37 @@ export async function createRequestRenderer({
|
|
|
293
293
|
...styleUrls.map((href) => `<link rel="stylesheet" href="${href}">`),
|
|
294
294
|
].join("\n");
|
|
295
295
|
},
|
|
296
|
-
resolveBootstrap({ result }) {
|
|
296
|
+
resolveBootstrap({ routeResult, result }) {
|
|
297
|
+
const routeClientImports = resolveRouteClientImports(
|
|
298
|
+
routeResult,
|
|
299
|
+
projectRoot,
|
|
300
|
+
currentAssetManifest,
|
|
301
|
+
);
|
|
297
302
|
return createHydrationBootstrap({
|
|
298
|
-
hydrationData: normalizeHydrationDataForClient(
|
|
303
|
+
hydrationData: normalizeHydrationDataForClient(
|
|
304
|
+
result.hydrationData,
|
|
305
|
+
projectRoot,
|
|
306
|
+
routeClientImports,
|
|
307
|
+
),
|
|
299
308
|
assetResolver(moduleId) {
|
|
300
309
|
return currentAssetResolver(moduleId);
|
|
301
310
|
},
|
|
302
311
|
hydrationModuleUrl: currentHydrationModuleUrl,
|
|
303
312
|
});
|
|
304
313
|
},
|
|
305
|
-
transformDocument({ result, document }) {
|
|
314
|
+
transformDocument({ routeResult, result, document }) {
|
|
315
|
+
const routeClientImports = resolveRouteClientImports(
|
|
316
|
+
routeResult,
|
|
317
|
+
projectRoot,
|
|
318
|
+
currentAssetManifest,
|
|
319
|
+
);
|
|
306
320
|
return rewriteHydrationDataScript(
|
|
307
321
|
document,
|
|
308
|
-
normalizeHydrationDataForClient(
|
|
322
|
+
normalizeHydrationDataForClient(
|
|
323
|
+
result.hydrationData,
|
|
324
|
+
projectRoot,
|
|
325
|
+
routeClientImports,
|
|
326
|
+
),
|
|
309
327
|
);
|
|
310
328
|
},
|
|
311
329
|
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { terminal } from "./terminal.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates the default development-event sink used by the CLI and dev server.
|
|
5
|
+
* Keeping presentation here lets integrations replace it with onDevelopmentEvent
|
|
6
|
+
* without losing any lifecycle, build, or request diagnostics.
|
|
7
|
+
*/
|
|
8
|
+
export function createDevelopmentEventReporter() {
|
|
9
|
+
return (event) => {
|
|
10
|
+
switch (event.type) {
|
|
11
|
+
case "server-starting":
|
|
12
|
+
console.log(`${terminal.cyan("▲")} ${terminal.bold("Evolit")} ${terminal.cyan(event.mode ?? "dev")}`);
|
|
13
|
+
break;
|
|
14
|
+
case "usage":
|
|
15
|
+
console.log(`${terminal.bold("Usage:")} evolit <directory>`);
|
|
16
|
+
console.log(` evolit ${terminal.cyan("init")} <directory>`);
|
|
17
|
+
console.log(` evolit ${terminal.cyan("<dev|build|start>")} [--port 3000]`);
|
|
18
|
+
break;
|
|
19
|
+
case "site-created":
|
|
20
|
+
console.log(`${terminal.green("✓")} Created evolit site: ${terminal.cyan(event.directory)}`);
|
|
21
|
+
break;
|
|
22
|
+
case "build-ready":
|
|
23
|
+
console.log(`${terminal.green("✓")} Built Evolit app: ${terminal.cyan(event.manifestPath)}`);
|
|
24
|
+
break;
|
|
25
|
+
case "initializing":
|
|
26
|
+
console.log(`${terminal.cyan("[evolit]")} ${terminal.yellow("○")} Preparing development runtime…`);
|
|
27
|
+
break;
|
|
28
|
+
case "vendor-runtime-ready":
|
|
29
|
+
console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Vendor runtime ready.`);
|
|
30
|
+
break;
|
|
31
|
+
case "client-assets-building":
|
|
32
|
+
console.log(`${terminal.cyan("[evolit]")} ${terminal.magenta("○")} Compiling client assets (${event.entryCount} entries)…`);
|
|
33
|
+
break;
|
|
34
|
+
case "client-assets-ready":
|
|
35
|
+
console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Client assets compiled in ${terminal.dim(`${event.durationMs}ms`)} (${event.entryCount} entries).`);
|
|
36
|
+
break;
|
|
37
|
+
case "client-assets-cache-hit":
|
|
38
|
+
console.log(`${terminal.cyan("[evolit]")} ${terminal.blue("●")} Client assets served from cache.`);
|
|
39
|
+
break;
|
|
40
|
+
case "invalidated":
|
|
41
|
+
console.log(
|
|
42
|
+
`${terminal.cyan("[evolit]")} ${terminal.yellow("△")} Source change detected${event.changedPathCount == null ? "" : ` (${event.changedPathCount} files)`}; invalidated ${event.affectedClientEntryCount} client entries.`,
|
|
43
|
+
);
|
|
44
|
+
break;
|
|
45
|
+
case "unmanaged-import":
|
|
46
|
+
console.warn(
|
|
47
|
+
`${terminal.yellow("[evolit]")} ${terminal.yellow("⚠")} Unmanaged import ${terminal.cyan(JSON.stringify(event.resolvedImportPath))} from ${terminal.cyan(JSON.stringify(event.sourcePath))} is outside the project root. Evolit does not watch it; changes may not trigger a rebuild, and it may be unavailable in another checkout or deployment. It will be emitted under ${terminal.cyan("/_evolit/static/__unmanaged__/")}.`,
|
|
48
|
+
);
|
|
49
|
+
break;
|
|
50
|
+
case "server-ready":
|
|
51
|
+
console.log(`${terminal.green("✓")} Ready on ${terminal.cyan(`http://localhost:${event.port}`)}`);
|
|
52
|
+
break;
|
|
53
|
+
case "request": {
|
|
54
|
+
const statusColor = event.status >= 500
|
|
55
|
+
? terminal.red
|
|
56
|
+
: event.status >= 400
|
|
57
|
+
? terminal.yellow
|
|
58
|
+
: event.status >= 300
|
|
59
|
+
? terminal.blue
|
|
60
|
+
: terminal.green;
|
|
61
|
+
console.log(
|
|
62
|
+
`${terminal.cyan("[evolit]")} ${terminal.dim(event.method)} ${event.pathname} ${statusColor(event.status)} in ${terminal.dim(`${event.durationMs}ms`)}${event.cacheState ? ` ${terminal.blue(`(${event.cacheState})`)}` : ""}`,
|
|
63
|
+
);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "request-error":
|
|
67
|
+
console.error(
|
|
68
|
+
`${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.method} ${event.pathname} failed`,
|
|
69
|
+
event.error,
|
|
70
|
+
);
|
|
71
|
+
break;
|
|
72
|
+
case "fatal-error":
|
|
73
|
+
console.error(
|
|
74
|
+
`${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.error instanceof Error ? event.error.stack ?? event.error.message : String(event.error)}`,
|
|
75
|
+
);
|
|
76
|
+
break;
|
|
77
|
+
case "route-error":
|
|
78
|
+
console.error(
|
|
79
|
+
`${terminal.red("[evolit]")} ${terminal.red("✖")} Route rendering failed ${terminal.dim(`(${event.digest})`)} at ${terminal.cyan(event.pathname)}`,
|
|
80
|
+
event.error,
|
|
81
|
+
);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
package/src/render.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isEvolitHttpSignal,
|
|
13
13
|
runWithRequestContext,
|
|
14
14
|
} from "./request-context.js";
|
|
15
|
-
import {
|
|
15
|
+
import { createDevelopmentEventReporter } from "./development-events.js";
|
|
16
16
|
import {
|
|
17
17
|
LITSX_COMPONENT,
|
|
18
18
|
LITSX_SERVER_COMPONENT,
|
|
@@ -90,10 +90,12 @@ function createBoundaryError(error, { mode, route, reportRouteError }) {
|
|
|
90
90
|
if (typeof reportRouteError === "function") {
|
|
91
91
|
reportRouteError(report);
|
|
92
92
|
} else {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
createDevelopmentEventReporter()({
|
|
94
|
+
type: "route-error",
|
|
95
|
+
digest,
|
|
96
|
+
pathname: route.pathname,
|
|
97
|
+
error: originalError,
|
|
98
|
+
});
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
const boundaryError = new Error("An unexpected server error occurred.");
|
package/src/route-config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export const DEFAULT_REVALIDATE_SECONDS = 60;
|
|
2
|
+
|
|
1
3
|
function normalizeRevalidatePolicy(cacheConfig) {
|
|
2
4
|
if (!cacheConfig || typeof cacheConfig !== "object" || Array.isArray(cacheConfig)) {
|
|
3
5
|
throw new Error("Expected routeConfig.cache to be an object when using revalidate mode.");
|
|
@@ -15,7 +17,7 @@ function normalizeRevalidatePolicy(cacheConfig) {
|
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export function normalizeRouteCachePolicy(routeConfig = {}) {
|
|
18
|
-
const cacheConfig = routeConfig?.cache ??
|
|
20
|
+
const cacheConfig = routeConfig?.cache ?? { revalidate: DEFAULT_REVALIDATE_SECONDS };
|
|
19
21
|
|
|
20
22
|
if (cacheConfig === "dynamic") {
|
|
21
23
|
return { mode: "dynamic" };
|
package/src/server.js
CHANGED
|
@@ -9,7 +9,7 @@ import { APP_DIRECTORY, BUILD_DIRECTORY, INTERNAL_DIRECTORY, MANIFEST_FILENAME }
|
|
|
9
9
|
import { normalizeClientAssetManifest } from "./client-assets.js";
|
|
10
10
|
import { pathExists, readJson } from "./fs-utils.js";
|
|
11
11
|
import { createDefaultRouteCacheKey, resolveResponseCacheRuntime } from "./response-cache.js";
|
|
12
|
-
import {
|
|
12
|
+
import { createDevelopmentEventReporter } from "./development-events.js";
|
|
13
13
|
|
|
14
14
|
function getPort(explicitPort) {
|
|
15
15
|
const port = explicitPort ?? process.env.PORT ?? "3000";
|
|
@@ -141,57 +141,9 @@ async function createRecursiveDirectoryWatcher(rootDirectory, onChange) {
|
|
|
141
141
|
|
|
142
142
|
async function createServer(projectRoot, mode, explicitPort, options = {}) {
|
|
143
143
|
const reportDevelopmentEvent = mode === "development"
|
|
144
|
-
? options.onDevelopmentEvent ?? (
|
|
145
|
-
switch (event.type) {
|
|
146
|
-
case "initializing":
|
|
147
|
-
console.log(`${terminal.cyan("[evolit]")} ${terminal.yellow("○")} Preparing development runtime…`);
|
|
148
|
-
break;
|
|
149
|
-
case "vendor-runtime-ready":
|
|
150
|
-
console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Vendor runtime ready.`);
|
|
151
|
-
break;
|
|
152
|
-
case "client-assets-building":
|
|
153
|
-
console.log(`${terminal.cyan("[evolit]")} ${terminal.magenta("○")} Compiling client assets (${event.entryCount} entries)…`);
|
|
154
|
-
break;
|
|
155
|
-
case "client-assets-ready":
|
|
156
|
-
console.log(`${terminal.cyan("[evolit]")} ${terminal.green("✓")} Client assets compiled in ${terminal.dim(`${event.durationMs}ms`)} (${event.entryCount} entries).`);
|
|
157
|
-
break;
|
|
158
|
-
case "client-assets-cache-hit":
|
|
159
|
-
console.log(`${terminal.cyan("[evolit]")} ${terminal.blue("●")} Client assets served from cache.`);
|
|
160
|
-
break;
|
|
161
|
-
case "invalidated":
|
|
162
|
-
console.log(
|
|
163
|
-
`${terminal.cyan("[evolit]")} ${terminal.yellow("△")} Source change detected${event.changedPathCount == null ? "" : ` (${event.changedPathCount} files)`}; invalidated ${event.affectedClientEntryCount} client entries.`,
|
|
164
|
-
);
|
|
165
|
-
break;
|
|
166
|
-
case "external-import":
|
|
167
|
-
console.warn(
|
|
168
|
-
`${terminal.yellow("[evolit]")} ${terminal.yellow("⚠")} Development import ${terminal.cyan(JSON.stringify(event.resolvedImportPath))} from ${terminal.cyan(JSON.stringify(event.sourcePath))} is outside the project root. It will be emitted under ${terminal.cyan("/_evolit/static/__external__/")}.`,
|
|
169
|
-
);
|
|
170
|
-
break;
|
|
171
|
-
case "request": {
|
|
172
|
-
const statusColor = event.status >= 500
|
|
173
|
-
? terminal.red
|
|
174
|
-
: event.status >= 400
|
|
175
|
-
? terminal.yellow
|
|
176
|
-
: event.status >= 300
|
|
177
|
-
? terminal.blue
|
|
178
|
-
: terminal.green;
|
|
179
|
-
console.log(
|
|
180
|
-
`${terminal.cyan("[evolit]")} ${terminal.dim(event.method)} ${event.pathname} ${statusColor(event.status)} in ${terminal.dim(`${event.durationMs}ms`)}${event.cacheState ? ` ${terminal.blue(`(${event.cacheState})`)}` : ""}`,
|
|
181
|
-
);
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
case "request-error":
|
|
185
|
-
console.error(
|
|
186
|
-
`${terminal.red("[evolit]")} ${terminal.red("✖")} ${event.method} ${event.pathname} failed`,
|
|
187
|
-
event.error,
|
|
188
|
-
);
|
|
189
|
-
break;
|
|
190
|
-
default:
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
})
|
|
144
|
+
? options.onDevelopmentEvent ?? createDevelopmentEventReporter()
|
|
194
145
|
: null;
|
|
146
|
+
reportDevelopmentEvent?.({ type: "server-starting" });
|
|
195
147
|
const evolitConfig = options.evolitConfig ?? await loadEvolitConfig(projectRoot);
|
|
196
148
|
const responseCacheRuntime = options.responseCacheStore
|
|
197
149
|
? {
|
|
@@ -331,6 +283,7 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
|
|
|
331
283
|
await new Promise((resolve) => {
|
|
332
284
|
server.listen(port, resolve);
|
|
333
285
|
});
|
|
286
|
+
reportDevelopmentEvent?.({ type: "server-ready", port });
|
|
334
287
|
return server;
|
|
335
288
|
},
|
|
336
289
|
async close() {
|