This repository was archived by the owner on May 10, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathsortedRoutes.js
153 lines (129 loc) · 5.22 KB
/
sortedRoutes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copied from serverless-next.js (v1.9.10)
// https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/lib/sortedRoutes.js
/// This file taken was from next.js repo and converted to JS.
// https://github.com/zeit/next.js/blob/canary/packages/next/next-server/lib/router/utils/sorted-routes.ts
class UrlNode {
constructor() {
this.placeholder = true;
this.children = new Map();
this.slugName = null;
this.restSlugName = null;
}
insert(urlPath) {
this._insert(urlPath.split("/").filter(Boolean), [], false);
}
smoosh() {
return this._smoosh();
}
_smoosh(prefix = "/") {
const childrenPaths = [...this.children.keys()].sort();
if (this.slugName !== null) {
childrenPaths.splice(childrenPaths.indexOf("[]"), 1);
}
if (this.restSlugName !== null) {
childrenPaths.splice(childrenPaths.indexOf("[...]"), 1);
}
const routes = childrenPaths
.map(c => this.children.get(c)._smoosh(`${prefix}${c}/`))
.reduce((prev, curr) => [...prev, ...curr], []);
if (this.slugName !== null) {
routes.push(
...this.children.get("[]")._smoosh(`${prefix}[${this.slugName}]/`)
);
}
if (!this.placeholder) {
routes.unshift(prefix === "/" ? "/" : prefix.slice(0, -1));
}
if (this.restSlugName !== null) {
routes.push(
...this.children
.get("[...]")
._smoosh(`${prefix}[...${this.restSlugName}]/`)
);
}
return routes;
}
_insert(urlPaths, slugNames, isCatchAll) {
if (urlPaths.length === 0) {
this.placeholder = false;
return;
}
if (isCatchAll) {
throw new Error(`Catch-all must be the last part of the URL.`);
}
// The next segment in the urlPaths list
let nextSegment = urlPaths[0];
// Check if the segment matches `[something]`
if (nextSegment.startsWith("[") && nextSegment.endsWith("]")) {
// Strip `[` and `]`, leaving only `something`
let segmentName = nextSegment.slice(1, -1);
if (segmentName.startsWith("...")) {
segmentName = segmentName.substring(3);
isCatchAll = true;
}
if (segmentName.startsWith(".")) {
throw new Error(
`Segment names may not start with erroneous periods ('${segmentName}').`
);
}
function handleSlug(previousSlug, nextSlug) {
if (previousSlug !== null) {
// If the specific segment already has a slug but the slug is not `something`
// This prevents collisions like:
// pages/[post]/index.js
// pages/[id]/index.js
// Because currently multiple dynamic params on the same segment level are not supported
if (previousSlug !== nextSlug) {
// TODO: This error seems to be confusing for users, needs an err.sh link, the description can be based on above comment.
throw new Error(
`You cannot use different slug names for the same dynamic path ('${previousSlug}' !== '${nextSlug}').`
);
}
}
if (slugNames.indexOf(nextSlug) !== -1) {
throw new Error(
`You cannot have the same slug name "${nextSlug}" repeat within a single dynamic path`
);
}
slugNames.push(nextSlug);
}
if (isCatchAll) {
handleSlug(this.restSlugName, segmentName);
// slugName is kept as it can only be one particular slugName
this.restSlugName = segmentName;
// nextSegment is overwritten to [] so that it can later be sorted specifically
nextSegment = "[...]";
} else {
handleSlug(this.slugName, segmentName);
// slugName is kept as it can only be one particular slugName
this.slugName = segmentName;
// nextSegment is overwritten to [] so that it can later be sorted specifically
nextSegment = "[]";
}
}
// If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
if (!this.children.has(nextSegment)) {
this.children.set(nextSegment, new UrlNode());
}
this.children
.get(nextSegment)
._insert(urlPaths.slice(1), slugNames, isCatchAll);
}
}
module.exports = function getSortedRoutes(normalizedPages) {
// First the UrlNode is created, and every UrlNode can have only 1 dynamic segment
// Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js
// Only 1 dynamic segment per nesting level
// So in the case that is test/integration/dynamic-routing it'll be this:
// pages/[post]/comments.js
// pages/blog/[post]/comment/[id].js
// Both are fine because `pages/[post]` and `pages/blog` are on the same level
// So in this case `UrlNode` created here has `this.slugName === 'post'`
// And since your PR passed through `slugName` as an array basically it'd including it in too many possibilities
// Instead what has to be passed through is the upwards path's dynamic names
const root = new UrlNode();
// Here the `root` gets injected multiple paths, and insert will break them up into sublevels
normalizedPages.forEach(pagePath => root.insert(pagePath));
// Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
return root.smoosh();
};