solidstep 0.3.0 → 0.3.2
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 +56 -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.2",
|
|
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,2FAuXX,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,8 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
395
342
|
loaderData: loaderData,
|
|
396
343
|
};
|
|
397
344
|
};
|
|
398
|
-
let routeManifest =
|
|
345
|
+
let routeManifest = null;
|
|
346
|
+
let clientManifest = null;
|
|
399
347
|
const hydrationScript = ({ nonce, }) => {
|
|
400
348
|
const script = generateHydrationScript();
|
|
401
349
|
return nonce
|
|
@@ -424,40 +372,21 @@ const handler = eventHandler(async (event) => {
|
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
// extract route params and search params
|
|
434
|
-
let params = {};
|
|
435
|
-
const searchParams = {};
|
|
436
|
-
const [pathnamePart, searchParamPart] = url.split('?');
|
|
437
|
-
if (searchParamPart) {
|
|
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
|
-
}
|
|
378
|
+
if (!clientManifest) {
|
|
379
|
+
clientManifest = getManifest('client');
|
|
458
380
|
}
|
|
381
|
+
const cspNonce = event.locals?.cspNonce;
|
|
382
|
+
const urlObj = new URL(req.url);
|
|
383
|
+
const pathnamePart = urlObj.pathname;
|
|
384
|
+
const searchParams = Object.fromEntries(urlObj.searchParams);
|
|
385
|
+
const match = matchRoute(routeManifest, pathnamePart);
|
|
386
|
+
const matched = match?.handler;
|
|
387
|
+
const params = match?.params || {};
|
|
459
388
|
if (matched && matched.type === 'route') {
|
|
460
|
-
const routeModule = await matched.handler
|
|
389
|
+
const routeModule = await getCachedModule(matched.handler);
|
|
461
390
|
const reqMethod = req.method?.toUpperCase();
|
|
462
391
|
if (reqMethod) {
|
|
463
392
|
const handler = routeModule[reqMethod];
|
|
@@ -514,10 +443,14 @@ const handler = eventHandler(async (event) => {
|
|
|
514
443
|
try {
|
|
515
444
|
if (!matched) {
|
|
516
445
|
try {
|
|
517
|
-
const
|
|
446
|
+
const match = matchRoute(routeManifest, '/');
|
|
447
|
+
const notFoundEntry = match.handler;
|
|
448
|
+
if (!notFoundEntry) {
|
|
449
|
+
throw new Error('No not-found page configured');
|
|
450
|
+
}
|
|
518
451
|
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
519
452
|
toRender: 'not-found',
|
|
520
|
-
entry:
|
|
453
|
+
entry: notFoundEntry,
|
|
521
454
|
routeParams: {},
|
|
522
455
|
searchParams: {},
|
|
523
456
|
req: req,
|
|
@@ -547,8 +480,10 @@ const handler = eventHandler(async (event) => {
|
|
|
547
480
|
}
|
|
548
481
|
}
|
|
549
482
|
else {
|
|
550
|
-
const { options } = matched
|
|
551
|
-
|
|
483
|
+
const { options } = matched
|
|
484
|
+
.mainPage.options
|
|
485
|
+
? await getCachedModule(matched.mainPage
|
|
486
|
+
.options)
|
|
552
487
|
: { options: {} };
|
|
553
488
|
if (options?.responseHeaders) {
|
|
554
489
|
const headers = options.responseHeaders;
|
|
@@ -651,7 +586,8 @@ const handler = eventHandler(async (event) => {
|
|
|
651
586
|
console.error(e1);
|
|
652
587
|
}
|
|
653
588
|
try {
|
|
654
|
-
const errorPage = matched
|
|
589
|
+
const errorPage = matched
|
|
590
|
+
.errorPage;
|
|
655
591
|
if (!errorPage) {
|
|
656
592
|
throw e1;
|
|
657
593
|
}
|
|
@@ -663,7 +599,7 @@ const handler = eventHandler(async (event) => {
|
|
|
663
599
|
req: req,
|
|
664
600
|
pageOptions: {},
|
|
665
601
|
cspNonce,
|
|
666
|
-
error: e1
|
|
602
|
+
error: e1,
|
|
667
603
|
});
|
|
668
604
|
assets.push(...documentAssets);
|
|
669
605
|
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
|
+
};
|