@pyreon/router 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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +235 -93
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +235 -91
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index2.d.ts +1 -1
- package/lib/types/index2.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/match.ts +408 -71
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"ffb5fd41-1","name":"loader.ts"},{"uid":"ffb5fd41-3","name":"match.ts"},{"uid":"ffb5fd41-5","name":"scroll.ts"},{"uid":"ffb5fd41-7","name":"types.ts"},{"uid":"ffb5fd41-9","name":"router.ts"},{"uid":"ffb5fd41-11","name":"components.tsx"},{"uid":"ffb5fd41-13","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"ffb5fd41-1":{"renderedLength":2855,"gzipLength":1243,"brotliLength":0,"metaUid":"ffb5fd41-0"},"ffb5fd41-3":{"renderedLength":10804,"gzipLength":3328,"brotliLength":0,"metaUid":"ffb5fd41-2"},"ffb5fd41-5":{"renderedLength":1367,"gzipLength":576,"brotliLength":0,"metaUid":"ffb5fd41-4"},"ffb5fd41-7":{"renderedLength":385,"gzipLength":246,"brotliLength":0,"metaUid":"ffb5fd41-6"},"ffb5fd41-9":{"renderedLength":8965,"gzipLength":2657,"brotliLength":0,"metaUid":"ffb5fd41-8"},"ffb5fd41-11":{"renderedLength":6631,"gzipLength":2488,"brotliLength":0,"metaUid":"ffb5fd41-10"},"ffb5fd41-13":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"ffb5fd41-12"}},"nodeMetas":{"ffb5fd41-0":{"id":"/src/loader.ts","moduleParts":{"index.js":"ffb5fd41-1"},"imported":[{"uid":"ffb5fd41-14"}],"importedBy":[{"uid":"ffb5fd41-12"},{"uid":"ffb5fd41-10"}]},"ffb5fd41-2":{"id":"/src/match.ts","moduleParts":{"index.js":"ffb5fd41-3"},"imported":[],"importedBy":[{"uid":"ffb5fd41-12"},{"uid":"ffb5fd41-8"}]},"ffb5fd41-4":{"id":"/src/scroll.ts","moduleParts":{"index.js":"ffb5fd41-5"},"imported":[],"importedBy":[{"uid":"ffb5fd41-8"}]},"ffb5fd41-6":{"id":"/src/types.ts","moduleParts":{"index.js":"ffb5fd41-7"},"imported":[],"importedBy":[{"uid":"ffb5fd41-12"},{"uid":"ffb5fd41-8"}]},"ffb5fd41-8":{"id":"/src/router.ts","moduleParts":{"index.js":"ffb5fd41-9"},"imported":[{"uid":"ffb5fd41-14"},{"uid":"ffb5fd41-15"},{"uid":"ffb5fd41-2"},{"uid":"ffb5fd41-4"},{"uid":"ffb5fd41-6"}],"importedBy":[{"uid":"ffb5fd41-12"},{"uid":"ffb5fd41-10"}]},"ffb5fd41-10":{"id":"/src/components.tsx","moduleParts":{"index.js":"ffb5fd41-11"},"imported":[{"uid":"ffb5fd41-14"},{"uid":"ffb5fd41-0"},{"uid":"ffb5fd41-8"}],"importedBy":[{"uid":"ffb5fd41-12"}]},"ffb5fd41-12":{"id":"/src/index.ts","moduleParts":{"index.js":"ffb5fd41-13"},"imported":[{"uid":"ffb5fd41-10"},{"uid":"ffb5fd41-0"},{"uid":"ffb5fd41-2"},{"uid":"ffb5fd41-8"},{"uid":"ffb5fd41-6"}],"importedBy":[],"isEntry":true},"ffb5fd41-14":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"ffb5fd41-10"},{"uid":"ffb5fd41-0"},{"uid":"ffb5fd41-8"}]},"ffb5fd41-15":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"ffb5fd41-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -135,69 +135,213 @@ function stringifyQuery(query) {
|
|
|
135
135
|
for (const [k, v] of Object.entries(query)) parts.push(v ? `${encodeURIComponent(k)}=${encodeURIComponent(v)}` : encodeURIComponent(k));
|
|
136
136
|
return parts.length ? `?${parts.join("&")}` : "";
|
|
137
137
|
}
|
|
138
|
+
/** WeakMap cache: compile each RouteRecord[] once */
|
|
139
|
+
const _compiledCache = /* @__PURE__ */ new WeakMap();
|
|
140
|
+
function compileSegment(raw) {
|
|
141
|
+
if (raw.endsWith("*") && raw.startsWith(":")) return {
|
|
142
|
+
raw,
|
|
143
|
+
isParam: true,
|
|
144
|
+
isSplat: true,
|
|
145
|
+
paramName: raw.slice(1, -1)
|
|
146
|
+
};
|
|
147
|
+
if (raw.startsWith(":")) return {
|
|
148
|
+
raw,
|
|
149
|
+
isParam: true,
|
|
150
|
+
isSplat: false,
|
|
151
|
+
paramName: raw.slice(1)
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
raw,
|
|
155
|
+
isParam: false,
|
|
156
|
+
isSplat: false,
|
|
157
|
+
paramName: ""
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function compileRoute(route) {
|
|
161
|
+
const pattern = route.path;
|
|
162
|
+
if (pattern === "(.*)" || pattern === "*") return {
|
|
163
|
+
route,
|
|
164
|
+
isWildcard: true,
|
|
165
|
+
segments: [],
|
|
166
|
+
segmentCount: 0,
|
|
167
|
+
isStatic: false,
|
|
168
|
+
staticPath: null,
|
|
169
|
+
children: null,
|
|
170
|
+
firstSegment: null
|
|
171
|
+
};
|
|
172
|
+
const segments = pattern.split("/").filter(Boolean).map(compileSegment);
|
|
173
|
+
const isStatic = segments.every((s) => !s.isParam);
|
|
174
|
+
const staticPath = isStatic ? `/${segments.map((s) => s.raw).join("/")}` : null;
|
|
175
|
+
const first = segments.length > 0 ? segments[0] : void 0;
|
|
176
|
+
const firstSegment = first && !first.isParam ? first.raw : null;
|
|
177
|
+
return {
|
|
178
|
+
route,
|
|
179
|
+
isWildcard: false,
|
|
180
|
+
segments,
|
|
181
|
+
segmentCount: segments.length,
|
|
182
|
+
isStatic,
|
|
183
|
+
staticPath,
|
|
184
|
+
children: null,
|
|
185
|
+
firstSegment
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function compileRoutes(routes) {
|
|
189
|
+
const cached = _compiledCache.get(routes);
|
|
190
|
+
if (cached) return cached;
|
|
191
|
+
const compiled = routes.map((r) => {
|
|
192
|
+
const c = compileRoute(r);
|
|
193
|
+
if (r.children && r.children.length > 0) c.children = compileRoutes(r.children);
|
|
194
|
+
return c;
|
|
195
|
+
});
|
|
196
|
+
_compiledCache.set(routes, compiled);
|
|
197
|
+
return compiled;
|
|
198
|
+
}
|
|
199
|
+
/** Extract first static segment from a segment list, or null if dynamic/empty */
|
|
200
|
+
function getFirstSegment(segments) {
|
|
201
|
+
const first = segments[0];
|
|
202
|
+
if (first && !first.isParam) return first.raw;
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
/** Build a FlattenedRoute from segments + metadata */
|
|
206
|
+
function makeFlatEntry(segments, chain, meta, isWildcard) {
|
|
207
|
+
const isStatic = !isWildcard && segments.every((s) => !s.isParam);
|
|
208
|
+
return {
|
|
209
|
+
segments,
|
|
210
|
+
segmentCount: segments.length,
|
|
211
|
+
matchedChain: chain,
|
|
212
|
+
isStatic,
|
|
213
|
+
staticPath: isStatic ? `/${segments.map((s) => s.raw).join("/")}` : null,
|
|
214
|
+
meta,
|
|
215
|
+
firstSegment: getFirstSegment(segments),
|
|
216
|
+
hasSplat: segments.some((s) => s.isSplat),
|
|
217
|
+
isWildcard
|
|
218
|
+
};
|
|
219
|
+
}
|
|
138
220
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* Supports:
|
|
143
|
-
* - Exact segments: "/about"
|
|
144
|
-
* - Param segments: "/user/:id"
|
|
145
|
-
* - Wildcard: "(.*)" matches everything
|
|
221
|
+
* Flatten nested routes into leaf entries with pre-joined segments.
|
|
222
|
+
* This eliminates recursion during matching for the common case.
|
|
146
223
|
*/
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
224
|
+
function flattenRoutes(compiled) {
|
|
225
|
+
const result = [];
|
|
226
|
+
flattenWalk(result, compiled, [], [], {});
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
function flattenWalk(result, routes, parentSegments, parentChain, parentMeta) {
|
|
230
|
+
for (const c of routes) flattenOne(result, c, parentSegments, [...parentChain, c.route], c.route.meta ? {
|
|
231
|
+
...parentMeta,
|
|
232
|
+
...c.route.meta
|
|
233
|
+
} : { ...parentMeta });
|
|
234
|
+
}
|
|
235
|
+
function flattenOne(result, c, parentSegments, chain, meta) {
|
|
236
|
+
if (c.isWildcard) {
|
|
237
|
+
result.push(makeFlatEntry(parentSegments, chain, meta, true));
|
|
238
|
+
if (c.children && c.children.length > 0) flattenWalk(result, c.children, parentSegments, chain, meta);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const joined = [...parentSegments, ...c.segments];
|
|
242
|
+
if (c.children && c.children.length > 0) flattenWalk(result, c.children, joined, chain, meta);
|
|
243
|
+
result.push(makeFlatEntry(joined, chain, meta, false));
|
|
244
|
+
}
|
|
245
|
+
const _indexCache = /* @__PURE__ */ new WeakMap();
|
|
246
|
+
/** Classify a single flattened route into the appropriate index bucket */
|
|
247
|
+
function indexFlatRoute(f, staticMap, segmentMap, dynamicFirst, wildcards) {
|
|
248
|
+
if (f.isStatic && f.staticPath && !staticMap.has(f.staticPath)) staticMap.set(f.staticPath, f);
|
|
249
|
+
if (f.isWildcard) {
|
|
250
|
+
wildcards.push(f);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (f.segmentCount === 0) return;
|
|
254
|
+
if (f.firstSegment) {
|
|
255
|
+
let bucket = segmentMap.get(f.firstSegment);
|
|
256
|
+
if (!bucket) {
|
|
257
|
+
bucket = [];
|
|
258
|
+
segmentMap.set(f.firstSegment, bucket);
|
|
259
|
+
}
|
|
260
|
+
bucket.push(f);
|
|
261
|
+
} else dynamicFirst.push(f);
|
|
262
|
+
}
|
|
263
|
+
function buildRouteIndex(routes, compiled) {
|
|
264
|
+
const cached = _indexCache.get(routes);
|
|
265
|
+
if (cached) return cached;
|
|
266
|
+
const flattened = flattenRoutes(compiled);
|
|
267
|
+
const staticMap = /* @__PURE__ */ new Map();
|
|
268
|
+
const segmentMap = /* @__PURE__ */ new Map();
|
|
269
|
+
const dynamicFirst = [];
|
|
270
|
+
const wildcards = [];
|
|
271
|
+
for (const f of flattened) indexFlatRoute(f, staticMap, segmentMap, dynamicFirst, wildcards);
|
|
272
|
+
const index = {
|
|
273
|
+
staticMap,
|
|
274
|
+
segmentMap,
|
|
275
|
+
dynamicFirst,
|
|
276
|
+
wildcards
|
|
277
|
+
};
|
|
278
|
+
_indexCache.set(routes, index);
|
|
279
|
+
return index;
|
|
280
|
+
}
|
|
281
|
+
/** Split path into segments without allocating a filtered array */
|
|
282
|
+
function splitPath(path) {
|
|
283
|
+
if (path === "/") return [];
|
|
284
|
+
const start = path.charCodeAt(0) === 47 ? 1 : 0;
|
|
285
|
+
const end = path.length;
|
|
286
|
+
if (start >= end) return [];
|
|
287
|
+
const parts = [];
|
|
288
|
+
let segStart = start;
|
|
289
|
+
for (let i = start; i <= end; i++) if (i === end || path.charCodeAt(i) === 47) {
|
|
290
|
+
if (i > segStart) parts.push(path.substring(segStart, i));
|
|
291
|
+
segStart = i + 1;
|
|
292
|
+
}
|
|
293
|
+
return parts;
|
|
294
|
+
}
|
|
295
|
+
/** Decode only if the segment contains a `%` character */
|
|
296
|
+
function decodeSafe(s) {
|
|
297
|
+
return s.indexOf("%") >= 0 ? decodeURIComponent(s) : s;
|
|
298
|
+
}
|
|
299
|
+
/** Collect remaining path segments as a decoded splat value */
|
|
300
|
+
function captureSplat(pathParts, from, pathLen) {
|
|
301
|
+
const remaining = [];
|
|
302
|
+
for (let j = from; j < pathLen; j++) {
|
|
303
|
+
const p = pathParts[j];
|
|
304
|
+
if (p !== void 0) remaining.push(decodeSafe(p));
|
|
305
|
+
}
|
|
306
|
+
return remaining.join("/");
|
|
307
|
+
}
|
|
308
|
+
/** Try to match a flattened route against path parts */
|
|
309
|
+
function matchFlattened(f, pathParts, pathLen) {
|
|
310
|
+
if (f.segmentCount !== pathLen) {
|
|
311
|
+
if (!f.hasSplat || pathLen < f.segmentCount) return null;
|
|
312
|
+
}
|
|
151
313
|
const params = {};
|
|
152
|
-
|
|
153
|
-
|
|
314
|
+
const segments = f.segments;
|
|
315
|
+
const count = f.segmentCount;
|
|
316
|
+
for (let i = 0; i < count; i++) {
|
|
317
|
+
const seg = segments[i];
|
|
154
318
|
const pt = pathParts[i];
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
params[paramName] = pathParts
|
|
319
|
+
if (!seg || pt === void 0) return null;
|
|
320
|
+
if (seg.isSplat) {
|
|
321
|
+
params[seg.paramName] = captureSplat(pathParts, i, pathLen);
|
|
158
322
|
return params;
|
|
159
323
|
}
|
|
160
|
-
if (
|
|
161
|
-
else if (
|
|
324
|
+
if (seg.isParam) params[seg.paramName] = decodeSafe(pt);
|
|
325
|
+
else if (seg.raw !== pt) return null;
|
|
162
326
|
}
|
|
163
|
-
if (patternParts.length !== pathParts.length) return null;
|
|
164
327
|
return params;
|
|
165
328
|
}
|
|
166
|
-
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
params
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const pathParts = path.split("/").filter(Boolean);
|
|
177
|
-
if (pathParts.length < patternParts.length) return null;
|
|
178
|
-
const params = {};
|
|
179
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
180
|
-
const pp = patternParts[i];
|
|
181
|
-
const pt = pathParts[i];
|
|
182
|
-
if (pp.endsWith("*") && pp.startsWith(":")) {
|
|
183
|
-
const paramName = pp.slice(1, -1);
|
|
184
|
-
params[paramName] = pathParts.slice(i).map(decodeURIComponent).join("/");
|
|
185
|
-
return {
|
|
186
|
-
params,
|
|
187
|
-
rest: "/"
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
if (pp.startsWith(":")) params[pp.slice(1)] = decodeURIComponent(pt);
|
|
191
|
-
else if (pp !== pt) return null;
|
|
329
|
+
/** Search a list of flattened candidates for a match */
|
|
330
|
+
function searchCandidates(candidates, pathParts, pathLen) {
|
|
331
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
332
|
+
const f = candidates[i];
|
|
333
|
+
if (!f) continue;
|
|
334
|
+
const params = matchFlattened(f, pathParts, pathLen);
|
|
335
|
+
if (params) return {
|
|
336
|
+
params,
|
|
337
|
+
matched: f.matchedChain
|
|
338
|
+
};
|
|
192
339
|
}
|
|
193
|
-
return
|
|
194
|
-
params,
|
|
195
|
-
rest: `/${pathParts.slice(patternParts.length).join("/")}`
|
|
196
|
-
};
|
|
340
|
+
return null;
|
|
197
341
|
}
|
|
198
342
|
/**
|
|
199
343
|
* Resolve a raw path (including query string and hash) against the route tree.
|
|
200
|
-
*
|
|
344
|
+
* Uses flattened index for O(1) static lookup and first-segment dispatch.
|
|
201
345
|
*/
|
|
202
346
|
function resolveRoute(rawPath, routes) {
|
|
203
347
|
const qIdx = rawPath.indexOf("?");
|
|
@@ -207,14 +351,50 @@ function resolveRoute(rawPath, routes) {
|
|
|
207
351
|
const cleanPath = hIdx >= 0 ? pathAndHash.slice(0, hIdx) : pathAndHash;
|
|
208
352
|
const hash = hIdx >= 0 ? pathAndHash.slice(hIdx + 1) : "";
|
|
209
353
|
const query = parseQuery(queryPart);
|
|
210
|
-
const
|
|
211
|
-
|
|
354
|
+
const index = buildRouteIndex(routes, compileRoutes(routes));
|
|
355
|
+
const staticMatch = index.staticMap.get(cleanPath);
|
|
356
|
+
if (staticMatch) return {
|
|
357
|
+
path: cleanPath,
|
|
358
|
+
params: {},
|
|
359
|
+
query,
|
|
360
|
+
hash,
|
|
361
|
+
matched: staticMatch.matchedChain,
|
|
362
|
+
meta: staticMatch.meta
|
|
363
|
+
};
|
|
364
|
+
const pathParts = splitPath(cleanPath);
|
|
365
|
+
const pathLen = pathParts.length;
|
|
366
|
+
if (pathLen > 0) {
|
|
367
|
+
const first = pathParts[0];
|
|
368
|
+
const bucket = index.segmentMap.get(first);
|
|
369
|
+
if (bucket) {
|
|
370
|
+
const match = searchCandidates(bucket, pathParts, pathLen);
|
|
371
|
+
if (match) return {
|
|
372
|
+
path: cleanPath,
|
|
373
|
+
params: match.params,
|
|
374
|
+
query,
|
|
375
|
+
hash,
|
|
376
|
+
matched: match.matched,
|
|
377
|
+
meta: mergeMeta(match.matched)
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const dynMatch = searchCandidates(index.dynamicFirst, pathParts, pathLen);
|
|
382
|
+
if (dynMatch) return {
|
|
212
383
|
path: cleanPath,
|
|
213
|
-
params:
|
|
384
|
+
params: dynMatch.params,
|
|
214
385
|
query,
|
|
215
386
|
hash,
|
|
216
|
-
matched:
|
|
217
|
-
meta: mergeMeta(
|
|
387
|
+
matched: dynMatch.matched,
|
|
388
|
+
meta: mergeMeta(dynMatch.matched)
|
|
389
|
+
};
|
|
390
|
+
const w = index.wildcards[0];
|
|
391
|
+
if (w) return {
|
|
392
|
+
path: cleanPath,
|
|
393
|
+
params: {},
|
|
394
|
+
query,
|
|
395
|
+
hash,
|
|
396
|
+
matched: w.matchedChain,
|
|
397
|
+
meta: w.meta
|
|
218
398
|
};
|
|
219
399
|
return {
|
|
220
400
|
path: cleanPath,
|
|
@@ -225,44 +405,6 @@ function resolveRoute(rawPath, routes) {
|
|
|
225
405
|
meta: {}
|
|
226
406
|
};
|
|
227
407
|
}
|
|
228
|
-
function matchRoutes(path, routes, parentMatched, parentParams = {}) {
|
|
229
|
-
for (const route of routes) {
|
|
230
|
-
const result = matchSingleRoute(path, route, parentMatched, parentParams);
|
|
231
|
-
if (result) return result;
|
|
232
|
-
}
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
function matchSingleRoute(path, route, parentMatched, parentParams) {
|
|
236
|
-
if (!route.children || route.children.length === 0) {
|
|
237
|
-
const params = matchPath(route.path, path);
|
|
238
|
-
if (params === null) return null;
|
|
239
|
-
return {
|
|
240
|
-
params: {
|
|
241
|
-
...parentParams,
|
|
242
|
-
...params
|
|
243
|
-
},
|
|
244
|
-
matched: [...parentMatched, route]
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
const prefix = matchPrefix(route.path, path);
|
|
248
|
-
if (prefix === null) return null;
|
|
249
|
-
const allParams = {
|
|
250
|
-
...parentParams,
|
|
251
|
-
...prefix.params
|
|
252
|
-
};
|
|
253
|
-
const matched = [...parentMatched, route];
|
|
254
|
-
const childMatch = matchRoutes(prefix.rest, route.children, matched, allParams);
|
|
255
|
-
if (childMatch) return childMatch;
|
|
256
|
-
const exactParams = matchPath(route.path, path);
|
|
257
|
-
if (exactParams === null) return null;
|
|
258
|
-
return {
|
|
259
|
-
params: {
|
|
260
|
-
...parentParams,
|
|
261
|
-
...exactParams
|
|
262
|
-
},
|
|
263
|
-
matched
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
408
|
/** Merge meta from matched routes (leaf takes precedence) */
|
|
267
409
|
function mergeMeta(matched) {
|
|
268
410
|
const meta = {};
|