nuxt-typed-router 2.3.4 → 3.0.0-beta.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/README.md +0 -3
- package/dist/module.d.ts +8 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +630 -186
- package/package.json +23 -17
package/README.md
CHANGED
|
@@ -24,9 +24,6 @@
|
|
|
24
24
|
- Out of the box `i18n` support
|
|
25
25
|
- Supports routes extended by config and modules
|
|
26
26
|
|
|
27
|
-
> ⚠️ Since `v2.1.x`, `useTypedRouter` and `useTypedRoute` are no longer exported.
|
|
28
|
-
The package can now override types from `useRouter`, `useRoute` and `navigateTo`
|
|
29
|
-
|
|
30
27
|
<br/>
|
|
31
28
|
|
|
32
29
|
<br/>
|
package/dist/module.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
|
|
3
3
|
interface ModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* ⛔️ Experimental, disable it if you encounter problems
|
|
6
|
+
*
|
|
7
|
+
* Enables path autocomplete and path validity for programmatic validation
|
|
8
|
+
*
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
experimentalPathCheck?: boolean;
|
|
4
12
|
/**
|
|
5
13
|
* Set to false if you don't want a plugin generated
|
|
6
14
|
* @default false
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { addPluginTemplate, extendPages, defineNuxtModule, createResolver } from
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import logSymbols from 'log-symbols';
|
|
4
4
|
import { defu } from 'defu';
|
|
5
|
+
import { customAlphabet, nanoid as nanoid$1 } from 'nanoid';
|
|
5
6
|
import prettier from 'prettier';
|
|
6
7
|
import fs from 'fs';
|
|
7
8
|
import { fileURLToPath } from 'url';
|
|
@@ -13,6 +14,7 @@ class ModuleOptionsStore {
|
|
|
13
14
|
constructor() {
|
|
14
15
|
this.plugin = false;
|
|
15
16
|
this.strict = false;
|
|
17
|
+
this.experimentalPathCheck = true;
|
|
16
18
|
this.autoImport = false;
|
|
17
19
|
this.rootDir = "";
|
|
18
20
|
this.i18n = false;
|
|
@@ -31,6 +33,8 @@ class ModuleOptionsStore {
|
|
|
31
33
|
this.i18n = options.i18n;
|
|
32
34
|
if (options.i18nLocales != null)
|
|
33
35
|
this.i18nLocales = options.i18nLocales;
|
|
36
|
+
if (options.experimentalPathCheck != null)
|
|
37
|
+
this.experimentalPathCheck = options.experimentalPathCheck;
|
|
34
38
|
}
|
|
35
39
|
getResolvedStrictOptions() {
|
|
36
40
|
let resolved;
|
|
@@ -158,24 +162,111 @@ function createRoutesParamsRecordResolvedExport(routesParams) {
|
|
|
158
162
|
}`;
|
|
159
163
|
}
|
|
160
164
|
|
|
165
|
+
const nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 10);
|
|
166
|
+
function createRoutePathSchema(routePaths) {
|
|
167
|
+
return `export type RoutePathSchema =
|
|
168
|
+
${routePaths.filter((f) => !!f.path).map((route) => `"${route.path}"`).join("|")}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
function createValidatePathTypes(pathElements) {
|
|
172
|
+
let pathConditions = pathElements.map(createTypeValidatePathCondition);
|
|
173
|
+
const conditionsList = pathConditions.map((m) => m.condition);
|
|
174
|
+
return `
|
|
175
|
+
${pathConditions.length ? conditionsList.join("\n\n") : ""}
|
|
176
|
+
|
|
177
|
+
export type ValidatePath<T extends string> = T extends string
|
|
178
|
+
? T extends '/'
|
|
179
|
+
? T
|
|
180
|
+
: ${pathConditions.length ? pathConditions.map((t) => `${t.typeName}<T> extends true ? T`).join(": ") : "never"}
|
|
181
|
+
: string extends T
|
|
182
|
+
? T
|
|
183
|
+
: \`Error: \${${pathConditions.map((t) => `${t.typeName}<T>`).join("|")}}\` : 'Type should be a string';
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
export type RouteNameFromPath<T extends string> = T extends string
|
|
187
|
+
? T extends '/'
|
|
188
|
+
? "index"
|
|
189
|
+
: ${pathConditions.length ? pathConditions.map((t) => `${t.typeName}<T> extends true ? "${t.routeName}"`).join(": ") : "never"}
|
|
190
|
+
: never : never;
|
|
191
|
+
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
function createTypeValidatePathCondition(elements) {
|
|
195
|
+
const typeName = `Validate${nanoid(7)}`;
|
|
196
|
+
const params = /* @__PURE__ */ new Map();
|
|
197
|
+
const routeName = elements.flat()[0].routeName;
|
|
198
|
+
const hasOnlyNames = elements.flat().every((elem) => elem.type === "name");
|
|
199
|
+
const condition = `type ${typeName}<T> = T extends \`/${elements.map((elementArray, index) => {
|
|
200
|
+
return elementArray.map((elem) => {
|
|
201
|
+
const isLast = index === elements.flat().length - 1;
|
|
202
|
+
if (elem.type === "name" && isLast && !hasOnlyNames) {
|
|
203
|
+
const id = nanoid(6);
|
|
204
|
+
params.set(elem.id, id);
|
|
205
|
+
return `${elem.content}\${infer ${id}}`;
|
|
206
|
+
} else if (elem.type === "name") {
|
|
207
|
+
return elem.content;
|
|
208
|
+
} else if (elem.type === "param" || elem.type === "optionalParam") {
|
|
209
|
+
const id = nanoid(6);
|
|
210
|
+
params.set(elem.id, id);
|
|
211
|
+
return `\${infer ${id}}`;
|
|
212
|
+
} else if (elem.type === "catchAll") {
|
|
213
|
+
return `\${string}`;
|
|
214
|
+
}
|
|
215
|
+
}).join("");
|
|
216
|
+
}).join("/")}\`
|
|
217
|
+
? ${hasOnlyNames ? `true :` : elements.flat().map((elem, index) => {
|
|
218
|
+
let output = "";
|
|
219
|
+
const isLast = index === elements.flat().length - 1;
|
|
220
|
+
const isName = elem.type === "name";
|
|
221
|
+
const isOptional = elem.type === "optionalParam";
|
|
222
|
+
const isParam = elem.type === "param";
|
|
223
|
+
elem.type === "catchAll";
|
|
224
|
+
if (isName && isLast) {
|
|
225
|
+
output = `ValidEndOfPath<${params.get(elem.id)}> extends false ? "End of path '${elem.fullPath}' is invalid" : true :`;
|
|
226
|
+
} else if (isParam && isLast) {
|
|
227
|
+
output = `ValidParam<${params.get(elem.id)}> extends false ? "Parameter {${elem.content}} of path '${elem.fullPath}' is invalid" : true :`;
|
|
228
|
+
} else if (isParam) {
|
|
229
|
+
output = `ValidStringPath<${params.get(elem.id)}> extends false ? "Parameter {${elem.content}} of path '${elem.fullPath}' is required" : `;
|
|
230
|
+
} else if (isOptional && isLast) {
|
|
231
|
+
output = `ValidParam<${params.get(elem.id)}, false> extends false ? "Parameter {${elem.content}} of path '${elem.fullPath}' is invalid" : true :`;
|
|
232
|
+
} else if (isLast) {
|
|
233
|
+
output += "true :";
|
|
234
|
+
}
|
|
235
|
+
return output;
|
|
236
|
+
}).join("")} false ;`;
|
|
237
|
+
return {
|
|
238
|
+
typeName,
|
|
239
|
+
condition,
|
|
240
|
+
routeName
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
161
244
|
function createRoutesTypesFile({
|
|
162
245
|
routesList,
|
|
163
246
|
routesObjectTemplate,
|
|
164
247
|
routesDeclTemplate,
|
|
165
|
-
routesParams
|
|
248
|
+
routesParams,
|
|
249
|
+
routesPaths
|
|
166
250
|
}) {
|
|
251
|
+
const filteredRoutesList = routesList.filter(
|
|
252
|
+
(routeName, index) => routesList.indexOf(routeName) === index
|
|
253
|
+
);
|
|
254
|
+
const filteredRoutesParams = routesParams.filter(
|
|
255
|
+
(route, index) => routesParams.findIndex((r) => route.name === r.name) === index
|
|
256
|
+
);
|
|
167
257
|
return (
|
|
168
258
|
/* typescript */
|
|
169
259
|
`
|
|
170
|
-
${createRoutesNamesListExport(
|
|
260
|
+
${createRoutesNamesListExport(filteredRoutesList)}
|
|
261
|
+
export type WithoutBracket<T extends string> = T extends \`:\${string}\` ? never : T;
|
|
171
262
|
|
|
172
|
-
${createRoutesParamsRecordExport(
|
|
263
|
+
${createRoutesParamsRecordExport(filteredRoutesParams)}
|
|
173
264
|
|
|
174
|
-
${createRoutesParamsRecordResolvedExport(
|
|
265
|
+
${createRoutesParamsRecordResolvedExport(filteredRoutesParams)}
|
|
175
266
|
|
|
176
|
-
${createRoutesNamedLocationsExport(
|
|
267
|
+
${createRoutesNamedLocationsExport(filteredRoutesParams)}
|
|
177
268
|
|
|
178
|
-
${createRoutesNamedLocationsResolvedExport(
|
|
269
|
+
${createRoutesNamedLocationsResolvedExport(filteredRoutesParams)}
|
|
179
270
|
|
|
180
271
|
export type RoutesNamesListRecord = ${routesDeclTemplate};
|
|
181
272
|
|
|
@@ -193,15 +284,10 @@ function returnIfTrue(condition, template, otherwise) {
|
|
|
193
284
|
}
|
|
194
285
|
return otherwise ?? "";
|
|
195
286
|
}
|
|
196
|
-
function returnIfFalse(condition, template, otherwise) {
|
|
197
|
-
if (!condition) {
|
|
198
|
-
return template;
|
|
199
|
-
}
|
|
200
|
-
return otherwise ?? "";
|
|
201
|
-
}
|
|
202
287
|
|
|
203
288
|
function createTypedRouterFile() {
|
|
204
289
|
const strictOptions = moduleOptionStore.getResolvedStrictOptions();
|
|
290
|
+
const { i18n, experimentalPathCheck } = moduleOptionStore;
|
|
205
291
|
return (
|
|
206
292
|
/* typescript */
|
|
207
293
|
`
|
|
@@ -220,8 +306,12 @@ function createTypedRouterFile() {
|
|
|
220
306
|
RoutesNamedLocationsResolved,
|
|
221
307
|
RoutesNamesList,
|
|
222
308
|
RoutesParamsRecord,
|
|
223
|
-
RoutesParamsRecordResolved
|
|
309
|
+
RoutesParamsRecordResolved,
|
|
224
310
|
} from './__routes';
|
|
311
|
+
${returnIfTrue(
|
|
312
|
+
experimentalPathCheck,
|
|
313
|
+
`import type {TypedPathParameter, RouteNameFromPath} from './__paths';`
|
|
314
|
+
)}
|
|
225
315
|
import type { HasOneRequiredParameter } from './__types_utils';
|
|
226
316
|
|
|
227
317
|
|
|
@@ -231,28 +321,28 @@ function createTypedRouterFile() {
|
|
|
231
321
|
* RouteLocationRaw with discrimanated name and params properties
|
|
232
322
|
* {@link RouteLocationRaw}
|
|
233
323
|
* */
|
|
234
|
-
export type TypedRouteLocationRaw =
|
|
324
|
+
export type TypedRouteLocationRaw<T extends string = string> =
|
|
235
325
|
| (Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params'> & RoutesNamedLocations)
|
|
236
|
-
|
|
326
|
+
| Omit<RouteLocationPathRaw, 'path'>
|
|
237
327
|
${returnIfTrue(
|
|
238
|
-
strictOptions.router.strictRouteLocation,
|
|
239
|
-
|
|
240
|
-
"| RouteLocationPathRaw"
|
|
328
|
+
experimentalPathCheck && !strictOptions.router.strictRouteLocation,
|
|
329
|
+
`& {path?: TypedPathParameter<T>}`
|
|
241
330
|
)}
|
|
331
|
+
${returnIfTrue(!experimentalPathCheck && !strictOptions.router.strictToArgument, ` | string`)}
|
|
242
332
|
;
|
|
243
333
|
|
|
244
334
|
|
|
245
335
|
/**
|
|
246
336
|
* Alternative version of {@link TypedRouteLocationRaw} but with a name generic
|
|
247
337
|
*/
|
|
248
|
-
export type TypedRouteLocationRawFromName<T extends RoutesNamesList> =
|
|
338
|
+
export type TypedRouteLocationRawFromName<T extends RoutesNamesList, P extends string = string> =
|
|
249
339
|
| (Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params'> & TypedLocationAsRelativeRaw<T>)
|
|
250
|
-
|
|
340
|
+
| Omit<RouteLocationPathRaw, 'path'>
|
|
251
341
|
${returnIfTrue(
|
|
252
|
-
strictOptions.router.strictRouteLocation,
|
|
253
|
-
|
|
254
|
-
"| RouteLocationPathRaw"
|
|
342
|
+
experimentalPathCheck && !strictOptions.router.strictRouteLocation,
|
|
343
|
+
`& {path?: TypedPathParameter<P>}`
|
|
255
344
|
)}
|
|
345
|
+
${returnIfTrue(!experimentalPathCheck && !strictOptions.router.strictToArgument, ` | string`)}
|
|
256
346
|
|
|
257
347
|
/**
|
|
258
348
|
* Generic providing inference and dynamic inclusion of \`params\` property
|
|
@@ -294,24 +384,36 @@ function createTypedRouterFile() {
|
|
|
294
384
|
* @param to - Raw route location to resolve
|
|
295
385
|
* @param currentLocation - Optional current location to resolve against
|
|
296
386
|
*/
|
|
297
|
-
resolve<T extends RoutesNamesList>(
|
|
298
|
-
to: TypedRouteLocationRawFromName<T>,
|
|
299
|
-
currentLocation?:
|
|
387
|
+
resolve<T extends RoutesNamesList, P extends string>(
|
|
388
|
+
to: TypedRouteLocationRawFromName<T, P>,
|
|
389
|
+
currentLocation?: TypedRouteLocationRaw
|
|
300
390
|
): TypedRouteLocationFromName<T>;
|
|
391
|
+
${returnIfTrue(
|
|
392
|
+
experimentalPathCheck && !strictOptions.router.strictToArgument,
|
|
393
|
+
`resolve<T extends string>(to: TypedPathParameter<T>, currentLocation?: TypedRouteLocationRaw): TypedRouteLocationFromName<RouteNameFromPath<T>>;`
|
|
394
|
+
)}
|
|
301
395
|
/**
|
|
302
396
|
* Programmatically navigate to a new URL by pushing an entry in the history
|
|
303
397
|
* stack.
|
|
304
398
|
*
|
|
305
399
|
* @param to - Route location to navigate to
|
|
306
400
|
*/
|
|
307
|
-
push(to:
|
|
401
|
+
push<T extends RoutesNamesList, P extends string>(to: TypedRouteLocationRawFromName<T, P>): Promise<NavigationFailure | void | undefined>;
|
|
402
|
+
${returnIfTrue(
|
|
403
|
+
experimentalPathCheck && !strictOptions.router.strictToArgument,
|
|
404
|
+
`push<T extends string>(to: TypedPathParameter<T>): Promise<NavigationFailure | void | undefined>;`
|
|
405
|
+
)}
|
|
308
406
|
/**
|
|
309
407
|
* Programmatically navigate to a new URL by replacing the current entry in
|
|
310
408
|
* the history stack.
|
|
311
409
|
*
|
|
312
410
|
* @param to - Route location to navigate to
|
|
313
411
|
*/
|
|
314
|
-
replace(to:
|
|
412
|
+
replace<T extends RoutesNamesList, P extends string>(to: TypedRouteLocationRawFromName<T, P>): Promise<NavigationFailure | void | undefined>;
|
|
413
|
+
${returnIfTrue(
|
|
414
|
+
experimentalPathCheck && !strictOptions.router.strictToArgument,
|
|
415
|
+
`replace<T extends string>(to: TypedPathParameter<T>): Promise<NavigationFailure | void | undefined>;`
|
|
416
|
+
)}
|
|
315
417
|
}
|
|
316
418
|
|
|
317
419
|
|
|
@@ -354,21 +456,25 @@ function createTypedRouterFile() {
|
|
|
354
456
|
}
|
|
355
457
|
|
|
356
458
|
function createTypedRouterDefinitionFile() {
|
|
357
|
-
const { plugin, autoImport, i18n } = moduleOptionStore;
|
|
358
459
|
const strictOptions = moduleOptionStore.getResolvedStrictOptions();
|
|
460
|
+
const { plugin, autoImport, i18n, experimentalPathCheck } = moduleOptionStore;
|
|
359
461
|
return (
|
|
360
462
|
/* typescript */
|
|
361
463
|
`
|
|
362
464
|
|
|
363
|
-
import type { NuxtLinkProps } from '#app';
|
|
364
|
-
import
|
|
465
|
+
import type { NuxtLinkProps, PageMeta } from '#app';
|
|
466
|
+
import NuxtLink from 'nuxt/dist/app/components/nuxt-link';
|
|
365
467
|
import type { RouteLocationRaw, RouteLocationPathRaw } from 'vue-router';
|
|
366
|
-
import type {
|
|
367
|
-
import type {TypedRouter, TypedRoute} from './__router';
|
|
468
|
+
import type { RoutesNamedLocations, RoutesNamesListRecord, RoutesNamesList } from './__routes';
|
|
469
|
+
import type {TypedRouter, TypedRoute, TypedRouteLocationRawFromName, TypedLocationAsRelativeRaw} from './__router';
|
|
368
470
|
import { useRoute as _useRoute } from './__useTypedRoute';
|
|
369
471
|
import { useRouter as _useRouter } from './__useTypedRouter';
|
|
370
472
|
import { navigateTo as _navigateTo } from './__navigateTo';
|
|
371
473
|
import { useLocalePath as _useLocalePath, useLocaleRoute as _useLocaleRoute} from './__i18n-router';
|
|
474
|
+
import {definePageMeta as _definePageMeta} from './__definePageMeta';
|
|
475
|
+
|
|
476
|
+
${returnIfTrue(experimentalPathCheck, `import type {TypedPathParameter} from './__paths';`)}
|
|
477
|
+
|
|
372
478
|
|
|
373
479
|
declare global {
|
|
374
480
|
|
|
@@ -379,6 +485,8 @@ function createTypedRouterDefinitionFile() {
|
|
|
379
485
|
const useRoute: typeof _useRoute;
|
|
380
486
|
const useRouter: typeof _useRouter;
|
|
381
487
|
const navigateTo: typeof _navigateTo;
|
|
488
|
+
const definePageMeta: typeof _definePageMeta;
|
|
489
|
+
|
|
382
490
|
${returnIfTrue(
|
|
383
491
|
i18n,
|
|
384
492
|
/* typescript */
|
|
@@ -391,33 +499,33 @@ function createTypedRouterDefinitionFile() {
|
|
|
391
499
|
)}
|
|
392
500
|
}
|
|
393
501
|
|
|
394
|
-
type TypedNuxtLinkProps = Omit<NuxtLinkProps, 'to'> &
|
|
502
|
+
type TypedNuxtLinkProps<T extends string> = Omit<NuxtLinkProps, 'to'> &
|
|
503
|
+
{
|
|
395
504
|
to:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
strictOptions.NuxtLink.strictRouteLocation,
|
|
400
|
-
|
|
401
|
-
"| RouteLocationPathRaw"
|
|
505
|
+
| Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params'> & RoutesNamedLocations
|
|
506
|
+
| Omit<RouteLocationPathRaw, 'path'>
|
|
507
|
+
${returnIfTrue(
|
|
508
|
+
experimentalPathCheck && !strictOptions.NuxtLink.strictRouteLocation,
|
|
509
|
+
`& {path?: TypedPathParameter<T>}`
|
|
402
510
|
)}
|
|
403
|
-
|
|
511
|
+
${returnIfTrue(
|
|
512
|
+
!experimentalPathCheck && !strictOptions.NuxtLink.strictToArgument,
|
|
513
|
+
` | string`
|
|
514
|
+
)}
|
|
515
|
+
${returnIfTrue(
|
|
516
|
+
experimentalPathCheck && !strictOptions.NuxtLink.strictToArgument,
|
|
517
|
+
` | TypedPathParameter<T>`
|
|
518
|
+
)}
|
|
519
|
+
}
|
|
404
520
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
{},
|
|
414
|
-
string,
|
|
415
|
-
import('vue').VNodeProps &
|
|
416
|
-
import('vue').AllowedComponentProps &
|
|
417
|
-
import('vue').ComponentCustomProps,
|
|
418
|
-
Readonly<TypedNuxtLinkProps>,
|
|
419
|
-
{}
|
|
420
|
-
>;
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
export type TypedNuxtLink = new <P extends string>(props: TypedNuxtLinkProps<P>) => Omit<
|
|
524
|
+
typeof NuxtLink,
|
|
525
|
+
'$props'
|
|
526
|
+
> & {
|
|
527
|
+
$props: TypedNuxtLinkProps<P>;
|
|
528
|
+
};
|
|
421
529
|
|
|
422
530
|
declare module '@vue/runtime-core' {
|
|
423
531
|
export interface GlobalComponents {
|
|
@@ -447,7 +555,7 @@ function createTypedRouterDefinitionFile() {
|
|
|
447
555
|
}
|
|
448
556
|
|
|
449
557
|
function createIndexFile() {
|
|
450
|
-
const { i18n } = moduleOptionStore;
|
|
558
|
+
const { i18n, experimentalPathCheck } = moduleOptionStore;
|
|
451
559
|
return (
|
|
452
560
|
/* typescript */
|
|
453
561
|
`
|
|
@@ -469,11 +577,34 @@ function createIndexFile() {
|
|
|
469
577
|
RoutesNamesList,
|
|
470
578
|
RoutesNamesListRecord,
|
|
471
579
|
RoutesParamsRecord,
|
|
580
|
+
RoutePath,
|
|
581
|
+
RoutePathByName
|
|
472
582
|
} from './__routes';
|
|
473
583
|
export { useRoute } from './__useTypedRoute';
|
|
474
584
|
export { useRouter } from './__useTypedRouter';
|
|
475
585
|
export { navigateTo } from './__navigateTo';
|
|
476
|
-
|
|
586
|
+
export { definePageMeta } from './__definePageMeta';
|
|
587
|
+
|
|
588
|
+
${returnIfTrue(
|
|
589
|
+
experimentalPathCheck,
|
|
590
|
+
`export type { ValidatePath, RoutePathSchema, TypedPathParameter, RouteNameFromPath } from './__paths';`
|
|
591
|
+
)}
|
|
592
|
+
${returnIfTrue(i18n, `export {useLocalePath, useLocaleRoute} from './__i18n-router';`)}
|
|
593
|
+
|
|
594
|
+
export const helpers = {
|
|
595
|
+
route(
|
|
596
|
+
to: TypedRouteLocationRawFromName<T, P>,
|
|
597
|
+
): [T] extends [never] ? string : Required<
|
|
598
|
+
(Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params'> & TypedLocationAsRelativeRaw<T>)
|
|
599
|
+
> {
|
|
600
|
+
return to;
|
|
601
|
+
},
|
|
602
|
+
path(
|
|
603
|
+
to: TypedPathParameter<T>,
|
|
604
|
+
) : [T] extends [never] ? string : Required<TypedRouteLocationRawFromName<RouteNameFromPath<T>, T>> {
|
|
605
|
+
return to;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
477
608
|
`
|
|
478
609
|
);
|
|
479
610
|
}
|
|
@@ -502,7 +633,7 @@ function createPluginFile() {
|
|
|
502
633
|
);
|
|
503
634
|
}
|
|
504
635
|
|
|
505
|
-
function createUseTypedRouteFile(
|
|
636
|
+
function createUseTypedRouteFile() {
|
|
506
637
|
return (
|
|
507
638
|
/* typescript */
|
|
508
639
|
`
|
|
@@ -542,7 +673,7 @@ function createUseTypedRouteFile(routesDeclTemplate) {
|
|
|
542
673
|
);
|
|
543
674
|
}
|
|
544
675
|
|
|
545
|
-
function createUseTypedRouterFile(
|
|
676
|
+
function createUseTypedRouterFile() {
|
|
546
677
|
return (
|
|
547
678
|
/* typescript */
|
|
548
679
|
`
|
|
@@ -570,14 +701,20 @@ function createUseTypedRouterFile(routesDeclTemplate) {
|
|
|
570
701
|
}
|
|
571
702
|
|
|
572
703
|
function createNavigateToFile() {
|
|
704
|
+
const { router } = moduleOptionStore.getResolvedStrictOptions();
|
|
705
|
+
const { experimentalPathCheck } = moduleOptionStore;
|
|
573
706
|
return (
|
|
574
707
|
/* typescript */
|
|
575
708
|
`
|
|
576
709
|
import { navigateTo as defaultNavigateTo } from '#app';
|
|
577
710
|
import type { NavigateToOptions } from 'nuxt/dist/app/composables/router';
|
|
578
711
|
import type { NavigationFailure } from 'vue-router';
|
|
579
|
-
import type { TypedRouteLocationRawFromName, TypedRouteFromName } from './__router';
|
|
712
|
+
import type { TypedRouteLocationRawFromName, TypedRouteFromName, TypedRoute } from './__router';
|
|
580
713
|
import type { RoutesNamesList } from './__routes';
|
|
714
|
+
${returnIfTrue(
|
|
715
|
+
experimentalPathCheck,
|
|
716
|
+
`import type {TypedPathParameter, RouteNameFromPath} from './__paths';`
|
|
717
|
+
)}
|
|
581
718
|
|
|
582
719
|
/**
|
|
583
720
|
* Typed clone of \`navigateTo\`
|
|
@@ -588,10 +725,22 @@ function createNavigateToFile() {
|
|
|
588
725
|
* const resolved = navigateTo({name: 'foo', params: {foo: 'bar'}});
|
|
589
726
|
* \`\`\`
|
|
590
727
|
*/
|
|
591
|
-
|
|
592
|
-
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
interface NavigateToFunction {
|
|
731
|
+
<T extends RoutesNamesList, P extends string>(
|
|
732
|
+
to: TypedRouteLocationRawFromName<T, P>,
|
|
593
733
|
options?: NavigateToOptions
|
|
594
|
-
)
|
|
734
|
+
) : Promise<void | NavigationFailure | TypedRouteFromName<T>>
|
|
735
|
+
${returnIfTrue(
|
|
736
|
+
experimentalPathCheck && !router.strictToArgument,
|
|
737
|
+
`<T extends string>(
|
|
738
|
+
to: TypedPathParameter<T>,
|
|
739
|
+
options?: NavigateToOptions
|
|
740
|
+
) : Promise<void | NavigationFailure | TypedRouteFromName<RouteNameFromPath<T>>>`
|
|
741
|
+
)}
|
|
742
|
+
}
|
|
743
|
+
export const navigateTo: NavigateToFunction = defaultNavigateTo as any;
|
|
595
744
|
|
|
596
745
|
`
|
|
597
746
|
);
|
|
@@ -622,27 +771,49 @@ function createTypeUtilsRuntimeFile() {
|
|
|
622
771
|
}
|
|
623
772
|
|
|
624
773
|
function createi18nRouterFile() {
|
|
625
|
-
const {
|
|
774
|
+
const { router } = moduleOptionStore.getResolvedStrictOptions();
|
|
775
|
+
const { i18nLocales, experimentalPathCheck } = moduleOptionStore;
|
|
626
776
|
return (
|
|
627
777
|
/* typescript */
|
|
628
778
|
`
|
|
629
|
-
|
|
779
|
+
import type { RouteLocationRaw } from 'vue-router';
|
|
630
780
|
import { useLocalePath as _useLocalePath, useLocaleRoute as _useLocaleRoute} from 'vue-i18n-routing';
|
|
631
|
-
import type {TypedRouteLocationRawFromName,
|
|
781
|
+
import type {TypedRouteLocationRawFromName, TypedLocationAsRelativeRaw, TypedRouteFromName} from './__router';
|
|
632
782
|
import type {RoutesNamesList} from './__routes';
|
|
783
|
+
${returnIfTrue(
|
|
784
|
+
experimentalPathCheck,
|
|
785
|
+
`import type {TypedPathParameter, RouteNameFromPath} from './__paths';`
|
|
786
|
+
)}
|
|
633
787
|
|
|
634
788
|
export type I18nLocales = ${i18nLocales.length ? i18nLocales.map((loc) => `"${loc}"`).join("|") : "string"};
|
|
635
789
|
|
|
636
|
-
export
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
790
|
+
export interface TypedToLocalePath {
|
|
791
|
+
<T extends RoutesNamesList, P extends string>(
|
|
792
|
+
to: TypedRouteLocationRawFromName<T, P>,
|
|
793
|
+
locale?: I18nLocales | undefined
|
|
794
|
+
) : [T] extends [never] ? string : Required<
|
|
795
|
+
(Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params'> & TypedLocationAsRelativeRaw<T>)
|
|
796
|
+
>
|
|
797
|
+
${returnIfTrue(
|
|
798
|
+
experimentalPathCheck && !router.strictToArgument,
|
|
799
|
+
`<T extends string>(
|
|
800
|
+
to: TypedPathParameter<T>,
|
|
801
|
+
locale?: I18nLocales | undefined
|
|
802
|
+
) : [T] extends [never] ? string : Required<TypedRouteLocationRawFromName<RouteNameFromPath<T>, T>>;`
|
|
803
|
+
)}
|
|
804
|
+
}
|
|
640
805
|
|
|
641
806
|
export function useLocalePath(options?: Pick<NonNullable<Parameters<typeof _useLocalePath>[0]>, 'i18n'>): TypedToLocalePath {
|
|
642
807
|
return _useLocalePath(options) as any;
|
|
643
808
|
}
|
|
644
809
|
|
|
645
|
-
export
|
|
810
|
+
export interface TypedLocaleRoute {
|
|
811
|
+
<T extends RoutesNamesList, P extends string>(to: TypedRouteLocationRawFromName<T, P>, locale?: I18nLocales | undefined) : TypedRouteFromName<T>
|
|
812
|
+
${returnIfTrue(
|
|
813
|
+
experimentalPathCheck && !router.strictToArgument,
|
|
814
|
+
` <T extends string>(to: TypedPathParameter<T>, locale?: I18nLocales | undefined) : TypedRouteFromName<RouteNameFromPath<T>>;`
|
|
815
|
+
)}
|
|
816
|
+
}
|
|
646
817
|
|
|
647
818
|
|
|
648
819
|
export function useLocaleRoute(options?: Pick<NonNullable<Parameters<typeof _useLocaleRoute>[0]>, 'i18n'>): TypedLocaleRoute {
|
|
@@ -653,6 +824,283 @@ function createi18nRouterFile() {
|
|
|
653
824
|
);
|
|
654
825
|
}
|
|
655
826
|
|
|
827
|
+
const routeParamExtractRegxp = /(:(\w+)(\(.+\)[*+]?)?(\?)?)+/g;
|
|
828
|
+
function extractParamsFromPathDecl(path) {
|
|
829
|
+
let params = [];
|
|
830
|
+
let matches;
|
|
831
|
+
do {
|
|
832
|
+
matches = routeParamExtractRegxp.exec(path);
|
|
833
|
+
if (matches) {
|
|
834
|
+
const [_, mtch, key, catchAll, optional] = matches;
|
|
835
|
+
if (mtch) {
|
|
836
|
+
const _param = {
|
|
837
|
+
name: key,
|
|
838
|
+
optional: !!optional,
|
|
839
|
+
catchAll: !!catchAll
|
|
840
|
+
};
|
|
841
|
+
params.push(_param);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
} while (matches);
|
|
845
|
+
return params;
|
|
846
|
+
}
|
|
847
|
+
function replaceParamsFromPathDecl(path) {
|
|
848
|
+
const replacedPath = path.replace(routeParamExtractRegxp, (_, mtch, key, catchAll, optional) => {
|
|
849
|
+
return catchAll ? `\${((string | ''))}` : `\${(string ${optional ? "| ''" : ""})}`;
|
|
850
|
+
});
|
|
851
|
+
return replacedPath;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function extractRouteParamsFromPath(path, isIndexFileForRouting, previousParams) {
|
|
855
|
+
const params = extractParamsFromPathDecl(path);
|
|
856
|
+
let allMergedParams = params.map(
|
|
857
|
+
({ name, optional, catchAll }) => ({
|
|
858
|
+
key: name,
|
|
859
|
+
required: !optional,
|
|
860
|
+
notRequiredOnPage: optional,
|
|
861
|
+
catchAll
|
|
862
|
+
})
|
|
863
|
+
);
|
|
864
|
+
if (previousParams?.length) {
|
|
865
|
+
allMergedParams = previousParams.map((m) => ({ ...m, required: false })).concat(allMergedParams);
|
|
866
|
+
}
|
|
867
|
+
if (!params.length && isIndexFileForRouting) {
|
|
868
|
+
const lastItem = allMergedParams[allMergedParams.length - 1];
|
|
869
|
+
if (lastItem) {
|
|
870
|
+
lastItem.required = true;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return allMergedParams;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const ExtractRegex = /(^(\/)?([^:/]+)?(:(\w+)(\(.+\)[*+]?)?(\?)?)*([^:/]+)?)+/g;
|
|
877
|
+
function destructurePath(path, fullPath, routeName) {
|
|
878
|
+
let allPathElements = [];
|
|
879
|
+
let _path = `${path}`;
|
|
880
|
+
do {
|
|
881
|
+
const { pathElements, strippedPath } = extractPathElements(_path, fullPath, routeName);
|
|
882
|
+
allPathElements = allPathElements.concat(pathElements);
|
|
883
|
+
_path = _path.replace(strippedPath, "");
|
|
884
|
+
} while (_path.length);
|
|
885
|
+
return allPathElements;
|
|
886
|
+
}
|
|
887
|
+
function extractPathElements(partOfPath, fullPath, routeName) {
|
|
888
|
+
let pathElements = [];
|
|
889
|
+
let strippedPath = "";
|
|
890
|
+
let matches;
|
|
891
|
+
matches = ExtractRegex.exec(partOfPath);
|
|
892
|
+
if (matches) {
|
|
893
|
+
const [_, mtch, slash, path1, paramDef, key, catchAll, optional, path2] = matches;
|
|
894
|
+
if (mtch) {
|
|
895
|
+
strippedPath = mtch;
|
|
896
|
+
if (path1) {
|
|
897
|
+
pathElements.push({ type: "name", content: path1, fullPath, id: nanoid$1(6), routeName });
|
|
898
|
+
}
|
|
899
|
+
if (key) {
|
|
900
|
+
pathElements.push({
|
|
901
|
+
type: catchAll ? "catchAll" : optional ? "optionalParam" : "param",
|
|
902
|
+
content: key,
|
|
903
|
+
fullPath,
|
|
904
|
+
id: nanoid$1(6),
|
|
905
|
+
routeName
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
if (path2) {
|
|
909
|
+
pathElements.push({ type: "name", content: path2, fullPath, id: nanoid$1(6), routeName });
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return { pathElements, strippedPath };
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function createPathsFiles({ routesPaths }) {
|
|
917
|
+
const filteredRoutesPaths = routesPaths.filter(
|
|
918
|
+
(route, index) => routesPaths.findIndex((r) => route.name === r.name) === index && !routesPaths.find((r) => `${route.path}/` === r.path)
|
|
919
|
+
).sort((a, b) => {
|
|
920
|
+
const pathCountA = a.path.split("/");
|
|
921
|
+
const pathCountB = b.path.split("/");
|
|
922
|
+
pathCountA.splice(0, 1);
|
|
923
|
+
pathCountB.splice(0, 1);
|
|
924
|
+
const maxIndex = Math.max(pathCountA.length, pathCountB.length) - 1;
|
|
925
|
+
let order = 0;
|
|
926
|
+
let index = 0;
|
|
927
|
+
let alphabetOrder;
|
|
928
|
+
let hasElement;
|
|
929
|
+
let hasParam;
|
|
930
|
+
let indexOfParam;
|
|
931
|
+
do {
|
|
932
|
+
alphabetOrder = pathCountA[index]?.localeCompare(pathCountB[index]);
|
|
933
|
+
hasElement = (pathCountA[index] != null ? 1 : 0) - (pathCountB[index] != null ? 1 : 0);
|
|
934
|
+
hasParam = (pathCountA[index]?.includes(":") ? 1 : 0) - (pathCountB[index]?.includes(":") ? 1 : 0);
|
|
935
|
+
indexOfParam = pathCountB[index]?.indexOf(":") - pathCountA[index]?.indexOf(":");
|
|
936
|
+
if (alphabetOrder !== 0 && index === 0) {
|
|
937
|
+
order = alphabetOrder;
|
|
938
|
+
break;
|
|
939
|
+
} else {
|
|
940
|
+
if (hasElement !== 0) {
|
|
941
|
+
order = hasElement;
|
|
942
|
+
break;
|
|
943
|
+
} else if (hasParam !== 0) {
|
|
944
|
+
order = hasParam;
|
|
945
|
+
break;
|
|
946
|
+
} else if (hasParam === 0 && indexOfParam !== 0) {
|
|
947
|
+
order = indexOfParam;
|
|
948
|
+
break;
|
|
949
|
+
} else if (alphabetOrder !== 0) {
|
|
950
|
+
order = alphabetOrder;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
index = index + 1;
|
|
955
|
+
} while (index < maxIndex);
|
|
956
|
+
return order;
|
|
957
|
+
});
|
|
958
|
+
const pathElements = filteredRoutesPaths.filter((f) => f.path && f.path !== "/").map((route) => {
|
|
959
|
+
return route.path.split("/").filter((f) => f.length).map((m) => destructurePath(m, route.path, route.name));
|
|
960
|
+
});
|
|
961
|
+
const validatePathTypes = createValidatePathTypes(pathElements);
|
|
962
|
+
return (
|
|
963
|
+
/* typescript */
|
|
964
|
+
`
|
|
965
|
+
|
|
966
|
+
${createRoutePathSchema(filteredRoutesPaths)};
|
|
967
|
+
|
|
968
|
+
type ValidStringPath<T> = T extends \`\${string} \${string}\` ? false : T extends '' ? false : true;
|
|
969
|
+
|
|
970
|
+
type ValidParam<T, R extends boolean = true> = T extends \`\${infer A}/\${infer B}\`
|
|
971
|
+
? A extends \`\${string} \${string}\`
|
|
972
|
+
? false
|
|
973
|
+
: A extends \`?\${string}\`
|
|
974
|
+
? false
|
|
975
|
+
: A extends \`\${string} \${string}\`
|
|
976
|
+
? false
|
|
977
|
+
: A extends ''
|
|
978
|
+
? B extends ''
|
|
979
|
+
? true
|
|
980
|
+
: false
|
|
981
|
+
: B extends \`?\${string}\`
|
|
982
|
+
? false
|
|
983
|
+
: B extends \`#\${string}\`
|
|
984
|
+
? true
|
|
985
|
+
: B extends ''
|
|
986
|
+
? true
|
|
987
|
+
: false
|
|
988
|
+
: R extends true
|
|
989
|
+
? T extends ''
|
|
990
|
+
? false
|
|
991
|
+
: ValidParam<T, false>
|
|
992
|
+
: T extends \`?\${string}\`
|
|
993
|
+
? false
|
|
994
|
+
: T extends \`\${string} \${string}\`
|
|
995
|
+
? false
|
|
996
|
+
: true;
|
|
997
|
+
|
|
998
|
+
type ValidEndOfPath<T> = T extends \`/\`
|
|
999
|
+
? true
|
|
1000
|
+
: T extends ''
|
|
1001
|
+
? true
|
|
1002
|
+
: T extends \`\${string} \${string}\`
|
|
1003
|
+
? false
|
|
1004
|
+
: T extends \`?\${string}\`
|
|
1005
|
+
? true
|
|
1006
|
+
: T extends \`#\${string}\`
|
|
1007
|
+
? true
|
|
1008
|
+
: false;
|
|
1009
|
+
|
|
1010
|
+
${validatePathTypes}
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
export type TypedPathParameter<T extends string> = ValidatePath<T> | RoutePathSchema
|
|
1014
|
+
|
|
1015
|
+
`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function createDefinePageMetaFile() {
|
|
1020
|
+
const strictOptions = moduleOptionStore.getResolvedStrictOptions();
|
|
1021
|
+
const { experimentalPathCheck } = moduleOptionStore;
|
|
1022
|
+
return (
|
|
1023
|
+
/* typescript */
|
|
1024
|
+
`
|
|
1025
|
+
|
|
1026
|
+
import { definePageMeta as defaultDefinePageMeta } from '#imports';
|
|
1027
|
+
import type {PageMeta, NuxtError} from '#app'
|
|
1028
|
+
import type {TypedRouteFromName, TypedRoute, TypedRouteLocationRawFromName} from './__router';
|
|
1029
|
+
import type {RoutesNamesList, RoutesNamedLocations} from './__routes';
|
|
1030
|
+
${returnIfTrue(experimentalPathCheck, `import type {TypedPathParameter} from './__paths';`)}
|
|
1031
|
+
|
|
1032
|
+
type FilteredPageMeta = {
|
|
1033
|
+
[T in keyof PageMeta as [unknown] extends [PageMeta[T]] ? never : T]: PageMeta[T];
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
export type TypedPageMeta<T extends RoutesNamesList, P extends string, U extends RoutesNamesList> = Omit<FilteredPageMeta, 'redirect' | 'validate' | 'key'> & {
|
|
1037
|
+
/**
|
|
1038
|
+
* Validate whether a given route can validly be rendered with this page.
|
|
1039
|
+
*
|
|
1040
|
+
* Return true if it is valid, or false if not. If another match can't be found,
|
|
1041
|
+
* this will mean a 404. You can also directly return an object with
|
|
1042
|
+
* statusCode/statusMessage to respond immediately with an error (other matches
|
|
1043
|
+
* will not be checked).
|
|
1044
|
+
*/
|
|
1045
|
+
validate?: (route: [T] extends [never] ? TypedRoute : TypedRouteFromName<T>) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>;
|
|
1046
|
+
/**
|
|
1047
|
+
* Where to redirect if the route is directly matched. The redirection happens
|
|
1048
|
+
* before any navigation guard and triggers a new navigation with the new
|
|
1049
|
+
* target location.
|
|
1050
|
+
*/
|
|
1051
|
+
redirect?:
|
|
1052
|
+
| TypedRouteLocationRawFromName<U, P>
|
|
1053
|
+
| ((to: [T] extends [never] ? TypedRoute : TypedRouteFromName<T>)
|
|
1054
|
+
=> TypedRouteLocationRawFromName<any, P> ${returnIfTrue(
|
|
1055
|
+
experimentalPathCheck && !strictOptions.router.strictToArgument,
|
|
1056
|
+
` | TypedPathParameter<P>`
|
|
1057
|
+
)})
|
|
1058
|
+
${returnIfTrue(
|
|
1059
|
+
experimentalPathCheck && !strictOptions.router.strictToArgument,
|
|
1060
|
+
` | TypedPathParameter<P>`
|
|
1061
|
+
)}
|
|
1062
|
+
key?: false | string | ((route: [T] extends [never] ? TypedRoute : TypedRouteFromName<T>) => string);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Typed clone of \`definePageMeta\`
|
|
1068
|
+
*
|
|
1069
|
+
* \u26A0\uFE0F Types for the redirect function may be buggy or not display autocomplete
|
|
1070
|
+
*
|
|
1071
|
+
* @exemple
|
|
1072
|
+
*
|
|
1073
|
+
* \`\`\`ts
|
|
1074
|
+
* definePageMeta('current-location-name', {
|
|
1075
|
+
* validate(route) {
|
|
1076
|
+
* });
|
|
1077
|
+
* // or
|
|
1078
|
+
* definePageMeta({
|
|
1079
|
+
* validate(route) {
|
|
1080
|
+
* });
|
|
1081
|
+
* \`\`\`
|
|
1082
|
+
*/
|
|
1083
|
+
export function definePageMeta<
|
|
1084
|
+
P extends string,
|
|
1085
|
+
U extends RoutesNamesList
|
|
1086
|
+
>(meta: TypedPageMeta<never, P, U>): void;
|
|
1087
|
+
export function definePageMeta<
|
|
1088
|
+
T extends RoutesNamesList,
|
|
1089
|
+
P extends string,
|
|
1090
|
+
U extends RoutesNamesList
|
|
1091
|
+
>(routeName: T, meta: TypedPageMeta<T, P, U>): void;
|
|
1092
|
+
export function definePageMeta(metaOrName: any, meta?: any): void {
|
|
1093
|
+
if (typeof metaOrName === 'string') {
|
|
1094
|
+
return defaultDefinePageMeta(meta as any);
|
|
1095
|
+
} else {
|
|
1096
|
+
return defaultDefinePageMeta(metaOrName as any);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
656
1104
|
async function handleAddPlugin() {
|
|
657
1105
|
const pluginName = "__typed-router.plugin.ts";
|
|
658
1106
|
addPluginTemplate({
|
|
@@ -735,9 +1183,8 @@ const watermarkTemplate = `
|
|
|
735
1183
|
`;
|
|
736
1184
|
|
|
737
1185
|
let previousGeneratedRoutes = "";
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}) {
|
|
1186
|
+
let firstRun = false;
|
|
1187
|
+
async function saveGeneratedFiles({ outputData }) {
|
|
741
1188
|
const { i18n } = moduleOptionStore;
|
|
742
1189
|
const filesMap = [
|
|
743
1190
|
{
|
|
@@ -748,19 +1195,22 @@ async function saveGeneratedFiles({
|
|
|
748
1195
|
fileName: "__useTypedRoute.ts",
|
|
749
1196
|
content: createUseTypedRouteFile()
|
|
750
1197
|
},
|
|
1198
|
+
{
|
|
1199
|
+
fileName: "__paths.d.ts",
|
|
1200
|
+
content: createPathsFiles(outputData)
|
|
1201
|
+
},
|
|
751
1202
|
{
|
|
752
1203
|
fileName: `__routes.ts`,
|
|
753
|
-
content: createRoutesTypesFile(
|
|
754
|
-
routesList,
|
|
755
|
-
routesObjectTemplate,
|
|
756
|
-
routesDeclTemplate,
|
|
757
|
-
routesParams
|
|
758
|
-
})
|
|
1204
|
+
content: createRoutesTypesFile(outputData)
|
|
759
1205
|
},
|
|
760
1206
|
{
|
|
761
1207
|
fileName: "__navigateTo.ts",
|
|
762
1208
|
content: createNavigateToFile()
|
|
763
1209
|
},
|
|
1210
|
+
{
|
|
1211
|
+
fileName: "__definePageMeta.ts",
|
|
1212
|
+
content: createDefinePageMetaFile()
|
|
1213
|
+
},
|
|
764
1214
|
{
|
|
765
1215
|
fileName: `__router.d.ts`,
|
|
766
1216
|
content: createTypedRouterFile()
|
|
@@ -794,57 +1244,21 @@ async function saveGeneratedFiles({
|
|
|
794
1244
|
return processPathAndWriteFile({ content: waterMakeredContent, fileName });
|
|
795
1245
|
})
|
|
796
1246
|
);
|
|
797
|
-
if (previousGeneratedRoutes !== routesList.join(",")) {
|
|
798
|
-
previousGeneratedRoutes = routesList.join(",");
|
|
1247
|
+
if (previousGeneratedRoutes !== outputData.routesList.join(",")) {
|
|
1248
|
+
previousGeneratedRoutes = outputData.routesList.join(",");
|
|
799
1249
|
console.log(logSymbols.success, `Router autocompletions generated \u{1F6A6}`);
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
matches = routeParamExtractRegxp.exec(path);
|
|
809
|
-
if (matches) {
|
|
810
|
-
const [_, mtch, key, catchAll, optional] = matches;
|
|
811
|
-
if (mtch) {
|
|
812
|
-
params.push({ name: key, optional: !!optional, catchAll: !!catchAll });
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
} while (matches);
|
|
816
|
-
let allMergedParams = params.map(
|
|
817
|
-
({ name, optional, catchAll }) => ({
|
|
818
|
-
key: name,
|
|
819
|
-
required: !optional,
|
|
820
|
-
notRequiredOnPage: optional,
|
|
821
|
-
catchAll
|
|
822
|
-
})
|
|
823
|
-
);
|
|
824
|
-
if (previousParams?.length) {
|
|
825
|
-
allMergedParams = previousParams.map((m) => ({ ...m, required: false })).concat(allMergedParams);
|
|
826
|
-
}
|
|
827
|
-
if (!params.length && isIndexFileForRouting) {
|
|
828
|
-
const lastItem = allMergedParams[allMergedParams.length - 1];
|
|
829
|
-
if (lastItem) {
|
|
830
|
-
lastItem.required = true;
|
|
1250
|
+
if (!firstRun) {
|
|
1251
|
+
firstRun = true;
|
|
1252
|
+
console.log(
|
|
1253
|
+
logSymbols.warning,
|
|
1254
|
+
chalk.yellow(
|
|
1255
|
+
`Route path autocomplete is still experimental. You can disable it with the "nuxtTypedRouter.experimentalPathCheck: false" option`
|
|
1256
|
+
)
|
|
1257
|
+
);
|
|
831
1258
|
}
|
|
832
1259
|
}
|
|
833
|
-
return allMergedParams;
|
|
834
1260
|
}
|
|
835
1261
|
|
|
836
|
-
function extractMatchingSiblings(mainRoute, siblingRoutes) {
|
|
837
|
-
return siblingRoutes?.filter((s) => {
|
|
838
|
-
const chunkName = extractChunkMain(mainRoute.file);
|
|
839
|
-
if (chunkName && s.name) {
|
|
840
|
-
const siblingChunkName = extractChunkMain(s.file);
|
|
841
|
-
if (!siblingChunkName)
|
|
842
|
-
return false;
|
|
843
|
-
return chunkName === siblingChunkName;
|
|
844
|
-
}
|
|
845
|
-
return false;
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
1262
|
function extractUnMatchingSiblings(mainRoute, siblingRoutes) {
|
|
849
1263
|
return siblingRoutes?.filter((s) => {
|
|
850
1264
|
const chunkName = extractChunkMain(mainRoute.file);
|
|
@@ -862,11 +1276,14 @@ function extractChunkMain(chunkName) {
|
|
|
862
1276
|
return chunkArray?.join("/");
|
|
863
1277
|
}
|
|
864
1278
|
|
|
865
|
-
function createKeyedName(route) {
|
|
1279
|
+
function createKeyedName(route, parent) {
|
|
866
1280
|
const splittedPaths = route.path.split("/");
|
|
867
1281
|
const parentPath = splittedPaths[splittedPaths.length - 1];
|
|
868
|
-
|
|
869
|
-
|
|
1282
|
+
if (parent) {
|
|
1283
|
+
return camelCase(parentPath || "index");
|
|
1284
|
+
} else {
|
|
1285
|
+
return camelCase(route.path.split("/").join("-") || "index");
|
|
1286
|
+
}
|
|
870
1287
|
}
|
|
871
1288
|
function createNameKeyFromFullName(route, level, parentName) {
|
|
872
1289
|
let splitted = [];
|
|
@@ -878,68 +1295,65 @@ function createNameKeyFromFullName(route, level, parentName) {
|
|
|
878
1295
|
const keyName = route.path === "" ? "index" : camelCase(splitted.join("-")) || "index";
|
|
879
1296
|
return keyName;
|
|
880
1297
|
}
|
|
1298
|
+
function hasi18nSibling(source, route) {
|
|
1299
|
+
return source.some((rt) => {
|
|
1300
|
+
return route.name?.match(new RegExp(`^(${rt.name})___[a-zA-Z]+`, "g")) || rt.path !== "/" && route.path?.match(new RegExp(`/?[a-zA-Z]+${rt.path}`, "g"));
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
881
1303
|
function walkThoughRoutes({
|
|
882
1304
|
route,
|
|
883
1305
|
level,
|
|
884
1306
|
siblings,
|
|
885
|
-
|
|
1307
|
+
parent,
|
|
886
1308
|
previousParams,
|
|
887
1309
|
output,
|
|
888
1310
|
isLast
|
|
889
1311
|
}) {
|
|
890
|
-
const matchingSiblings = extractMatchingSiblings(route, siblings);
|
|
891
|
-
const haveMatchingSiblings = !!matchingSiblings?.length && route.path !== "/";
|
|
892
1312
|
const chunkArray = route.file?.split("/") ?? [];
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
let nameKey = createKeyedName(route);
|
|
898
|
-
const hasSamePathI18nSibling = siblings?.some((sibling) => {
|
|
899
|
-
const _name = createKeyedName(sibling);
|
|
900
|
-
return _name === nameKey;
|
|
901
|
-
});
|
|
902
|
-
if (hasSamePathI18nSibling) {
|
|
903
|
-
nameKey = camelCase(route.path.split("/").join("-"));
|
|
904
|
-
}
|
|
905
|
-
output.routesObjectTemplate += `${nameKey}:{`;
|
|
906
|
-
output.routesDeclTemplate += `"${nameKey}":{`;
|
|
907
|
-
const allRouteParams = extractRouteParamsFromPath(route.path, false, previousParams);
|
|
908
|
-
childrenChunks?.map(
|
|
909
|
-
(routeConfig, index) => walkThoughRoutes({
|
|
910
|
-
route: routeConfig,
|
|
911
|
-
level: level + 1,
|
|
912
|
-
siblings: extractUnMatchingSiblings(route, siblings),
|
|
913
|
-
parentName: nameKey,
|
|
914
|
-
previousParams: allRouteParams,
|
|
915
|
-
output,
|
|
916
|
-
isLast: isItemLast(childrenChunks, index)
|
|
917
|
-
})
|
|
918
|
-
);
|
|
919
|
-
output.routesObjectTemplate += "},";
|
|
920
|
-
output.routesDeclTemplate += `}${isLast ? "" : ","}`;
|
|
921
|
-
} else if (route.name) {
|
|
922
|
-
let keyName = createNameKeyFromFullName(route, level, parentName);
|
|
923
|
-
const hasSameNameI18nSibling = siblings?.some((sibling) => {
|
|
924
|
-
const _name = createNameKeyFromFullName(sibling, level, parentName);
|
|
925
|
-
return _name === keyName;
|
|
926
|
-
});
|
|
927
|
-
if (hasSameNameI18nSibling) {
|
|
928
|
-
keyName = camelCase(route.name);
|
|
929
|
-
}
|
|
930
|
-
output.routesObjectTemplate += `'${keyName}': '${route.name}' as const,`;
|
|
931
|
-
output.routesDeclTemplate += `"${keyName}": "${route.name}"${isLast ? "" : ","}`;
|
|
932
|
-
output.routesList.push(route.name);
|
|
933
|
-
const isIndexFileForRouting = route.path === "";
|
|
934
|
-
const allRouteParams = extractRouteParamsFromPath(
|
|
935
|
-
route.path,
|
|
936
|
-
isIndexFileForRouting,
|
|
937
|
-
previousParams
|
|
938
|
-
);
|
|
939
|
-
output.routesParams.push({
|
|
1313
|
+
chunkArray[chunkArray?.length - 1].split(".vue")[0];
|
|
1314
|
+
if (!hasi18nSibling(output.routesPaths, route)) {
|
|
1315
|
+
const newPath = `${parent?.path ?? ""}${route.path.startsWith("/") ? route.path : `/${route.path}`}`;
|
|
1316
|
+
output.routesPaths.push({
|
|
940
1317
|
name: route.name,
|
|
941
|
-
|
|
1318
|
+
typePath: replaceParamsFromPathDecl(newPath),
|
|
1319
|
+
path: newPath
|
|
942
1320
|
});
|
|
1321
|
+
if (route.children?.length) {
|
|
1322
|
+
let childrenChunks = route.children;
|
|
1323
|
+
let nameKey = createKeyedName(route, parent);
|
|
1324
|
+
const allRouteParams = extractRouteParamsFromPath(route.path, false, previousParams);
|
|
1325
|
+
const newRoute = { ...route, name: nameKey, path: newPath };
|
|
1326
|
+
output.routesObjectTemplate += `${nameKey}:{`;
|
|
1327
|
+
output.routesDeclTemplate += `"${nameKey}":{`;
|
|
1328
|
+
childrenChunks?.map(
|
|
1329
|
+
(routeConfig, index) => walkThoughRoutes({
|
|
1330
|
+
route: routeConfig,
|
|
1331
|
+
level: level + 1,
|
|
1332
|
+
siblings: extractUnMatchingSiblings(route, siblings),
|
|
1333
|
+
parent: newRoute,
|
|
1334
|
+
previousParams: allRouteParams,
|
|
1335
|
+
output,
|
|
1336
|
+
isLast: isItemLast(childrenChunks, index)
|
|
1337
|
+
})
|
|
1338
|
+
);
|
|
1339
|
+
output.routesObjectTemplate += "},";
|
|
1340
|
+
output.routesDeclTemplate += `}${isLast ? "" : ","}`;
|
|
1341
|
+
} else if (route.name) {
|
|
1342
|
+
let keyName = createNameKeyFromFullName(route, level, parent?.name);
|
|
1343
|
+
output.routesObjectTemplate += `'${keyName}': '${route.name}' as const,`;
|
|
1344
|
+
output.routesDeclTemplate += `"${keyName}": "${route.name}"${isLast ? "" : ","}`;
|
|
1345
|
+
output.routesList.push(route.name);
|
|
1346
|
+
const isIndexFileForRouting = route.path === "";
|
|
1347
|
+
const allRouteParams = extractRouteParamsFromPath(
|
|
1348
|
+
route.path,
|
|
1349
|
+
isIndexFileForRouting,
|
|
1350
|
+
previousParams
|
|
1351
|
+
);
|
|
1352
|
+
output.routesParams.push({
|
|
1353
|
+
name: route.name,
|
|
1354
|
+
params: allRouteParams
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
943
1357
|
}
|
|
944
1358
|
}
|
|
945
1359
|
|
|
@@ -949,14 +1363,23 @@ function constructRouteMap(routesConfig) {
|
|
|
949
1363
|
let routesDeclTemplate = "{";
|
|
950
1364
|
let routesList = [];
|
|
951
1365
|
let routesParams = [];
|
|
952
|
-
|
|
1366
|
+
let routesPaths = [];
|
|
1367
|
+
let routesPathsTree = "{";
|
|
1368
|
+
const output = {
|
|
1369
|
+
routesObjectTemplate,
|
|
1370
|
+
routesDeclTemplate,
|
|
1371
|
+
routesList,
|
|
1372
|
+
routesParams,
|
|
1373
|
+
routesPaths,
|
|
1374
|
+
routesPathsTree
|
|
1375
|
+
};
|
|
953
1376
|
startGenerator({
|
|
954
1377
|
output,
|
|
955
1378
|
routesConfig
|
|
956
1379
|
});
|
|
957
1380
|
return output;
|
|
958
1381
|
} catch (e) {
|
|
959
|
-
throw new Error("Generation failed");
|
|
1382
|
+
throw new Error("Generation failed", e);
|
|
960
1383
|
}
|
|
961
1384
|
}
|
|
962
1385
|
function startGenerator({ output, routesConfig }) {
|
|
@@ -1034,7 +1457,8 @@ const module = defineNuxtModule({
|
|
|
1034
1457
|
},
|
|
1035
1458
|
defaults: {
|
|
1036
1459
|
plugin: false,
|
|
1037
|
-
strict: false
|
|
1460
|
+
strict: false,
|
|
1461
|
+
experimentalPathCheck: true
|
|
1038
1462
|
},
|
|
1039
1463
|
setup(moduleOptions, nuxt) {
|
|
1040
1464
|
const { resolve } = createResolver(import.meta.url);
|
|
@@ -1066,8 +1490,28 @@ const module = defineNuxtModule({
|
|
|
1066
1490
|
"@typed-router": resolve(`${rootDir}/.nuxt/typed-router`)
|
|
1067
1491
|
};
|
|
1068
1492
|
nuxt.options.typescript.tsConfig = {
|
|
1069
|
-
include: ["./typed-router/typed-router.d.ts"]
|
|
1493
|
+
include: ["./typed-router/typed-router.d.ts"],
|
|
1494
|
+
...moduleOptions.experimentalPathCheck && {
|
|
1495
|
+
// Enable Volar components generics https://github.com/vuejs/rfcs/discussions/436
|
|
1496
|
+
vueCompilerOptions: {
|
|
1497
|
+
jsxTemplates: true,
|
|
1498
|
+
experimentalRfc436: true
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1070
1501
|
};
|
|
1502
|
+
if (nuxt.options.dev) {
|
|
1503
|
+
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
1504
|
+
tabs.push({
|
|
1505
|
+
name: "nuxt-typed-router",
|
|
1506
|
+
title: "Nuxt Typed Router",
|
|
1507
|
+
icon: "https://github.com/victorgarciaesgi/nuxt-typed-router/blob/master/.github/images/logo.png?raw=true",
|
|
1508
|
+
view: {
|
|
1509
|
+
type: "iframe",
|
|
1510
|
+
src: "https://nuxt-typed-router.vercel.app/"
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1071
1515
|
createTypedRouter({ nuxt });
|
|
1072
1516
|
}
|
|
1073
1517
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-typed-router",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Provide autocompletion for
|
|
3
|
+
"version": "3.0.0-beta.0",
|
|
4
|
+
"description": "Provide autocompletion for paths, routes names and params in Nuxt apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/module.cjs",
|
|
7
7
|
"types": "./dist/types.d.ts",
|
|
@@ -22,13 +22,15 @@
|
|
|
22
22
|
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground && pnpm run test:prepare-fixtures",
|
|
23
23
|
"build:test": "cross-env NUXT_BUILD_TYPE=stub pnpm run prepack && pnpm run dev:build",
|
|
24
24
|
"test:prepare-fixtures": "nuxi prepare test/fixtures/simple && nuxi prepare test/fixtures/withOptions && nuxi prepare test/fixtures/complex",
|
|
25
|
-
"test:fixtures": "
|
|
26
|
-
"test:types": "pnpm run typecheck && pnpm run test:vue
|
|
25
|
+
"test:fixtures": "vitest run --dir test",
|
|
26
|
+
"test:types": "pnpm run typecheck && pnpm run test:vue",
|
|
27
27
|
"test:vue": "vue-tsc -p test/fixtures/simple/tsconfig.json --noEmit && vue-tsc -p test/fixtures/complex/tsconfig.json --noEmit && vue-tsc -p test/fixtures/withOptions/tsconfig.json --noEmit",
|
|
28
28
|
"test": "pnpm run dev:prepare && pnpm run test:types && pnpm run test:fixtures",
|
|
29
|
+
"lint": "eslint --ext .ts --ext .vue .",
|
|
29
30
|
"docs:dev": "cd docs && pnpm run dev",
|
|
30
31
|
"docs:build": "npm run dev:prepare && cd docs && nuxi generate",
|
|
31
|
-
"typecheck": "tsc --noEmit"
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"release": "pnpm test && standard-version && git push --follow-tags && npm publish"
|
|
32
34
|
},
|
|
33
35
|
"publishConfig": {
|
|
34
36
|
"access": "public"
|
|
@@ -57,37 +59,41 @@
|
|
|
57
59
|
"url": "https://github.com/victorgarciaesgi/nuxt-typed-router/issues"
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
|
60
|
-
"@nuxt/kit": "3.
|
|
62
|
+
"@nuxt/kit": "^3.2.0",
|
|
61
63
|
"chalk": "^5.2.0",
|
|
62
64
|
"defu": "^6.1.2",
|
|
63
65
|
"lodash-es": "^4.17.21",
|
|
64
66
|
"log-symbols": "^5.1.0",
|
|
65
|
-
"mkdirp": "^1.
|
|
67
|
+
"mkdirp": "^2.1.3",
|
|
68
|
+
"nanoid": "^4.0.1",
|
|
66
69
|
"pathe": "1.1.0",
|
|
67
|
-
"prettier": "2.8.
|
|
70
|
+
"prettier": "2.8.4"
|
|
68
71
|
},
|
|
69
72
|
"devDependencies": {
|
|
73
|
+
"@nuxt/devtools": "^0.1.0",
|
|
70
74
|
"@nuxt/module-builder": "^0.2.1",
|
|
71
|
-
"@nuxt/test-utils": "^3.
|
|
72
|
-
"@nuxt/types": "^2.
|
|
75
|
+
"@nuxt/test-utils": "^3.2.0",
|
|
76
|
+
"@nuxt/types": "^2.16.0",
|
|
73
77
|
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
|
74
|
-
"@nuxtjs/web-vitals": "^0.2.2",
|
|
75
78
|
"@nuxtjs/i18n": "8.0.0-beta.9",
|
|
79
|
+
"@nuxtjs/web-vitals": "^0.2.2",
|
|
76
80
|
"@types/lodash-es": "^4.17.6",
|
|
77
81
|
"@types/mkdirp": "^1.0.2",
|
|
78
|
-
"@types/node": "^
|
|
82
|
+
"@types/node": "^18.13.0",
|
|
79
83
|
"@types/prettier": "^2.7.2",
|
|
80
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
81
|
-
"@typescript-eslint/parser": "^5.
|
|
82
|
-
"@vue/test-utils": "^2.2.
|
|
84
|
+
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
|
85
|
+
"@typescript-eslint/parser": "^5.51.0",
|
|
86
|
+
"@vue/test-utils": "^2.2.10",
|
|
83
87
|
"cross-env": "^7.0.3",
|
|
84
88
|
"eslint": "8.33.0",
|
|
85
89
|
"eslint-config-prettier": "^8.6.0",
|
|
86
90
|
"eslint-plugin-vue": "^9.9.0",
|
|
87
|
-
"nuxt": "3.
|
|
91
|
+
"nuxt": "3.2.0",
|
|
88
92
|
"playwright": "1.30.0",
|
|
93
|
+
"standard-version": "^9.5.0",
|
|
94
|
+
"tsd": "^0.25.0",
|
|
89
95
|
"typescript": "^4.9.5",
|
|
90
|
-
"vitest": "^0.28.
|
|
96
|
+
"vitest": "^0.28.2",
|
|
91
97
|
"vue-eslint-parser": "^9.1.0",
|
|
92
98
|
"vue-router": "^4.1.6",
|
|
93
99
|
"vue-tsc": "^1.0.24"
|