solidstep 0.3.0 → 0.3.1
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/package.json +70 -70
- package/server.d.ts.map +1 -1
- package/server.js +53 -120
- package/utils/cache.js +3 -3
- package/utils/path-router.d.ts +70 -0
- package/utils/path-router.d.ts.map +1 -0
- package/utils/path-router.js +97 -0
package/package.json
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "solidstep",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Next Step SolidJS Framework for building web applications.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"author": "HamzaKV <hamzakv333@gmail.com>",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/HamzaKV/solidstep.git"
|
|
10
|
-
},
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"exports": {
|
|
13
|
-
".": "./index.js",
|
|
14
|
-
"./hooks/action-state": "./utils/hooks/action-state.js",
|
|
15
|
-
"./utils/cache": "./utils/cache.js",
|
|
16
|
-
"./utils/cookies": "./utils/cookies.js",
|
|
17
|
-
"./utils/cors": "./utils/cors.js",
|
|
18
|
-
"./utils/csp": "./utils/csp.js",
|
|
19
|
-
"./utils/csrf": "./utils/csrf.js",
|
|
20
|
-
"./utils/error-handler": "./utils/error-handler.js",
|
|
21
|
-
"./utils/fetch.client": "./utils/fetch.client.js",
|
|
22
|
-
"./utils/fetch.server": "./utils/fetch.server.js",
|
|
23
|
-
"./utils/loader": "./utils/loader.js",
|
|
24
|
-
"./utils/logger": "./utils/logger.js",
|
|
25
|
-
"./utils/meta": "./utils/meta.js",
|
|
26
|
-
"./utils/redirect": "./utils/redirect.js",
|
|
27
|
-
"./utils/server-only": "./utils/server-only.js"
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"clean": "rimraf ./dist",
|
|
31
|
-
"copy-files:root": "copyfiles -u 0 README.md package.json generate/**/* LICENSE ./dist",
|
|
32
|
-
"build": "pnpm clean && tsc && pnpm copy-files:root",
|
|
33
|
-
"test:local": "pnpm build && cd ./dist && pnpm link --global",
|
|
34
|
-
"test:local:clean": "pnpm unlink && pnpm clean",
|
|
35
|
-
"roll": "pnpm build && cd dist && npm publish"
|
|
36
|
-
},
|
|
37
|
-
"keywords": [
|
|
38
|
-
"solidjs",
|
|
39
|
-
"web-development",
|
|
40
|
-
"typescript",
|
|
41
|
-
"npm",
|
|
42
|
-
"solidstep",
|
|
43
|
-
"framework"
|
|
44
|
-
],
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@vinxi/server-functions": "0.5.1",
|
|
47
|
-
"pino": "^10.1.0",
|
|
48
|
-
"seroval": "^1.3.2",
|
|
49
|
-
"seroval-plugins": "^1.3.2",
|
|
50
|
-
"undici": "^7.15.0",
|
|
51
|
-
"vite-plugin-solid": "^2.11.7"
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"copyfiles": "^2.4.1",
|
|
55
|
-
"rimraf": "^6.0.1",
|
|
56
|
-
"solid-js": "^1.9.7",
|
|
57
|
-
"typescript": "^5.8.3",
|
|
58
|
-
"vinxi": "^0.5.8"
|
|
59
|
-
},
|
|
60
|
-
"peerDependencies": {
|
|
61
|
-
"solid-js": "^1.9.7",
|
|
62
|
-
"vinxi": "^0.5.8"
|
|
63
|
-
},
|
|
64
|
-
"engines": {
|
|
65
|
-
"node": ">=20"
|
|
66
|
-
},
|
|
67
|
-
"publishConfig": {
|
|
68
|
-
"access": "public"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "solidstep",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Next Step SolidJS Framework for building web applications.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "HamzaKV <hamzakv333@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/HamzaKV/solidstep.git"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./index.js",
|
|
14
|
+
"./hooks/action-state": "./utils/hooks/action-state.js",
|
|
15
|
+
"./utils/cache": "./utils/cache.js",
|
|
16
|
+
"./utils/cookies": "./utils/cookies.js",
|
|
17
|
+
"./utils/cors": "./utils/cors.js",
|
|
18
|
+
"./utils/csp": "./utils/csp.js",
|
|
19
|
+
"./utils/csrf": "./utils/csrf.js",
|
|
20
|
+
"./utils/error-handler": "./utils/error-handler.js",
|
|
21
|
+
"./utils/fetch.client": "./utils/fetch.client.js",
|
|
22
|
+
"./utils/fetch.server": "./utils/fetch.server.js",
|
|
23
|
+
"./utils/loader": "./utils/loader.js",
|
|
24
|
+
"./utils/logger": "./utils/logger.js",
|
|
25
|
+
"./utils/meta": "./utils/meta.js",
|
|
26
|
+
"./utils/redirect": "./utils/redirect.js",
|
|
27
|
+
"./utils/server-only": "./utils/server-only.js"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"clean": "rimraf ./dist",
|
|
31
|
+
"copy-files:root": "copyfiles -u 0 README.md package.json generate/**/* LICENSE ./dist",
|
|
32
|
+
"build": "pnpm clean && tsc && pnpm copy-files:root",
|
|
33
|
+
"test:local": "pnpm build && cd ./dist && pnpm link --global",
|
|
34
|
+
"test:local:clean": "pnpm unlink && pnpm clean",
|
|
35
|
+
"roll": "pnpm build && cd dist && npm publish"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"solidjs",
|
|
39
|
+
"web-development",
|
|
40
|
+
"typescript",
|
|
41
|
+
"npm",
|
|
42
|
+
"solidstep",
|
|
43
|
+
"framework"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@vinxi/server-functions": "0.5.1",
|
|
47
|
+
"pino": "^10.1.0",
|
|
48
|
+
"seroval": "^1.3.2",
|
|
49
|
+
"seroval-plugins": "^1.3.2",
|
|
50
|
+
"undici": "^7.15.0",
|
|
51
|
+
"vite-plugin-solid": "^2.11.7"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"copyfiles": "^2.4.1",
|
|
55
|
+
"rimraf": "^6.0.1",
|
|
56
|
+
"solid-js": "^1.9.7",
|
|
57
|
+
"typescript": "^5.8.3",
|
|
58
|
+
"vinxi": "^0.5.8"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"solid-js": "^1.9.7",
|
|
62
|
+
"vinxi": "^0.5.8"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=20"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"AAkfA,QAAA,MAAM,OAAO,2FAmXX,CAAC;AAEH,eAAe,OAAO,CAAC"}
|
package/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eventHandler, toWebRequest, setHeader, setResponseStatus } from 'vinxi/http';
|
|
1
|
+
import { eventHandler, toWebRequest, setHeader, setResponseStatus, } from 'vinxi/http';
|
|
2
2
|
import { getManifest } from 'vinxi/manifest';
|
|
3
3
|
import { generateHydrationScript, renderToString } from 'solid-js/web';
|
|
4
4
|
import fileRoutes, {} from 'vinxi/routes';
|
|
@@ -8,52 +8,31 @@ import { handleServerFunction } from './utils/server-action.server';
|
|
|
8
8
|
import { readFile } from 'node:fs/promises';
|
|
9
9
|
import { dirname } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { createNode, insertRoute, matchRoute, } from './utils/path-router';
|
|
12
|
+
// Module cache for dynamically imported modules
|
|
13
|
+
const moduleCache = new Map();
|
|
14
|
+
const getCachedModule = async (importFn) => {
|
|
15
|
+
const key = importFn.src;
|
|
16
|
+
if (moduleCache.has(key)) {
|
|
17
|
+
return moduleCache.get(key);
|
|
18
|
+
}
|
|
19
|
+
const module = await importFn.import();
|
|
20
|
+
moduleCache.set(key, module);
|
|
21
|
+
return module;
|
|
22
|
+
};
|
|
11
23
|
const isPageFile = (file) => file.endsWith('page.tsx') ||
|
|
12
24
|
file.endsWith('page.jsx') ||
|
|
13
25
|
file.endsWith('page.ts') ||
|
|
14
26
|
file.endsWith('page.js');
|
|
15
27
|
const isRouteFile = (file) => file.endsWith('route.ts') || file.endsWith('route.js');
|
|
16
|
-
const parseSegment = (part) => {
|
|
17
|
-
if (part.startsWith('[[') && part.endsWith(']]')) {
|
|
18
|
-
return `:^*${part.slice(5, -2)}`;
|
|
19
|
-
}
|
|
20
|
-
return part.startsWith('[')
|
|
21
|
-
? `:${part.slice(1, -1).replace(/\.\.\./, '*')}`
|
|
22
|
-
: part;
|
|
23
|
-
};
|
|
24
|
-
const createMatcher = (routePath) => {
|
|
25
|
-
const keys = [];
|
|
26
|
-
const segments = routePath.split('/').map((part) => {
|
|
27
|
-
if (part.startsWith(':')) {
|
|
28
|
-
if (part.startsWith(':^*')) {
|
|
29
|
-
// Catch-all inclusive
|
|
30
|
-
keys.push(part.slice(3));
|
|
31
|
-
return '?(.*)?';
|
|
32
|
-
}
|
|
33
|
-
if (part.startsWith(':*')) {
|
|
34
|
-
// Catch-all
|
|
35
|
-
keys.push(part.slice(2));
|
|
36
|
-
return '.*';
|
|
37
|
-
}
|
|
38
|
-
// Standard param
|
|
39
|
-
keys.push(part.slice(1));
|
|
40
|
-
return '([^/]+)';
|
|
41
|
-
}
|
|
42
|
-
return part;
|
|
43
|
-
});
|
|
44
|
-
return {
|
|
45
|
-
regex: new RegExp(`^${segments.join('/')}$`),
|
|
46
|
-
keys,
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
28
|
const getNormalizedPath = (path, clean) => {
|
|
50
|
-
const segments = path.split('/').slice(2)
|
|
29
|
+
const segments = path.split('/').slice(2);
|
|
51
30
|
if (clean)
|
|
52
31
|
return `/${segments.filter((s) => !s.startsWith('(')).join('/')}`;
|
|
53
32
|
return `/${segments.join('/')}`;
|
|
54
33
|
};
|
|
55
34
|
const createRouteManifest = async () => {
|
|
56
|
-
const
|
|
35
|
+
const rootNode = createNode();
|
|
57
36
|
const allRoutes = [];
|
|
58
37
|
const layoutsMap = new Map();
|
|
59
38
|
const loadingPagesMap = new Map();
|
|
@@ -102,7 +81,6 @@ const createRouteManifest = async () => {
|
|
|
102
81
|
const groupName = group.path
|
|
103
82
|
.split('/')
|
|
104
83
|
.filter((s) => !s.startsWith('('))
|
|
105
|
-
.map(parseSegment)
|
|
106
84
|
.at(-1);
|
|
107
85
|
if (!groupName)
|
|
108
86
|
continue;
|
|
@@ -134,11 +112,8 @@ const createRouteManifest = async () => {
|
|
|
134
112
|
});
|
|
135
113
|
}
|
|
136
114
|
}
|
|
137
|
-
const
|
|
138
|
-
entries[routePath] = {
|
|
115
|
+
const entry = {
|
|
139
116
|
type: 'page',
|
|
140
|
-
regex: matcherRegex,
|
|
141
|
-
paramKeys: keys,
|
|
142
117
|
mainPage: {
|
|
143
118
|
manifestPath: fileRoute.path,
|
|
144
119
|
page: fileRoute.$component,
|
|
@@ -170,47 +145,19 @@ const createRouteManifest = async () => {
|
|
|
170
145
|
layouts: layouts,
|
|
171
146
|
groups: groups,
|
|
172
147
|
};
|
|
148
|
+
insertRoute(rootNode, routePath, entry);
|
|
173
149
|
}
|
|
174
150
|
else if (src && isRouteFile(src)) {
|
|
175
|
-
const
|
|
176
|
-
entries[routePath] = {
|
|
151
|
+
const entry = {
|
|
177
152
|
type: 'route',
|
|
178
|
-
|
|
179
|
-
paramKeys: keys,
|
|
153
|
+
routePath,
|
|
180
154
|
handler: fileRoute.$handler,
|
|
181
155
|
manifestPath: fileRoute.path,
|
|
182
156
|
};
|
|
157
|
+
insertRoute(rootNode, routePath, entry);
|
|
183
158
|
}
|
|
184
159
|
}
|
|
185
|
-
return
|
|
186
|
-
};
|
|
187
|
-
const extractRouteParams = (route, url) => {
|
|
188
|
-
const routeSegments = route.split('/').filter(s => !(s.startsWith('('))).filter(Boolean);
|
|
189
|
-
const urlSegments = url.split('/').filter(Boolean);
|
|
190
|
-
const params = {};
|
|
191
|
-
let matched = true;
|
|
192
|
-
for (let i = 0; i < routeSegments.length; i++) {
|
|
193
|
-
const routeSeg = routeSegments[i];
|
|
194
|
-
const urlSeg = urlSegments[i];
|
|
195
|
-
const isDynamic = routeSeg.startsWith('[') && routeSeg.endsWith(']');
|
|
196
|
-
if (isDynamic) {
|
|
197
|
-
if (routeSeg.includes('...')) {
|
|
198
|
-
// Catch-all parameter
|
|
199
|
-
const isCatchAll = routeSeg.startsWith('[[') && routeSeg.endsWith(']]');
|
|
200
|
-
const paramName = routeSeg.slice(isCatchAll ? 5 : 4, isCatchAll ? -2 : -1);
|
|
201
|
-
params[paramName] = urlSegments.slice(i);
|
|
202
|
-
break; // No more segments to match
|
|
203
|
-
}
|
|
204
|
-
const paramName = routeSeg.slice(1, -1);
|
|
205
|
-
params[paramName] = urlSeg;
|
|
206
|
-
}
|
|
207
|
-
else if (routeSeg !== urlSeg) {
|
|
208
|
-
matched = false;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (matched)
|
|
213
|
-
return { route, params };
|
|
160
|
+
return rootNode;
|
|
214
161
|
};
|
|
215
162
|
const template = `
|
|
216
163
|
<!DOCTYPE html>
|
|
@@ -244,7 +191,7 @@ const generateHtmlHead = (meta) => {
|
|
|
244
191
|
.join('\n');
|
|
245
192
|
return head;
|
|
246
193
|
};
|
|
247
|
-
const render = async ({ toRender, entry, routeParams, searchParams, req, pageOptions, cspNonce, error }) => {
|
|
194
|
+
const render = async ({ toRender, entry, routeParams, searchParams, req, pageOptions, cspNonce, error, }) => {
|
|
248
195
|
const url = new URL(req.url);
|
|
249
196
|
const path = url.pathname;
|
|
250
197
|
const cachedEntry = getCache(path);
|
|
@@ -264,12 +211,12 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
264
211
|
const moduleSrc = `${layout.layout.src}&pick=$css`;
|
|
265
212
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
266
213
|
assets.push(...moduleAssets);
|
|
267
|
-
const { default: layoutModule } = await layout.layout
|
|
214
|
+
const { default: layoutModule } = await getCachedModule(layout.layout);
|
|
268
215
|
const { loader: layoutLoader } = layout.loader
|
|
269
|
-
? await layout.loader
|
|
216
|
+
? await getCachedModule(layout.loader)
|
|
270
217
|
: { loader: null };
|
|
271
218
|
const { generateMeta: generateMetaPage } = layout.generateMeta
|
|
272
|
-
? await layout.generateMeta
|
|
219
|
+
? await getCachedModule(layout.generateMeta)
|
|
273
220
|
: { generateMeta: null };
|
|
274
221
|
let data = {};
|
|
275
222
|
if (generateMetaPage) {
|
|
@@ -299,9 +246,9 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
299
246
|
const moduleSrc = `${group.page.src}&pick=$css`;
|
|
300
247
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
301
248
|
assets.push(...moduleAssets);
|
|
302
|
-
const { default: groupPage } = await group.page
|
|
249
|
+
const { default: groupPage } = await getCachedModule(group.page);
|
|
303
250
|
const { loader: groupLoader } = group.loader
|
|
304
|
-
? await group.loader
|
|
251
|
+
? await getCachedModule(group.loader)
|
|
305
252
|
: { loader: null };
|
|
306
253
|
let data = {};
|
|
307
254
|
if (groupLoader) {
|
|
@@ -339,12 +286,12 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
339
286
|
const moduleSrc = `${pageToRender.page.src}&pick=$css`;
|
|
340
287
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
341
288
|
assets.push(...moduleAssets);
|
|
342
|
-
const { default: page } = await pageToRender.page
|
|
289
|
+
const { default: page } = await getCachedModule(pageToRender.page);
|
|
343
290
|
const { loader: pageLoader } = pageToRender.loader
|
|
344
|
-
? await pageToRender.loader
|
|
291
|
+
? await getCachedModule(pageToRender.loader)
|
|
345
292
|
: { loader: null };
|
|
346
293
|
const { generateMeta } = pageToRender.generateMeta
|
|
347
|
-
? await pageToRender.generateMeta
|
|
294
|
+
? await getCachedModule(pageToRender.generateMeta)
|
|
348
295
|
: { generateMeta: null };
|
|
349
296
|
let data = {};
|
|
350
297
|
if (pageLoader) {
|
|
@@ -395,7 +342,7 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
395
342
|
loaderData: loaderData,
|
|
396
343
|
};
|
|
397
344
|
};
|
|
398
|
-
let routeManifest =
|
|
345
|
+
let routeManifest = null;
|
|
399
346
|
const hydrationScript = ({ nonce, }) => {
|
|
400
347
|
const script = generateHydrationScript();
|
|
401
348
|
return nonce
|
|
@@ -418,46 +365,25 @@ const onStart = async () => {
|
|
|
418
365
|
}
|
|
419
366
|
};
|
|
420
367
|
onStart();
|
|
368
|
+
const clientManifest = getManifest('client');
|
|
421
369
|
const handler = eventHandler(async (event) => {
|
|
422
370
|
const req = toWebRequest(event);
|
|
423
371
|
try {
|
|
424
372
|
if (req.url?.includes('_server')) {
|
|
425
373
|
return handleServerFunction(event);
|
|
426
374
|
}
|
|
427
|
-
|
|
428
|
-
if (!routeManifest || Object.keys(routeManifest).length === 0) {
|
|
375
|
+
if (!routeManifest) {
|
|
429
376
|
routeManifest = await createRouteManifest();
|
|
430
377
|
}
|
|
431
378
|
const cspNonce = event.locals?.cspNonce;
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
for (const param of searchParamPart.split('&')) {
|
|
439
|
-
const [key, value] = param.split('=');
|
|
440
|
-
searchParams[key] = decodeURIComponent(value || '');
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
let matched;
|
|
444
|
-
for (const entry of Object.values(routeManifest)) {
|
|
445
|
-
const match = entry.regex.exec(pathnamePart);
|
|
446
|
-
if (match) {
|
|
447
|
-
matched = entry;
|
|
448
|
-
if (entry.paramKeys) {
|
|
449
|
-
const routePath = matched && matched.type === 'route'
|
|
450
|
-
? matched.manifestPath.split('/').slice(2).join('/')
|
|
451
|
-
: matched && matched.type === 'page'
|
|
452
|
-
? matched.mainPage.manifestPath.split('/').slice(2).join('/')
|
|
453
|
-
: '/';
|
|
454
|
-
params = extractRouteParams(routePath, pathnamePart)?.params || {};
|
|
455
|
-
}
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
379
|
+
const urlObj = new URL(req.url);
|
|
380
|
+
const pathnamePart = urlObj.pathname;
|
|
381
|
+
const searchParams = Object.fromEntries(urlObj.searchParams);
|
|
382
|
+
const match = matchRoute(routeManifest, pathnamePart);
|
|
383
|
+
const matched = match?.handler;
|
|
384
|
+
const params = match?.params || {};
|
|
459
385
|
if (matched && matched.type === 'route') {
|
|
460
|
-
const routeModule = await matched.handler
|
|
386
|
+
const routeModule = await getCachedModule(matched.handler);
|
|
461
387
|
const reqMethod = req.method?.toUpperCase();
|
|
462
388
|
if (reqMethod) {
|
|
463
389
|
const handler = routeModule[reqMethod];
|
|
@@ -514,10 +440,14 @@ const handler = eventHandler(async (event) => {
|
|
|
514
440
|
try {
|
|
515
441
|
if (!matched) {
|
|
516
442
|
try {
|
|
517
|
-
const
|
|
443
|
+
const match = matchRoute(routeManifest, '/');
|
|
444
|
+
const notFoundEntry = match.handler;
|
|
445
|
+
if (!notFoundEntry) {
|
|
446
|
+
throw new Error('No not-found page configured');
|
|
447
|
+
}
|
|
518
448
|
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
519
449
|
toRender: 'not-found',
|
|
520
|
-
entry:
|
|
450
|
+
entry: notFoundEntry,
|
|
521
451
|
routeParams: {},
|
|
522
452
|
searchParams: {},
|
|
523
453
|
req: req,
|
|
@@ -547,8 +477,10 @@ const handler = eventHandler(async (event) => {
|
|
|
547
477
|
}
|
|
548
478
|
}
|
|
549
479
|
else {
|
|
550
|
-
const { options } = matched
|
|
551
|
-
|
|
480
|
+
const { options } = matched
|
|
481
|
+
.mainPage.options
|
|
482
|
+
? await getCachedModule(matched.mainPage
|
|
483
|
+
.options)
|
|
552
484
|
: { options: {} };
|
|
553
485
|
if (options?.responseHeaders) {
|
|
554
486
|
const headers = options.responseHeaders;
|
|
@@ -651,7 +583,8 @@ const handler = eventHandler(async (event) => {
|
|
|
651
583
|
console.error(e1);
|
|
652
584
|
}
|
|
653
585
|
try {
|
|
654
|
-
const errorPage = matched
|
|
586
|
+
const errorPage = matched
|
|
587
|
+
.errorPage;
|
|
655
588
|
if (!errorPage) {
|
|
656
589
|
throw e1;
|
|
657
590
|
}
|
|
@@ -663,7 +596,7 @@ const handler = eventHandler(async (event) => {
|
|
|
663
596
|
req: req,
|
|
664
597
|
pageOptions: {},
|
|
665
598
|
cspNonce,
|
|
666
|
-
error: e1
|
|
599
|
+
error: e1,
|
|
667
600
|
});
|
|
668
601
|
assets.push(...documentAssets);
|
|
669
602
|
clientHydrationScript = `
|
package/utils/cache.js
CHANGED
|
@@ -39,7 +39,7 @@ export const getCache = (key) => {
|
|
|
39
39
|
const entry = cacheMap.get(key);
|
|
40
40
|
if (!entry || !entry.expiresAt)
|
|
41
41
|
return null;
|
|
42
|
-
if (entry.expiresAt && entry.expiresAt <
|
|
42
|
+
if (entry.expiresAt && entry.expiresAt < performance.now()) {
|
|
43
43
|
cacheMap.delete(key);
|
|
44
44
|
if (entry.prev)
|
|
45
45
|
entry.prev.next = entry.next;
|
|
@@ -58,14 +58,14 @@ export const setCache = (key, value, ttlMs) => {
|
|
|
58
58
|
if (cacheMap.has(key)) {
|
|
59
59
|
const node = cacheMap.get(key);
|
|
60
60
|
node.value = value;
|
|
61
|
-
node.expiresAt = ttlMs ?
|
|
61
|
+
node.expiresAt = ttlMs ? performance.now() + ttlMs : null;
|
|
62
62
|
moveToFront(node);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
const newNode = {
|
|
66
66
|
key,
|
|
67
67
|
value,
|
|
68
|
-
expiresAt: ttlMs ?
|
|
68
|
+
expiresAt: ttlMs ? performance.now() + ttlMs : null
|
|
69
69
|
};
|
|
70
70
|
newNode.next = head;
|
|
71
71
|
if (head)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type Import = {
|
|
2
|
+
src: string;
|
|
3
|
+
import: any;
|
|
4
|
+
};
|
|
5
|
+
export type RoutePageHandler = {
|
|
6
|
+
type: 'page';
|
|
7
|
+
mainPage: {
|
|
8
|
+
manifestPath: string;
|
|
9
|
+
page: Import;
|
|
10
|
+
loader?: Import;
|
|
11
|
+
generateMeta?: Import;
|
|
12
|
+
options?: Import;
|
|
13
|
+
};
|
|
14
|
+
loadingPage?: {
|
|
15
|
+
manifestPath: string;
|
|
16
|
+
page: Import;
|
|
17
|
+
generateMeta?: Import;
|
|
18
|
+
};
|
|
19
|
+
errorPage?: {
|
|
20
|
+
manifestPath: string;
|
|
21
|
+
page: Import;
|
|
22
|
+
generateMeta?: Import;
|
|
23
|
+
};
|
|
24
|
+
notFoundPage?: {
|
|
25
|
+
manifestPath: string;
|
|
26
|
+
page: Import;
|
|
27
|
+
generateMeta?: Import;
|
|
28
|
+
};
|
|
29
|
+
layouts: {
|
|
30
|
+
manifestPath: string;
|
|
31
|
+
layout: Import;
|
|
32
|
+
loader?: Import;
|
|
33
|
+
generateMeta?: Import;
|
|
34
|
+
}[];
|
|
35
|
+
groups?: {
|
|
36
|
+
[key: string]: {
|
|
37
|
+
manifestPath: string;
|
|
38
|
+
page: Import;
|
|
39
|
+
loader?: Import;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export type RouteHandler = {
|
|
44
|
+
type: 'route';
|
|
45
|
+
handler: Import;
|
|
46
|
+
manifestPath: string;
|
|
47
|
+
} | RoutePageHandler;
|
|
48
|
+
type Params = Record<string, string | string[]>;
|
|
49
|
+
export type RouteNode = {
|
|
50
|
+
staticChildren: Map<string, RouteNode>;
|
|
51
|
+
paramChild?: {
|
|
52
|
+
name: string;
|
|
53
|
+
node: RouteNode;
|
|
54
|
+
};
|
|
55
|
+
catchAllChild?: {
|
|
56
|
+
name: string;
|
|
57
|
+
optional: boolean;
|
|
58
|
+
node: RouteNode;
|
|
59
|
+
};
|
|
60
|
+
handler?: RouteHandler;
|
|
61
|
+
};
|
|
62
|
+
export declare const createNode: () => RouteNode;
|
|
63
|
+
export declare const insertRoute: (root: RouteNode, path: string, handler: RouteHandler) => void;
|
|
64
|
+
type MatchResult = {
|
|
65
|
+
handler: RouteHandler;
|
|
66
|
+
params: Params;
|
|
67
|
+
} | null;
|
|
68
|
+
export declare const matchRoute: (root: RouteNode, path: string) => MatchResult;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=path-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-router.d.ts","sourceRoot":"","sources":["../../utils/path-router.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,MAAM,GAAG;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,GAAG,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,CAAC,EAAE;QACV,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,SAAS,CAAC,EAAE;QACR,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,YAAY,CAAC,EAAE;QACX,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,OAAO,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,EAAE,CAAC;IACJ,MAAM,CAAC,EAAE;QACL,CAAC,GAAG,EAAE,MAAM,GAAG;YACX,YAAY,EAAE,MAAM,CAAC;YACrB,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;SACnB,CAAC;KACL,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,YAAY,GAClB;IACE,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACxB,GACC,gBAAgB,CAAC;AAEvB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AAEhD,MAAM,MAAM,SAAS,GAAG;IACpB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAEvC,UAAU,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,SAAS,CAAC;KACnB,CAAA;IAED,aAAa,CAAC,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,IAAI,EAAE,SAAS,CAAC;KACnB,CAAA;IAED,OAAO,CAAC,EAAE,YAAY,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,UAAU,QAAO,SAE5B,CAAC;AAyCH,eAAO,MAAM,WAAW,GACpB,MAAM,SAAS,EACf,MAAM,MAAM,EACZ,SAAS,YAAY,SAyCxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACf,OAAO,EAAE,YAAY,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;CACjB,GAAG,IAAI,CAAA;AAER,eAAO,MAAM,UAAU,GACnB,MAAM,SAAS,EACf,MAAM,MAAM,KACb,WAmDF,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export const createNode = () => ({
|
|
2
|
+
staticChildren: new Map()
|
|
3
|
+
});
|
|
4
|
+
const parseSegment = (segment) => {
|
|
5
|
+
// [[...slug]]
|
|
6
|
+
if (segment.startsWith('[[...') && segment.endsWith(']]')) {
|
|
7
|
+
return { type: 'catchAll', name: segment.slice(5, -2), optional: true };
|
|
8
|
+
}
|
|
9
|
+
// [...slug]
|
|
10
|
+
if (segment.startsWith('[...') && segment.endsWith(']')) {
|
|
11
|
+
return { type: 'catchAll', name: segment.slice(4, -1), optional: false };
|
|
12
|
+
}
|
|
13
|
+
// [id]
|
|
14
|
+
if (segment.startsWith('[') && segment.endsWith(']')) {
|
|
15
|
+
return { type: 'param', name: segment.slice(1, -1) };
|
|
16
|
+
}
|
|
17
|
+
return { type: 'static', value: segment };
|
|
18
|
+
};
|
|
19
|
+
export const insertRoute = (root, path, handler) => {
|
|
20
|
+
const segments = path.split('/').filter(Boolean);
|
|
21
|
+
let node = root;
|
|
22
|
+
for (const segment of segments) {
|
|
23
|
+
const parsed = parseSegment(segment);
|
|
24
|
+
if (parsed.type === 'static' && parsed.value) {
|
|
25
|
+
if (!node.staticChildren.has(parsed.value)) {
|
|
26
|
+
node.staticChildren.set(parsed.value, createNode());
|
|
27
|
+
}
|
|
28
|
+
node = node.staticChildren.get(parsed.value);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (parsed.type === 'param') {
|
|
32
|
+
if (!node.paramChild) {
|
|
33
|
+
node.paramChild = {
|
|
34
|
+
name: parsed.name,
|
|
35
|
+
node: createNode()
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
node = node.paramChild.node;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (parsed.type === 'catchAll') {
|
|
42
|
+
if (!node.catchAllChild) {
|
|
43
|
+
node.catchAllChild = {
|
|
44
|
+
name: parsed.name,
|
|
45
|
+
optional: parsed.optional,
|
|
46
|
+
node: createNode()
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
node = node.catchAllChild.node;
|
|
50
|
+
break; // catch-all always consumes the rest
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
node.handler = handler;
|
|
54
|
+
};
|
|
55
|
+
export const matchRoute = (root, path) => {
|
|
56
|
+
const segments = path.split('/').filter(Boolean);
|
|
57
|
+
const params = {};
|
|
58
|
+
const walk = (node, index) => {
|
|
59
|
+
// End of path
|
|
60
|
+
if (index === segments.length) {
|
|
61
|
+
if (node.handler)
|
|
62
|
+
return node.handler;
|
|
63
|
+
// Optional catch-all can match empty
|
|
64
|
+
if (node.catchAllChild?.optional) {
|
|
65
|
+
params[node.catchAllChild.name] = [];
|
|
66
|
+
return node.catchAllChild.node.handler ?? null;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const segment = segments[index];
|
|
71
|
+
// 1. Static
|
|
72
|
+
const staticChild = node.staticChildren.get(segment);
|
|
73
|
+
if (staticChild) {
|
|
74
|
+
const res = walk(staticChild, index + 1);
|
|
75
|
+
if (res)
|
|
76
|
+
return res;
|
|
77
|
+
}
|
|
78
|
+
// 2. Param
|
|
79
|
+
if (node.paramChild) {
|
|
80
|
+
params[node.paramChild.name] = segment;
|
|
81
|
+
const res = walk(node.paramChild.node, index + 1);
|
|
82
|
+
if (res)
|
|
83
|
+
return res;
|
|
84
|
+
delete params[node.paramChild.name];
|
|
85
|
+
}
|
|
86
|
+
// 3. Catch-all
|
|
87
|
+
if (node.catchAllChild) {
|
|
88
|
+
params[node.catchAllChild.name] = segments.slice(index);
|
|
89
|
+
return node.catchAllChild.node.handler ?? null;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
};
|
|
93
|
+
const handler = walk(root, 0);
|
|
94
|
+
if (!handler)
|
|
95
|
+
return null;
|
|
96
|
+
return { handler, params };
|
|
97
|
+
};
|