evolit 0.1.0-alpha.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/LICENSE +201 -0
- package/README.md +352 -0
- package/package.json +63 -0
- package/src/app-discovery.js +312 -0
- package/src/build.js +417 -0
- package/src/cli.js +83 -0
- package/src/client-assets.js +1881 -0
- package/src/compiler.js +353 -0
- package/src/config.js +20 -0
- package/src/constants.js +49 -0
- package/src/deployment-runtime.js +463 -0
- package/src/fs-utils.js +71 -0
- package/src/index.js +167 -0
- package/src/render.js +516 -0
- package/src/request-context-browser.js +11 -0
- package/src/request-context.js +230 -0
- package/src/response-cache.js +214 -0
- package/src/route-config.js +60 -0
- package/src/scaffold.js +107 -0
- package/src/server-api.js +51 -0
- package/src/server.js +230 -0
- package/src/ssr-adapter.js +189 -0
- package/templates/default/app/about/page.litsx +20 -0
- package/templates/default/app/assets.d.ts +1 -0
- package/templates/default/app/components/feature-card.litsx +35 -0
- package/templates/default/app/global.css +27 -0
- package/templates/default/app/layout.litsx +18 -0
- package/templates/default/app/page.litsx +36 -0
- package/templates/default/jsconfig.json +22 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compileModuleGraph } from "./compiler.js";
|
|
4
|
+
import {
|
|
5
|
+
collectTransitiveAssetPreloads,
|
|
6
|
+
collectTransitiveStyleUrls,
|
|
7
|
+
createAssetResolver,
|
|
8
|
+
createHydrationBootstrap,
|
|
9
|
+
createStaticAssetPublicUrlMap,
|
|
10
|
+
emitBundledClientAssetsWithState,
|
|
11
|
+
getAssetByPublicUrl,
|
|
12
|
+
getSharedOutputRoot,
|
|
13
|
+
normalizeHydrationDataForClient,
|
|
14
|
+
normalizeClientAssetManifest,
|
|
15
|
+
resolveSharedVendorModuleUrl,
|
|
16
|
+
resolveBrowserPackageAssetFilePath,
|
|
17
|
+
rewriteHydrationDataScript,
|
|
18
|
+
} from "./client-assets.js";
|
|
19
|
+
import { loadEvolitConfig } from "./config.js";
|
|
20
|
+
import { createRouteResolver } from "./render.js";
|
|
21
|
+
import {
|
|
22
|
+
createCachedRouteResponse,
|
|
23
|
+
createDefaultRouteCacheKey,
|
|
24
|
+
isCachedRouteResponseFresh,
|
|
25
|
+
resolveResponseCacheRuntime,
|
|
26
|
+
} from "./response-cache.js";
|
|
27
|
+
import { createSsrAdapter, renderRouteTreeWithAdapter } from "./ssr-adapter.js";
|
|
28
|
+
const CONTENT_TYPE_BY_EXTENSION = new Map([
|
|
29
|
+
[".css", "text/css; charset=utf-8"],
|
|
30
|
+
[".svg", "image/svg+xml"],
|
|
31
|
+
[".png", "image/png"],
|
|
32
|
+
[".jpg", "image/jpeg"],
|
|
33
|
+
[".jpeg", "image/jpeg"],
|
|
34
|
+
[".gif", "image/gif"],
|
|
35
|
+
[".webp", "image/webp"],
|
|
36
|
+
[".avif", "image/avif"],
|
|
37
|
+
[".ico", "image/x-icon"],
|
|
38
|
+
[".woff", "font/woff"],
|
|
39
|
+
[".woff2", "font/woff2"],
|
|
40
|
+
[".ttf", "font/ttf"],
|
|
41
|
+
[".otf", "font/otf"],
|
|
42
|
+
[".map", "application/json; charset=utf-8"],
|
|
43
|
+
[".mjs", "text/javascript; charset=utf-8"],
|
|
44
|
+
[".js", "text/javascript; charset=utf-8"],
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
function getContentType(filePath) {
|
|
48
|
+
return CONTENT_TYPE_BY_EXTENSION.get(path.extname(filePath)) ?? "application/octet-stream";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function applyRouteCacheHeaders(response, cachePolicy) {
|
|
52
|
+
const headers = { ...(response.headers ?? {}) };
|
|
53
|
+
|
|
54
|
+
if (cachePolicy?.mode === "static") {
|
|
55
|
+
headers["cache-control"] = "public, max-age=31536000, immutable";
|
|
56
|
+
} else if (cachePolicy?.mode === "revalidate") {
|
|
57
|
+
headers["cache-control"] = `public, max-age=${cachePolicy.ttlSeconds}, stale-while-revalidate=${cachePolicy.ttlSeconds}`;
|
|
58
|
+
} else {
|
|
59
|
+
headers["cache-control"] = "no-store";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...response,
|
|
64
|
+
headers,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createPublicAssetOrigin({ projectRoot, mode, assetManifest, getAssetManifest }) {
|
|
69
|
+
function resolveAssetManifest() {
|
|
70
|
+
return normalizeClientAssetManifest(
|
|
71
|
+
typeof getAssetManifest === "function" ? getAssetManifest() : assetManifest,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
async resolve(pathname) {
|
|
77
|
+
if (pathname.startsWith("/_evolit/static/")) {
|
|
78
|
+
const normalizedAssetManifest = resolveAssetManifest();
|
|
79
|
+
const decodedPathname = decodeURIComponent(pathname);
|
|
80
|
+
const filePath = getAssetByPublicUrl(normalizedAssetManifest, decodedPathname)?.outputPath
|
|
81
|
+
?? getAssetByPublicUrl(normalizedAssetManifest, pathname)?.outputPath
|
|
82
|
+
?? null;
|
|
83
|
+
if (!filePath) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
filePath,
|
|
89
|
+
contentType: getContentType(filePath),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (pathname.startsWith("/_evolit/shared/")) {
|
|
94
|
+
const sharedRoot = getSharedOutputRoot(projectRoot, mode);
|
|
95
|
+
const relativePath = decodeURIComponent(pathname.slice("/_evolit/shared/".length));
|
|
96
|
+
const filePath = path.join(sharedRoot, relativePath);
|
|
97
|
+
return {
|
|
98
|
+
filePath,
|
|
99
|
+
contentType: getContentType(filePath),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (pathname.startsWith("/_evolit/pkg/")) {
|
|
104
|
+
const filePath = await resolveBrowserPackageAssetFilePath(pathname);
|
|
105
|
+
if (!filePath) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
filePath,
|
|
110
|
+
contentType: getContentType(filePath),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
},
|
|
116
|
+
async read(pathname) {
|
|
117
|
+
const asset = await this.resolve(pathname);
|
|
118
|
+
if (!asset) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
status: 200,
|
|
124
|
+
headers: { "content-type": asset.contentType },
|
|
125
|
+
body: await fs.readFile(asset.filePath),
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createResponseCacheController({ responseCacheRuntime }) {
|
|
132
|
+
return {
|
|
133
|
+
async getCacheKey(request, routeResult) {
|
|
134
|
+
return responseCacheRuntime.createKey({
|
|
135
|
+
request,
|
|
136
|
+
routeResult,
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
createResponse(routeResult, response, cacheState) {
|
|
140
|
+
const nextResponse = applyRouteCacheHeaders(response, routeResult.cachePolicy);
|
|
141
|
+
if (!cacheState) {
|
|
142
|
+
return nextResponse;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
...nextResponse,
|
|
147
|
+
headers: {
|
|
148
|
+
...nextResponse.headers,
|
|
149
|
+
"x-evolit-cache": cacheState,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
async read(request, routeResult) {
|
|
154
|
+
if (routeResult.type !== "route" || routeResult.cachePolicy.mode === "dynamic") {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const cacheKey = await this.getCacheKey(request, routeResult);
|
|
159
|
+
const cachedResponse = await responseCacheRuntime.store.get(cacheKey);
|
|
160
|
+
if (!cachedResponse) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const response = this.createResponse(routeResult, {
|
|
165
|
+
status: cachedResponse.status,
|
|
166
|
+
headers: { ...cachedResponse.headers },
|
|
167
|
+
body: cachedResponse.body,
|
|
168
|
+
}, isCachedRouteResponseFresh(cachedResponse) ? "HIT" : "STALE");
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
cacheKey,
|
|
172
|
+
entry: cachedResponse,
|
|
173
|
+
fresh: isCachedRouteResponseFresh(cachedResponse),
|
|
174
|
+
response,
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
async store(request, routeResult, response, cacheKey) {
|
|
178
|
+
if (routeResult.type !== "route") {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const nextResponse = applyRouteCacheHeaders(response, routeResult.cachePolicy);
|
|
183
|
+
if (routeResult.cachePolicy.mode !== "dynamic" && nextResponse.status === 200) {
|
|
184
|
+
const effectiveCacheKey = cacheKey ?? await this.getCacheKey(request, routeResult);
|
|
185
|
+
await responseCacheRuntime.store.put(
|
|
186
|
+
effectiveCacheKey,
|
|
187
|
+
createCachedRouteResponse(nextResponse, routeResult.cachePolicy),
|
|
188
|
+
);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
},
|
|
194
|
+
async write(request, routeResult, response, options = {}) {
|
|
195
|
+
const didStore = await this.store(
|
|
196
|
+
request,
|
|
197
|
+
routeResult,
|
|
198
|
+
response,
|
|
199
|
+
options.cacheKey,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
return this.createResponse(
|
|
203
|
+
routeResult,
|
|
204
|
+
response,
|
|
205
|
+
options.cacheState ?? (didStore ? "MISS" : "SKIP"),
|
|
206
|
+
);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function createRequestRenderer({ projectRoot, mode, assetManifest, routeResolver, responseCacheRuntime }) {
|
|
212
|
+
let currentAssetManifest = normalizeClientAssetManifest(assetManifest);
|
|
213
|
+
let currentAssetResolver = createAssetResolver(projectRoot, {
|
|
214
|
+
assetManifest: currentAssetManifest,
|
|
215
|
+
});
|
|
216
|
+
let currentHydrationModuleUrl = await resolveSharedVendorModuleUrl(
|
|
217
|
+
projectRoot,
|
|
218
|
+
mode,
|
|
219
|
+
"@litsx/ssr/hydration",
|
|
220
|
+
);
|
|
221
|
+
const devBundledEntries = new Set();
|
|
222
|
+
let devRollupCache = null;
|
|
223
|
+
const usesFrameworkRouteResolver = !routeResolver;
|
|
224
|
+
async function createFrameworkRouteResolver() {
|
|
225
|
+
return createRouteResolver(projectRoot, mode, {
|
|
226
|
+
getStaticAssetPublicUrls() {
|
|
227
|
+
return createStaticAssetPublicUrlMap(currentAssetManifest);
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
let effectiveRouteResolver = routeResolver ?? await createFrameworkRouteResolver();
|
|
232
|
+
const ssrAdapter = createSsrAdapter({
|
|
233
|
+
assetResolver(moduleId) {
|
|
234
|
+
return currentAssetResolver(moduleId);
|
|
235
|
+
},
|
|
236
|
+
async resolveAdditionalHead({ result }) {
|
|
237
|
+
const clientImports = Array.isArray(result.clientImports) ? result.clientImports : [];
|
|
238
|
+
const urls = currentAssetManifest
|
|
239
|
+
? collectTransitiveAssetPreloads(clientImports, currentAssetManifest)
|
|
240
|
+
: [];
|
|
241
|
+
const styleUrls = currentAssetManifest
|
|
242
|
+
? collectTransitiveStyleUrls(clientImports, currentAssetManifest)
|
|
243
|
+
: [];
|
|
244
|
+
if (urls.length === 0 && styleUrls.length === 0) {
|
|
245
|
+
return "";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return [
|
|
249
|
+
...urls.map((href) => `<link rel="modulepreload" href="${href}">`),
|
|
250
|
+
...styleUrls.map((href) => `<link rel="stylesheet" href="${href}">`),
|
|
251
|
+
].join("\n");
|
|
252
|
+
},
|
|
253
|
+
resolveBootstrap({ result }) {
|
|
254
|
+
return createHydrationBootstrap({
|
|
255
|
+
hydrationData: normalizeHydrationDataForClient(result.hydrationData, projectRoot),
|
|
256
|
+
assetResolver(moduleId) {
|
|
257
|
+
return currentAssetResolver(moduleId);
|
|
258
|
+
},
|
|
259
|
+
hydrationModuleUrl: currentHydrationModuleUrl,
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
transformDocument({ result, document }) {
|
|
263
|
+
return rewriteHydrationDataScript(
|
|
264
|
+
document,
|
|
265
|
+
normalizeHydrationDataForClient(result.hydrationData, projectRoot),
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
const responseCacheController = createResponseCacheController({
|
|
270
|
+
responseCacheRuntime: responseCacheRuntime ?? {
|
|
271
|
+
store: { get: async () => null, put: async () => {} },
|
|
272
|
+
createKey: ({ request }) => createDefaultRouteCacheKey(request),
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
get routeResolver() {
|
|
278
|
+
return effectiveRouteResolver;
|
|
279
|
+
},
|
|
280
|
+
responseCacheController,
|
|
281
|
+
async invalidateDevelopmentState() {
|
|
282
|
+
if (mode !== "development") {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
currentAssetManifest = null;
|
|
287
|
+
currentAssetResolver = createAssetResolver(projectRoot, {
|
|
288
|
+
assetManifest: currentAssetManifest,
|
|
289
|
+
});
|
|
290
|
+
devBundledEntries.clear();
|
|
291
|
+
devRollupCache = null;
|
|
292
|
+
|
|
293
|
+
if (usesFrameworkRouteResolver) {
|
|
294
|
+
effectiveRouteResolver = await createFrameworkRouteResolver();
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
async prepareRouteClientArtifacts(routeResult) {
|
|
298
|
+
if (routeResult.type !== "route" && !routeResult.boundaryModule) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const clientModules = [
|
|
303
|
+
...(routeResult.type === "route" ? [routeResult.route.page, ...routeResult.route.layouts] : []),
|
|
304
|
+
...(routeResult.boundaryModule ? [routeResult.boundaryModule] : []),
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
for (const clientModule of new Set(clientModules)) {
|
|
308
|
+
const clientBuild = await compileModuleGraph(clientModule, {
|
|
309
|
+
projectRoot,
|
|
310
|
+
mode,
|
|
311
|
+
sourceMaps: mode === "development",
|
|
312
|
+
target: "client",
|
|
313
|
+
});
|
|
314
|
+
devBundledEntries.add(
|
|
315
|
+
path.relative(clientBuild.outputRoot, clientBuild.entrypoint).split(path.sep).join("/"),
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (mode === "development") {
|
|
320
|
+
const bundledClientAssets = await emitBundledClientAssetsWithState(projectRoot, {
|
|
321
|
+
mode: "development",
|
|
322
|
+
entryClientModules: devBundledEntries,
|
|
323
|
+
rollupCache: devRollupCache,
|
|
324
|
+
});
|
|
325
|
+
currentAssetManifest = bundledClientAssets.manifest;
|
|
326
|
+
currentAssetResolver = createAssetResolver(projectRoot, {
|
|
327
|
+
assetManifest: currentAssetManifest,
|
|
328
|
+
});
|
|
329
|
+
currentHydrationModuleUrl = await resolveSharedVendorModuleUrl(
|
|
330
|
+
projectRoot,
|
|
331
|
+
mode,
|
|
332
|
+
"@litsx/ssr/hydration",
|
|
333
|
+
{
|
|
334
|
+
assetManifest: currentAssetManifest,
|
|
335
|
+
entryClientModules: [...devBundledEntries],
|
|
336
|
+
},
|
|
337
|
+
) ?? currentHydrationModuleUrl;
|
|
338
|
+
devRollupCache = bundledClientAssets.rollupCache;
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
async resolveRoutePolicy(request) {
|
|
342
|
+
return effectiveRouteResolver.resolveRoutePolicy(request);
|
|
343
|
+
},
|
|
344
|
+
async renderRoute(request, routePolicyResult = null) {
|
|
345
|
+
const shouldPrepareBeforeResolve = mode === "development" && !currentAssetManifest;
|
|
346
|
+
if (shouldPrepareBeforeResolve) {
|
|
347
|
+
const resolvedRoutePolicyResult = routePolicyResult ?? await effectiveRouteResolver.resolveRoutePolicy(request);
|
|
348
|
+
await this.prepareRouteClientArtifacts(resolvedRoutePolicyResult);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const routeResult = await effectiveRouteResolver.resolveRequest(request);
|
|
352
|
+
if (!shouldPrepareBeforeResolve || routeResult.boundaryModule) {
|
|
353
|
+
await this.prepareRouteClientArtifacts(routeResult);
|
|
354
|
+
}
|
|
355
|
+
const response = await renderRouteTreeWithAdapter(routeResult, ssrAdapter);
|
|
356
|
+
return {
|
|
357
|
+
routeResult,
|
|
358
|
+
response,
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
get assetManifest() {
|
|
362
|
+
return currentAssetManifest;
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export async function createDeploymentRuntime({
|
|
368
|
+
projectRoot,
|
|
369
|
+
mode,
|
|
370
|
+
assetManifest,
|
|
371
|
+
responseCacheRuntime,
|
|
372
|
+
routeResolver,
|
|
373
|
+
} = {}) {
|
|
374
|
+
const effectiveResponseCacheRuntime = responseCacheRuntime
|
|
375
|
+
?? await resolveResponseCacheRuntime(
|
|
376
|
+
projectRoot,
|
|
377
|
+
mode,
|
|
378
|
+
await loadEvolitConfig(projectRoot),
|
|
379
|
+
);
|
|
380
|
+
const runtimeState = {
|
|
381
|
+
assetManifest: normalizeClientAssetManifest(assetManifest),
|
|
382
|
+
};
|
|
383
|
+
const assets = createPublicAssetOrigin({
|
|
384
|
+
projectRoot,
|
|
385
|
+
mode,
|
|
386
|
+
getAssetManifest() {
|
|
387
|
+
return runtimeState.assetManifest;
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
const renderer = await createRequestRenderer({
|
|
391
|
+
projectRoot,
|
|
392
|
+
mode,
|
|
393
|
+
assetManifest: runtimeState.assetManifest,
|
|
394
|
+
responseCacheRuntime: effectiveResponseCacheRuntime,
|
|
395
|
+
routeResolver,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
assets,
|
|
400
|
+
cache: renderer.responseCacheController,
|
|
401
|
+
renderer,
|
|
402
|
+
async invalidateDevelopmentState() {
|
|
403
|
+
if (mode !== "development") {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await renderer.invalidateDevelopmentState();
|
|
408
|
+
if (typeof effectiveResponseCacheRuntime.store.clear === "function") {
|
|
409
|
+
await effectiveResponseCacheRuntime.store.clear();
|
|
410
|
+
}
|
|
411
|
+
runtimeState.assetManifest = null;
|
|
412
|
+
},
|
|
413
|
+
async handle(request) {
|
|
414
|
+
const revalidationTasks = this.revalidationTasks ??= new Map();
|
|
415
|
+
const pathname = new URL(request.url).pathname;
|
|
416
|
+
const assetResponse = await assets.read(pathname);
|
|
417
|
+
if (assetResponse) {
|
|
418
|
+
return assetResponse;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const routePolicyResult = await renderer.resolveRoutePolicy(request);
|
|
422
|
+
const cachedResponse = await renderer.responseCacheController.read(request, routePolicyResult);
|
|
423
|
+
if (cachedResponse) {
|
|
424
|
+
if (cachedResponse.fresh) {
|
|
425
|
+
return cachedResponse.response;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
routePolicyResult.type === "route"
|
|
430
|
+
&& routePolicyResult.cachePolicy.mode === "revalidate"
|
|
431
|
+
&& !revalidationTasks.has(cachedResponse.cacheKey)
|
|
432
|
+
) {
|
|
433
|
+
const revalidationTask = (async () => {
|
|
434
|
+
const { routeResult, response } = await renderer.renderRoute(request, routePolicyResult);
|
|
435
|
+
await renderer.responseCacheController.store(
|
|
436
|
+
request,
|
|
437
|
+
routeResult,
|
|
438
|
+
response,
|
|
439
|
+
cachedResponse.cacheKey,
|
|
440
|
+
);
|
|
441
|
+
})()
|
|
442
|
+
.catch(() => {})
|
|
443
|
+
.finally(() => {
|
|
444
|
+
revalidationTasks.delete(cachedResponse.cacheKey);
|
|
445
|
+
});
|
|
446
|
+
revalidationTasks.set(cachedResponse.cacheKey, revalidationTask);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return cachedResponse.response;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const { routeResult, response } = await renderer.renderRoute(request, routePolicyResult);
|
|
453
|
+
runtimeState.assetManifest = normalizeClientAssetManifest(renderer.assetManifest);
|
|
454
|
+
return renderer.responseCacheController.write(request, routeResult, response);
|
|
455
|
+
},
|
|
456
|
+
async close() {
|
|
457
|
+
const revalidationTasks = this.revalidationTasks;
|
|
458
|
+
if (revalidationTasks) {
|
|
459
|
+
await Promise.all(revalidationTasks.values());
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
package/src/fs-utils.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function pathExists(targetPath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(targetPath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function ensureDirectory(targetPath) {
|
|
14
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function readJson(targetPath) {
|
|
18
|
+
return JSON.parse(await fs.readFile(targetPath, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function writeJson(targetPath, value) {
|
|
22
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
23
|
+
await fs.writeFile(targetPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function walkFiles(rootDirectory) {
|
|
27
|
+
const files = [];
|
|
28
|
+
|
|
29
|
+
async function walk(currentDirectory) {
|
|
30
|
+
const entries = await fs.readdir(currentDirectory, { withFileTypes: true });
|
|
31
|
+
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const fullPath = path.join(currentDirectory, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
await walk(fullPath);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (entry.isFile()) {
|
|
40
|
+
files.push(fullPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!(await pathExists(rootDirectory))) {
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await walk(rootDirectory);
|
|
50
|
+
return files.sort();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function copyDirectory(sourceDirectory, targetDirectory) {
|
|
54
|
+
await ensureDirectory(targetDirectory);
|
|
55
|
+
const entries = await fs.readdir(sourceDirectory, { withFileTypes: true });
|
|
56
|
+
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const sourcePath = path.join(sourceDirectory, entry.name);
|
|
59
|
+
const targetPath = path.join(targetDirectory, entry.name);
|
|
60
|
+
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
await copyDirectory(sourcePath, targetPath);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (entry.isFile()) {
|
|
67
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
68
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds an application into `.evolit/build`, including server output, browser assets, and
|
|
3
|
+
* platform-neutral deployment manifests.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} projectRoot Absolute path to the application root.
|
|
6
|
+
* @returns {Promise<object>} The build manifest.
|
|
7
|
+
*/
|
|
8
|
+
export { buildProject } from "./build.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates the request runtime used by standalone Node servers and deployment adapters.
|
|
12
|
+
* It resolves public assets, cached responses, and render misses through one `handle()` method.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} options Runtime configuration, including `projectRoot` and `mode`.
|
|
15
|
+
* @returns {Promise<object>} A runtime with `handle(request)` and development invalidation hooks.
|
|
16
|
+
*/
|
|
17
|
+
export { createDeploymentRuntime } from "./deployment-runtime.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a development HTTP server with file watching and request-time compilation.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} projectRoot Absolute path to the application root.
|
|
23
|
+
* @param {object} [options] Server options, including `port`.
|
|
24
|
+
* @returns {Promise<object>} A server controller with `listen()` and `close()`.
|
|
25
|
+
*/
|
|
26
|
+
export { createDevServer } from "./server.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a production HTTP server from existing `.evolit/build` output.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} projectRoot Absolute path to the application root.
|
|
32
|
+
* @param {object} [options] Server options, including `port`.
|
|
33
|
+
* @returns {Promise<object>} A server controller with `listen()` and `close()`.
|
|
34
|
+
*/
|
|
35
|
+
export { createStartServer } from "./server.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Discovers page routes, layouts, and route boundaries below an application's `app` directory.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} projectRoot Absolute path to the application root.
|
|
41
|
+
* @returns {Promise<Array<object>>} Discovered route definitions.
|
|
42
|
+
*/
|
|
43
|
+
export { discoverAppRoutes } from "./app-discovery.js";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Discovers HTTP route handler modules below an application's `app` directory.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} projectRoot Absolute path to the application root.
|
|
49
|
+
* @returns {Promise<Array<object>>} Discovered handler definitions.
|
|
50
|
+
*/
|
|
51
|
+
export { discoverAppRouteHandlers } from "./app-discovery.js";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a starter Evolit application in an empty directory.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} targetDirectory Directory to scaffold.
|
|
57
|
+
* @param {object} [options] Scaffold options, including `name`.
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
export { scaffoldSite } from "./scaffold.js";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates the default LitSX SSR adapter used to turn route results into HTTP responses.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} [options] Document metadata and rendering options.
|
|
66
|
+
* @returns {object} An adapter with `renderRouteTree(routeResult)`.
|
|
67
|
+
*/
|
|
68
|
+
export { createSsrAdapter } from "./ssr-adapter.js";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Renders a resolved route result through an SSR adapter.
|
|
72
|
+
*
|
|
73
|
+
* @param {object} routeResult A result returned by the route resolver.
|
|
74
|
+
* @param {object} [adapter] SSR adapter. Defaults to `createSsrAdapter()`.
|
|
75
|
+
* @returns {Promise<{ status: number, headers: object, body: string|ReadableStream }>} HTTP response data.
|
|
76
|
+
*/
|
|
77
|
+
export { renderRouteTreeWithAdapter } from "./ssr-adapter.js";
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Reads request cookies and exposes mutations for the outgoing response.
|
|
81
|
+
* Only available while rendering a route or executing a route handler.
|
|
82
|
+
*
|
|
83
|
+
* @returns {object} A cookie store with `get`, `getAll`, `has`, `set`, and `delete`.
|
|
84
|
+
*/
|
|
85
|
+
export { cookies } from "./request-context.js";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Returns the incoming request headers. Reading headers makes the current route dynamic.
|
|
89
|
+
*
|
|
90
|
+
* @returns {Headers} The incoming request headers.
|
|
91
|
+
*/
|
|
92
|
+
export { headers } from "./request-context.js";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Signals a 404 response and renders the nearest `not-found.litsx` boundary when available.
|
|
96
|
+
*
|
|
97
|
+
* @returns {never}
|
|
98
|
+
*/
|
|
99
|
+
export { notFound } from "./request-context.js";
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Signals a permanent redirect with HTTP status 308.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} location Redirect destination.
|
|
105
|
+
* @returns {never}
|
|
106
|
+
*/
|
|
107
|
+
export { permanentRedirect } from "./request-context.js";
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Signals a redirect response. The default status is 307.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} location Redirect destination.
|
|
113
|
+
* @param {number} [status=307] Redirect HTTP status.
|
|
114
|
+
* @returns {never}
|
|
115
|
+
*/
|
|
116
|
+
export { redirect } from "./request-context.js";
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns the incoming request URL. Reading it makes the current route dynamic.
|
|
120
|
+
*
|
|
121
|
+
* @returns {URL} The incoming request URL.
|
|
122
|
+
*/
|
|
123
|
+
export { requestUrl } from "./request-context.js";
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Returns mutable response headers for the current route response.
|
|
127
|
+
*
|
|
128
|
+
* @returns {Headers} Headers merged into the outgoing response.
|
|
129
|
+
*/
|
|
130
|
+
export { responseHeaders } from "./request-context.js";
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* In-memory response cache store, intended for development and tests.
|
|
134
|
+
*/
|
|
135
|
+
export { MemoryResponseCacheStore } from "./response-cache.js";
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Filesystem-backed response cache store used by the standalone production runtime.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} rootDirectory Directory where cache records are stored.
|
|
141
|
+
*/
|
|
142
|
+
export { FileSystemResponseCacheStore } from "./response-cache.js";
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Response cache store backed by caller-provided object-storage operations.
|
|
146
|
+
*
|
|
147
|
+
* @param {object} options Object storage callbacks and an optional key prefix.
|
|
148
|
+
*/
|
|
149
|
+
export { ObjectStorageResponseCacheStore } from "./response-cache.js";
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Creates the default cache key from a request pathname and search string.
|
|
153
|
+
*
|
|
154
|
+
* @param {Request} request Incoming request.
|
|
155
|
+
* @returns {string} Cache key.
|
|
156
|
+
*/
|
|
157
|
+
export { createDefaultRouteCacheKey } from "./response-cache.js";
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resolves the response cache store and cache-key factory from `evolit.config.js`.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} projectRoot Absolute path to the application root.
|
|
163
|
+
* @param {"development"|"production"} mode Runtime mode.
|
|
164
|
+
* @param {object} [evolitConfig] Loaded framework configuration.
|
|
165
|
+
* @returns {Promise<{ store: object, createKey: Function }>} Cache runtime hooks.
|
|
166
|
+
*/
|
|
167
|
+
export { resolveResponseCacheRuntime } from "./response-cache.js";
|