@react-router/fs-routes 7.16.0 → 8.0.0-pre.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/dist/index.d.ts +15 -13
- package/dist/index.js +353 -440
- package/package.json +11 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { RouteConfigEntry } from '@react-router/dev/routes';
|
|
2
1
|
|
|
2
|
+
import { RouteConfigEntry } from "@react-router/dev/routes";
|
|
3
|
+
|
|
4
|
+
//#region index.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* Creates route config from the file system using a convention that matches
|
|
5
7
|
* [Remix v2's route file
|
|
@@ -7,16 +9,16 @@ import { RouteConfigEntry } from '@react-router/dev/routes';
|
|
|
7
9
|
* within `routes.ts`.
|
|
8
10
|
*/
|
|
9
11
|
declare function flatRoutes(options?: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
/**
|
|
13
|
+
* An array of [minimatch](https://www.npmjs.com/package/minimatch) globs that match files to ignore.
|
|
14
|
+
* Defaults to `[]`.
|
|
15
|
+
*/
|
|
16
|
+
ignoredRouteFiles?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* The directory containing file system routes, relative to the app directory.
|
|
19
|
+
* Defaults to `"./routes"`.
|
|
20
|
+
*/
|
|
21
|
+
rootDirectory?: string;
|
|
20
22
|
}): Promise<RouteConfigEntry[]>;
|
|
21
|
-
|
|
22
|
-
export { flatRoutes };
|
|
23
|
+
//#endregion
|
|
24
|
+
export { flatRoutes };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @react-router/fs-routes
|
|
2
|
+
* @react-router/fs-routes v8.0.0-pre.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -8,468 +8,381 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
17
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
18
|
-
var __export = (target, all) => {
|
|
19
|
-
for (var name in all)
|
|
20
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
21
|
-
};
|
|
22
|
-
var __copyProps = (to, from, except, desc) => {
|
|
23
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
24
|
-
for (let key of __getOwnPropNames(from))
|
|
25
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
26
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
27
|
-
}
|
|
28
|
-
return to;
|
|
29
|
-
};
|
|
30
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
31
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
32
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
33
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
34
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
35
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
36
|
-
mod
|
|
37
|
-
));
|
|
38
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
39
|
-
|
|
40
|
-
// index.ts
|
|
41
|
-
var index_exports = {};
|
|
42
|
-
__export(index_exports, {
|
|
43
|
-
flatRoutes: () => flatRoutes2
|
|
44
|
-
});
|
|
45
|
-
module.exports = __toCommonJS(index_exports);
|
|
46
|
-
var import_node_fs2 = __toESM(require("fs"));
|
|
47
|
-
var import_node_path3 = __toESM(require("path"));
|
|
48
|
-
var import_routes = require("@react-router/dev/routes");
|
|
49
|
-
|
|
50
|
-
// manifest.ts
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { getAppDirectory } from "@react-router/dev/routes";
|
|
14
|
+
import { makeRe } from "minimatch";
|
|
15
|
+
//#region manifest.ts
|
|
51
16
|
function routeManifestToRouteConfig(routeManifest, rootId = "root") {
|
|
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
|
-
return routeConfig;
|
|
17
|
+
let routeConfigById = {};
|
|
18
|
+
for (let id in routeManifest) {
|
|
19
|
+
let route = routeManifest[id];
|
|
20
|
+
routeConfigById[id] = {
|
|
21
|
+
id: route.id,
|
|
22
|
+
file: route.file,
|
|
23
|
+
path: route.path,
|
|
24
|
+
index: route.index,
|
|
25
|
+
caseSensitive: route.caseSensitive
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
let routeConfig = [];
|
|
29
|
+
for (let id in routeConfigById) {
|
|
30
|
+
let route = routeConfigById[id];
|
|
31
|
+
let parentId = routeManifest[route.id].parentId;
|
|
32
|
+
if (parentId === rootId) routeConfig.push(route);
|
|
33
|
+
else {
|
|
34
|
+
let parentRoute = parentId && routeConfigById[parentId];
|
|
35
|
+
if (parentRoute) {
|
|
36
|
+
parentRoute.children = parentRoute.children || [];
|
|
37
|
+
parentRoute.children.push(route);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return routeConfig;
|
|
78
42
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
var import_node_fs = __toESM(require("fs"));
|
|
82
|
-
var import_node_path2 = __toESM(require("path"));
|
|
83
|
-
var import_minimatch = require("minimatch");
|
|
84
|
-
|
|
85
|
-
// normalizeSlashes.ts
|
|
86
|
-
var import_node_path = __toESM(require("path"));
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region normalizeSlashes.ts
|
|
87
45
|
function normalizeSlashes(file) {
|
|
88
|
-
|
|
46
|
+
return file.replaceAll(path.win32.sep, "/");
|
|
89
47
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region flatRoutes.ts
|
|
50
|
+
const routeModuleExts = [
|
|
51
|
+
".js",
|
|
52
|
+
".jsx",
|
|
53
|
+
".ts",
|
|
54
|
+
".tsx",
|
|
55
|
+
".md",
|
|
56
|
+
".mdx"
|
|
57
|
+
];
|
|
58
|
+
const PrefixLookupTrieEndSymbol = Symbol("PrefixLookupTrieEndSymbol");
|
|
99
59
|
var PrefixLookupTrie = class {
|
|
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
|
-
this.#findAndRemoveRecursive(values, node[char], prefix + char, filter);
|
|
127
|
-
}
|
|
128
|
-
if (node[PrefixLookupTrieEndSymbol] && filter(prefix)) {
|
|
129
|
-
node[PrefixLookupTrieEndSymbol] = false;
|
|
130
|
-
values.push(prefix);
|
|
131
|
-
}
|
|
132
|
-
return values;
|
|
133
|
-
}
|
|
60
|
+
root = { [PrefixLookupTrieEndSymbol]: false };
|
|
61
|
+
add(value) {
|
|
62
|
+
if (!value) throw new Error("Cannot add empty string to PrefixLookupTrie");
|
|
63
|
+
let node = this.root;
|
|
64
|
+
for (let char of value) {
|
|
65
|
+
if (!node[char]) node[char] = { [PrefixLookupTrieEndSymbol]: false };
|
|
66
|
+
node = node[char];
|
|
67
|
+
}
|
|
68
|
+
node[PrefixLookupTrieEndSymbol] = true;
|
|
69
|
+
}
|
|
70
|
+
findAndRemove(prefix, filter) {
|
|
71
|
+
let node = this.root;
|
|
72
|
+
for (let char of prefix) {
|
|
73
|
+
if (!node[char]) return [];
|
|
74
|
+
node = node[char];
|
|
75
|
+
}
|
|
76
|
+
return this.#findAndRemoveRecursive([], node, prefix, filter);
|
|
77
|
+
}
|
|
78
|
+
#findAndRemoveRecursive(values, node, prefix, filter) {
|
|
79
|
+
for (let char of Object.keys(node)) this.#findAndRemoveRecursive(values, node[char], prefix + char, filter);
|
|
80
|
+
if (node[PrefixLookupTrieEndSymbol] && filter(prefix)) {
|
|
81
|
+
node[PrefixLookupTrieEndSymbol] = false;
|
|
82
|
+
values.push(prefix);
|
|
83
|
+
}
|
|
84
|
+
return values;
|
|
85
|
+
}
|
|
134
86
|
};
|
|
135
|
-
function flatRoutes(appDirectory, ignoredFilePatterns = [], prefix = "routes") {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let routes = [];
|
|
154
|
-
for (let entry of entries) {
|
|
155
|
-
let filepath = normalizeSlashes(import_node_path2.default.join(routesDir, entry.name));
|
|
156
|
-
let route = null;
|
|
157
|
-
if (entry.isDirectory()) {
|
|
158
|
-
route = findRouteModuleForFolder(
|
|
159
|
-
appDirectory,
|
|
160
|
-
filepath,
|
|
161
|
-
ignoredFileRegex
|
|
162
|
-
);
|
|
163
|
-
} else if (entry.isFile()) {
|
|
164
|
-
route = findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex);
|
|
165
|
-
}
|
|
166
|
-
if (route) routes.push(route);
|
|
167
|
-
}
|
|
168
|
-
let routeManifest = flatRoutesUniversal(appDirectory, routes, prefix);
|
|
169
|
-
return routeManifest;
|
|
87
|
+
function flatRoutes$1(appDirectory, ignoredFilePatterns = [], prefix = "routes") {
|
|
88
|
+
let ignoredFileRegex = Array.from(new Set(["**/.*", ...ignoredFilePatterns])).map((re) => makeRe(re)).filter((re) => !!re);
|
|
89
|
+
let routesDir = path.join(appDirectory, prefix);
|
|
90
|
+
if (!findFile(appDirectory, "root", routeModuleExts)) throw new Error(`Could not find a root route module in the app directory: ${appDirectory}`);
|
|
91
|
+
if (!fs.existsSync(routesDir)) throw new Error(`Could not find the routes directory: ${routesDir}. Did you forget to create it?`);
|
|
92
|
+
let entries = fs.readdirSync(routesDir, {
|
|
93
|
+
withFileTypes: true,
|
|
94
|
+
encoding: "utf-8"
|
|
95
|
+
});
|
|
96
|
+
let routes = [];
|
|
97
|
+
for (let entry of entries) {
|
|
98
|
+
let filepath = normalizeSlashes(path.join(routesDir, entry.name));
|
|
99
|
+
let route = null;
|
|
100
|
+
if (entry.isDirectory()) route = findRouteModuleForFolder(appDirectory, filepath, ignoredFileRegex);
|
|
101
|
+
else if (entry.isFile()) route = findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex);
|
|
102
|
+
if (route) routes.push(route);
|
|
103
|
+
}
|
|
104
|
+
return flatRoutesUniversal(appDirectory, routes, prefix);
|
|
170
105
|
}
|
|
171
106
|
function flatRoutesUniversal(appDirectory, routes, prefix = "routes") {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
107
|
+
let urlConflicts = /* @__PURE__ */ new Map();
|
|
108
|
+
let routeManifest = {};
|
|
109
|
+
let prefixLookup = new PrefixLookupTrie();
|
|
110
|
+
let uniqueRoutes = /* @__PURE__ */ new Map();
|
|
111
|
+
let routeIdConflicts = /* @__PURE__ */ new Map();
|
|
112
|
+
let normalizedApp = normalizeSlashes(appDirectory);
|
|
113
|
+
let appWithPrefix = path.posix.join(normalizedApp, prefix);
|
|
114
|
+
let routeIds = /* @__PURE__ */ new Map();
|
|
115
|
+
for (let file of routes) {
|
|
116
|
+
let normalizedFile = normalizeSlashes(file);
|
|
117
|
+
let routeExt = path.extname(normalizedFile);
|
|
118
|
+
let routeDir = path.dirname(normalizedFile);
|
|
119
|
+
let routeId = routeDir === appWithPrefix ? path.posix.relative(normalizedApp, normalizedFile).slice(0, -routeExt.length) : path.posix.relative(normalizedApp, routeDir);
|
|
120
|
+
let conflict = routeIds.get(routeId);
|
|
121
|
+
if (conflict) {
|
|
122
|
+
let currentConflicts = routeIdConflicts.get(routeId);
|
|
123
|
+
if (!currentConflicts) currentConflicts = [path.posix.relative(normalizedApp, conflict)];
|
|
124
|
+
currentConflicts.push(path.posix.relative(normalizedApp, normalizedFile));
|
|
125
|
+
routeIdConflicts.set(routeId, currentConflicts);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
routeIds.set(routeId, normalizedFile);
|
|
129
|
+
}
|
|
130
|
+
let sortedRouteIds = Array.from(routeIds).sort(([a], [b]) => b.length - a.length);
|
|
131
|
+
for (let [routeId, file] of sortedRouteIds) {
|
|
132
|
+
let index = routeId.endsWith("_index");
|
|
133
|
+
let [segments, raw] = getRouteSegments(routeId.slice(prefix.length + 1));
|
|
134
|
+
let pathname = createRoutePath(segments, raw, index);
|
|
135
|
+
routeManifest[routeId] = {
|
|
136
|
+
file: path.posix.relative(normalizedApp, file),
|
|
137
|
+
id: routeId,
|
|
138
|
+
path: pathname
|
|
139
|
+
};
|
|
140
|
+
if (index) routeManifest[routeId].index = true;
|
|
141
|
+
let childRouteIds = prefixLookup.findAndRemove(routeId, (value) => {
|
|
142
|
+
return [".", "/"].includes(value.slice(routeId.length).charAt(0));
|
|
143
|
+
});
|
|
144
|
+
prefixLookup.add(routeId);
|
|
145
|
+
if (childRouteIds.length > 0) for (let childRouteId of childRouteIds) routeManifest[childRouteId].parentId = routeId;
|
|
146
|
+
}
|
|
147
|
+
let parentChildrenMap = /* @__PURE__ */ new Map();
|
|
148
|
+
for (let [routeId] of sortedRouteIds) {
|
|
149
|
+
let config = routeManifest[routeId];
|
|
150
|
+
if (!config.parentId) continue;
|
|
151
|
+
let existingChildren = parentChildrenMap.get(config.parentId) || [];
|
|
152
|
+
existingChildren.push(config);
|
|
153
|
+
parentChildrenMap.set(config.parentId, existingChildren);
|
|
154
|
+
}
|
|
155
|
+
for (let [routeId] of sortedRouteIds) {
|
|
156
|
+
let config = routeManifest[routeId];
|
|
157
|
+
let originalPathname = config.path || "";
|
|
158
|
+
let pathname = config.path;
|
|
159
|
+
let parentConfig = config.parentId ? routeManifest[config.parentId] : null;
|
|
160
|
+
if (parentConfig?.path && pathname) pathname = pathname.slice(parentConfig.path.length).replace(/^\//, "").replace(/\/$/, "");
|
|
161
|
+
if (!config.parentId) config.parentId = "root";
|
|
162
|
+
config.path = pathname || void 0;
|
|
163
|
+
/**
|
|
164
|
+
* We do not try to detect path collisions for pathless layout route
|
|
165
|
+
* files because, by definition, they create the potential for route
|
|
166
|
+
* collisions _at that level in the tree_.
|
|
167
|
+
*
|
|
168
|
+
* Consider example where a user may want multiple pathless layout routes
|
|
169
|
+
* for different subfolders
|
|
170
|
+
*
|
|
171
|
+
* routes/
|
|
172
|
+
* account.tsx
|
|
173
|
+
* account._private.tsx
|
|
174
|
+
* account._private.orders.tsx
|
|
175
|
+
* account._private.profile.tsx
|
|
176
|
+
* account._public.tsx
|
|
177
|
+
* account._public.login.tsx
|
|
178
|
+
* account._public.perks.tsx
|
|
179
|
+
*
|
|
180
|
+
* In order to support both a public and private layout for `/account/*`
|
|
181
|
+
* URLs, we are creating a mutually exclusive set of URLs beneath 2
|
|
182
|
+
* separate pathless layout routes. In this case, the route paths for
|
|
183
|
+
* both account._public.tsx and account._private.tsx is the same
|
|
184
|
+
* (/account), but we're again not expecting to match at that level.
|
|
185
|
+
*
|
|
186
|
+
* By only ignoring this check when the final portion of the filename is
|
|
187
|
+
* pathless, we will still detect path collisions such as:
|
|
188
|
+
*
|
|
189
|
+
* routes/parent._pathless.foo.tsx
|
|
190
|
+
* routes/parent._pathless2.foo.tsx
|
|
191
|
+
*
|
|
192
|
+
* and
|
|
193
|
+
*
|
|
194
|
+
* routes/parent._pathless/index.tsx
|
|
195
|
+
* routes/parent._pathless2/index.tsx
|
|
196
|
+
*/
|
|
197
|
+
let lastRouteSegment = config.id.replace(new RegExp(`^${prefix}/`), "").split(".").pop();
|
|
198
|
+
if (lastRouteSegment && lastRouteSegment.startsWith("_") && lastRouteSegment !== "_index") continue;
|
|
199
|
+
let conflictRouteId = originalPathname + (config.index ? "?index" : "");
|
|
200
|
+
let conflict = uniqueRoutes.get(conflictRouteId);
|
|
201
|
+
uniqueRoutes.set(conflictRouteId, config);
|
|
202
|
+
if (conflict && (originalPathname || config.index)) {
|
|
203
|
+
let currentConflicts = urlConflicts.get(originalPathname);
|
|
204
|
+
if (!currentConflicts) currentConflicts = [conflict];
|
|
205
|
+
currentConflicts.push(config);
|
|
206
|
+
urlConflicts.set(originalPathname, currentConflicts);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (routeIdConflicts.size > 0) for (let [routeId, files] of routeIdConflicts.entries()) console.error(getRouteIdConflictErrorMessage(routeId, files));
|
|
211
|
+
if (urlConflicts.size > 0) for (let [path, routes] of urlConflicts.entries()) {
|
|
212
|
+
for (let i = 1; i < routes.length; i++) delete routeManifest[routes[i].id];
|
|
213
|
+
let files = routes.map((r) => r.file);
|
|
214
|
+
console.error(getRoutePathConflictErrorMessage(path, files));
|
|
215
|
+
}
|
|
216
|
+
return routeManifest;
|
|
269
217
|
}
|
|
270
218
|
function findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return filepath;
|
|
219
|
+
let relativePath = normalizeSlashes(path.relative(appDirectory, filepath));
|
|
220
|
+
if (ignoredFileRegex.some((regex) => regex.test(relativePath))) return null;
|
|
221
|
+
return filepath;
|
|
275
222
|
}
|
|
276
223
|
function findRouteModuleForFolder(appDirectory, filepath, ignoredFileRegex) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
console.error(
|
|
288
|
-
getRoutePathConflictErrorMessage(routePath || "/", [
|
|
289
|
-
routeRouteModule,
|
|
290
|
-
routeIndexModule
|
|
291
|
-
])
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
return routeRouteModule || routeIndexModule || null;
|
|
224
|
+
let relativePath = path.relative(appDirectory, filepath);
|
|
225
|
+
if (ignoredFileRegex.some((regex) => regex.test(relativePath))) return null;
|
|
226
|
+
let routeRouteModule = findFile(filepath, "route", routeModuleExts);
|
|
227
|
+
let routeIndexModule = findFile(filepath, "index", routeModuleExts);
|
|
228
|
+
if (routeRouteModule && routeIndexModule) {
|
|
229
|
+
let [segments, raw] = getRouteSegments(path.relative(appDirectory, filepath));
|
|
230
|
+
let routePath = createRoutePath(segments, raw, false);
|
|
231
|
+
console.error(getRoutePathConflictErrorMessage(routePath || "/", [routeRouteModule, routeIndexModule]));
|
|
232
|
+
}
|
|
233
|
+
return routeRouteModule || routeIndexModule || null;
|
|
295
234
|
}
|
|
296
235
|
function getRouteSegments(routeId) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (char === escapeEnd) {
|
|
397
|
-
state = "OPTIONAL";
|
|
398
|
-
rawRouteSegment += char;
|
|
399
|
-
break;
|
|
400
|
-
}
|
|
401
|
-
routeSegment += char;
|
|
402
|
-
rawRouteSegment += char;
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
pushRouteSegment(routeSegment, rawRouteSegment);
|
|
408
|
-
return [routeSegments, rawRouteSegments];
|
|
236
|
+
let routeSegments = [];
|
|
237
|
+
let rawRouteSegments = [];
|
|
238
|
+
let index = 0;
|
|
239
|
+
let routeSegment = "";
|
|
240
|
+
let rawRouteSegment = "";
|
|
241
|
+
let state = "NORMAL";
|
|
242
|
+
let pushRouteSegment = (segment, rawSegment) => {
|
|
243
|
+
if (!segment) return;
|
|
244
|
+
let notSupportedInRR = (segment, char) => {
|
|
245
|
+
throw new Error(`Route segment "${segment}" for "${routeId}" cannot contain "${char}".\nIf this is something you need, upvote this proposal for React Router https://github.com/remix-run/react-router/discussions/9822.`);
|
|
246
|
+
};
|
|
247
|
+
if (rawSegment.includes("*")) return notSupportedInRR(rawSegment, "*");
|
|
248
|
+
if (rawSegment.includes(":")) return notSupportedInRR(rawSegment, ":");
|
|
249
|
+
if (rawSegment.includes("/")) return notSupportedInRR(segment, "/");
|
|
250
|
+
routeSegments.push(segment);
|
|
251
|
+
rawRouteSegments.push(rawSegment);
|
|
252
|
+
};
|
|
253
|
+
while (index < routeId.length) {
|
|
254
|
+
let char = routeId[index];
|
|
255
|
+
index++;
|
|
256
|
+
switch (state) {
|
|
257
|
+
case "NORMAL":
|
|
258
|
+
if (isSegmentSeparator(char)) {
|
|
259
|
+
pushRouteSegment(routeSegment, rawRouteSegment);
|
|
260
|
+
routeSegment = "";
|
|
261
|
+
rawRouteSegment = "";
|
|
262
|
+
state = "NORMAL";
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (char === "[") {
|
|
266
|
+
state = "ESCAPE";
|
|
267
|
+
rawRouteSegment += char;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
if (char === "(") {
|
|
271
|
+
state = "OPTIONAL";
|
|
272
|
+
rawRouteSegment += char;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
if (!routeSegment && char === "$") {
|
|
276
|
+
if (index === routeId.length) {
|
|
277
|
+
routeSegment += "*";
|
|
278
|
+
rawRouteSegment += char;
|
|
279
|
+
} else {
|
|
280
|
+
routeSegment += ":";
|
|
281
|
+
rawRouteSegment += char;
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
routeSegment += char;
|
|
286
|
+
rawRouteSegment += char;
|
|
287
|
+
break;
|
|
288
|
+
case "ESCAPE":
|
|
289
|
+
if (char === "]") {
|
|
290
|
+
state = "NORMAL";
|
|
291
|
+
rawRouteSegment += char;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
routeSegment += char;
|
|
295
|
+
rawRouteSegment += char;
|
|
296
|
+
break;
|
|
297
|
+
case "OPTIONAL":
|
|
298
|
+
if (char === ")") {
|
|
299
|
+
routeSegment += "?";
|
|
300
|
+
rawRouteSegment += char;
|
|
301
|
+
state = "NORMAL";
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
if (char === "[") {
|
|
305
|
+
state = "OPTIONAL_ESCAPE";
|
|
306
|
+
rawRouteSegment += char;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
if (!routeSegment && char === "$") {
|
|
310
|
+
if (index === routeId.length) {
|
|
311
|
+
routeSegment += "*";
|
|
312
|
+
rawRouteSegment += char;
|
|
313
|
+
} else {
|
|
314
|
+
routeSegment += ":";
|
|
315
|
+
rawRouteSegment += char;
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
routeSegment += char;
|
|
320
|
+
rawRouteSegment += char;
|
|
321
|
+
break;
|
|
322
|
+
case "OPTIONAL_ESCAPE":
|
|
323
|
+
if (char === "]") {
|
|
324
|
+
state = "OPTIONAL";
|
|
325
|
+
rawRouteSegment += char;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
routeSegment += char;
|
|
329
|
+
rawRouteSegment += char;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
pushRouteSegment(routeSegment, rawRouteSegment);
|
|
334
|
+
return [routeSegments, rawRouteSegments];
|
|
409
335
|
}
|
|
410
336
|
function createRoutePath(routeSegments, rawRouteSegments, isIndex) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (segment.endsWith("_") && rawSegment.endsWith("_")) {
|
|
422
|
-
segment = segment.slice(0, -1);
|
|
423
|
-
}
|
|
424
|
-
result.push(segment);
|
|
425
|
-
}
|
|
426
|
-
return result.length ? result.join("/") : void 0;
|
|
337
|
+
let result = [];
|
|
338
|
+
if (isIndex) routeSegments = routeSegments.slice(0, -1);
|
|
339
|
+
for (let index = 0; index < routeSegments.length; index++) {
|
|
340
|
+
let segment = routeSegments[index];
|
|
341
|
+
let rawSegment = rawRouteSegments[index];
|
|
342
|
+
if (segment.startsWith("_") && rawSegment.startsWith("_")) continue;
|
|
343
|
+
if (segment.endsWith("_") && rawSegment.endsWith("_")) segment = segment.slice(0, -1);
|
|
344
|
+
result.push(segment);
|
|
345
|
+
}
|
|
346
|
+
return result.length ? result.join("/") : void 0;
|
|
427
347
|
}
|
|
428
348
|
function getRoutePathConflictErrorMessage(pathname, routes) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
return `\u26A0\uFE0F Route Path Collision: "${pathname}"
|
|
434
|
-
|
|
435
|
-
The following routes all define the same URL, only the first one will be used
|
|
436
|
-
|
|
437
|
-
\u{1F7E2} ${taken}
|
|
438
|
-
` + others.map((route) => `\u2B55\uFE0F\uFE0F ${route}`).join("\n") + "\n";
|
|
349
|
+
let [taken, ...others] = routes;
|
|
350
|
+
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
351
|
+
return `⚠️ Route Path Collision: "${pathname}"\n\nThe following routes all define the same URL, only the first one will be used\n\n🟢 ${taken}\n` + others.map((route) => `⭕️️ ${route}`).join("\n") + "\n";
|
|
439
352
|
}
|
|
440
353
|
function getRouteIdConflictErrorMessage(routeId, files) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
The following routes all define the same Route ID, only the first one will be used
|
|
445
|
-
|
|
446
|
-
\u{1F7E2} ${taken}
|
|
447
|
-
` + others.map((route) => `\u2B55\uFE0F\uFE0F ${route}`).join("\n") + "\n";
|
|
354
|
+
let [taken, ...others] = files;
|
|
355
|
+
return `⚠️ Route ID Collision: "${routeId}"\n\nThe following routes all define the same Route ID, only the first one will be used\n\n🟢 ${taken}\n` + others.map((route) => `⭕️️ ${route}`).join("\n") + "\n";
|
|
448
356
|
}
|
|
449
357
|
function isSegmentSeparator(checkChar) {
|
|
450
|
-
|
|
451
|
-
|
|
358
|
+
if (!checkChar) return false;
|
|
359
|
+
return [
|
|
360
|
+
"/",
|
|
361
|
+
".",
|
|
362
|
+
path.win32.sep
|
|
363
|
+
].includes(checkChar);
|
|
452
364
|
}
|
|
453
365
|
function findFile(dir, basename, extensions) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return void 0;
|
|
366
|
+
for (let ext of extensions) {
|
|
367
|
+
let name = basename + ext;
|
|
368
|
+
let file = path.join(dir, name);
|
|
369
|
+
if (fs.existsSync(file)) return file;
|
|
370
|
+
}
|
|
460
371
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region index.ts
|
|
374
|
+
/**
|
|
375
|
+
* Creates route config from the file system using a convention that matches
|
|
376
|
+
* [Remix v2's route file
|
|
377
|
+
* naming](https://v2.remix.run/docs/file-conventions/routes), for use
|
|
378
|
+
* within `routes.ts`.
|
|
379
|
+
*/
|
|
380
|
+
async function flatRoutes(options = {}) {
|
|
381
|
+
let { ignoredRouteFiles = [], rootDirectory: userRootDirectory = "routes" } = options;
|
|
382
|
+
let appDirectory = getAppDirectory();
|
|
383
|
+
let rootDirectory = path.resolve(appDirectory, userRootDirectory);
|
|
384
|
+
let prefix = normalizeSlashes(path.relative(appDirectory, rootDirectory));
|
|
385
|
+
return routeManifestToRouteConfig(fs.existsSync(rootDirectory) ? flatRoutes$1(appDirectory, ignoredRouteFiles, prefix) : {});
|
|
471
386
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
flatRoutes
|
|
475
|
-
});
|
|
387
|
+
//#endregion
|
|
388
|
+
export { flatRoutes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-router/fs-routes",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "8.0.0-pre.0",
|
|
4
5
|
"description": "File system routing conventions for React Router, for use within routes.ts",
|
|
5
6
|
"bugs": {
|
|
6
7
|
"url": "https://github.com/remix-run/react-router/issues"
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
},
|
|
23
24
|
"wireit": {
|
|
24
25
|
"build": {
|
|
25
|
-
"command": "
|
|
26
|
+
"command": "tsdown",
|
|
26
27
|
"files": [
|
|
27
28
|
"../../pnpm-workspace.yaml",
|
|
28
29
|
"*.ts",
|
|
@@ -35,17 +36,18 @@
|
|
|
35
36
|
}
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"minimatch": "^
|
|
39
|
+
"minimatch": "^10.2.5"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
42
|
+
"@types/node": "^22.19.19",
|
|
43
|
+
"tsdown": "^0.22.0",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"wireit": "0.14.12",
|
|
46
|
+
"@react-router/dev": "8.0.0-pre.0"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"typescript": "^5.1.0 || ^6.0.0",
|
|
48
|
-
"@react-router/dev": "^
|
|
50
|
+
"@react-router/dev": "^8.0.0-pre.0"
|
|
49
51
|
},
|
|
50
52
|
"peerDependenciesMeta": {
|
|
51
53
|
"typescript": {
|
|
@@ -53,7 +55,7 @@
|
|
|
53
55
|
}
|
|
54
56
|
},
|
|
55
57
|
"engines": {
|
|
56
|
-
"node": ">=
|
|
58
|
+
"node": ">=22.12.0"
|
|
57
59
|
},
|
|
58
60
|
"files": [
|
|
59
61
|
"dist/",
|