nuxt-typed-router 2.1.0 โ†’ 2.1.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/README.md CHANGED
@@ -18,11 +18,11 @@
18
18
 
19
19
  ## Provide a type safe router to Nuxt with auto-generated typed definitions for route names and autocompletion for route params
20
20
 
21
- - Provides types check and autocomplete to `NuxtLink`
22
- - Provides types check for `useRouter` and `useRoute`
23
- - Supports routes defined in `config.extendRoutes`
24
- - Supports optional params and catchAll routes
25
- - Infer route params based on route name
21
+ - `NuxtLink` route autocomplete and params type-check
22
+ - `useRouter` and `useRoute` route autocomplete and params type-check
23
+ - Supports optional params and catchAll routes
24
+ - Infer route params based on route name
25
+ - Supports routes defined in `config.extendRoutes`
26
26
 
27
27
  <br/>
28
28
 
@@ -59,6 +59,8 @@ Demo repo ๐Ÿงช : [nuxt-typed-router-demo](https://github.com/victorgarciaesgi/nu
59
59
  yarn add -D nuxt-typed-router
60
60
  # or
61
61
  npm install -D nuxt-typed-router
62
+ # or
63
+ pnpm install -D nuxt-typed-router
62
64
  ```
63
65
 
64
66
  ### Nuxt 2 legacy
@@ -86,8 +88,8 @@ export default defineNuxtConfig({
86
88
 
87
89
  1. Clone this repository
88
90
  2. Install dependencies using `pnpm`
89
- 3. Build project for local tests `pnpm build:local`
90
- 4. Start dev playground `pnpm play`
91
+ 3. Build project for local tests `pnpm run test`
92
+ 4. Start dev playground `pnpm run prepack && pnpm run dev`
91
93
  5. Build project for deploy `pnpm prepack`
92
94
 
93
95
  ## ๐Ÿ“‘ License
package/dist/module.d.ts CHANGED
@@ -1,2 +1,13 @@
1
- export * from "/Users/victorgarcia/Desktop/projects/nuxt-typed-router/src/module";
2
- export { default } from "/Users/victorgarcia/Desktop/projects/nuxt-typed-router/src/module";
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ interface ModuleOptions {
4
+ /**
5
+ * Set to false if you don't want a plugin generated
6
+ * @default false
7
+ */
8
+ plugin?: boolean;
9
+ }
10
+
11
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
12
+
13
+ export { ModuleOptions, _default as default };
package/dist/module.json CHANGED
@@ -5,5 +5,5 @@
5
5
  "nuxt": "^3.0.0-rc.1",
6
6
  "bridge": false
7
7
  },
8
- "version": "2.1.0"
8
+ "version": "2.1.1"
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,6 +1,681 @@
1
- import jiti from "file:///Users/victorgarcia/Desktop/projects/nuxt-typed-router/node_modules/.pnpm/jiti@1.16.2/node_modules/jiti/lib/index.js";
1
+ import { extendPages, defineNuxtModule, createResolver } from '@nuxt/kit';
2
+ import chalk from 'chalk';
3
+ import logSymbols from 'log-symbols';
4
+ import prettier from 'prettier';
5
+ import fs from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, resolve } from 'pathe';
8
+ import mkdirp from 'mkdirp';
9
+ import { camelCase } from 'lodash-es';
2
10
 
3
- /** @type {import("/Users/victorgarcia/Desktop/projects/nuxt-typed-router/src/module")} */
4
- const _module = jiti(null, { interopDefault: true, esmResolve: true })("/Users/victorgarcia/Desktop/projects/nuxt-typed-router/src/module.ts");
11
+ const staticTypesImports = `
12
+ import type {
13
+ NavigationFailure,
14
+ RouteLocation,
15
+ RouteLocationNormalizedLoaded,
16
+ RouteLocationOptions,
17
+ RouteQueryAndHash,
18
+ RouteLocationRaw,
19
+ Router,
20
+ } from 'vue-router';
21
+ import type { DefineComponent } from 'vue';
22
+ import type { NuxtLinkProps } from '#app';
23
+ import type {
24
+ TypedRouteList,
25
+ TypedRouteNamedMapper,
26
+ TypedRouteParams,
27
+ ResolvedTypedRouteNamedMapper,
28
+ } from './__routes';
29
+ import {useRoute as _useRoute} from './__useTypedRoute';
30
+ import {useRouter as _useRouter} from './__useTypedRouter';
31
+ `;
5
32
 
6
- export default _module;
33
+ const watermarkTemplate = `
34
+ // @ts-nocheck
35
+ // eslint-disable
36
+ /**
37
+ * ---------------------------------------------------
38
+ * \u{1F697}\u{1F6A6} Generated by nuxt-typed-router. Do not modify !
39
+ * ---------------------------------------------------
40
+ * */
41
+
42
+ `;
43
+
44
+ function createRuntimeTypeUtils(autoImport) {
45
+ return `
46
+ // Type utils
47
+ type ExtractRequiredParameters<T extends Record<string, any>> = Pick<
48
+ T,
49
+ { [K in keyof T]: undefined extends T[K] ? never : K }[keyof T]
50
+ >;
51
+
52
+ type HasOneRequiredParameter<T extends TypedRouteList> = [TypedRouteParams[T]] extends [never]
53
+ ? false
54
+ : [keyof ExtractRequiredParameters<TypedRouteParams[T]>] extends [undefined]
55
+ ? false
56
+ : true;
57
+
58
+ type TypedLocationAsRelativeRaw<T extends TypedRouteList> = {
59
+ name?: T;
60
+ } & ([TypedRouteParams[T]] extends [never]
61
+ ? {}
62
+ : HasOneRequiredParameter<T> extends false
63
+ ? { params?: TypedRouteParams[T] }
64
+ : { params: TypedRouteParams[T] });
65
+
66
+ type ResolvedTypedLocationAsRelativeRaw<T extends TypedRouteList> = {
67
+ name?: T;
68
+ } & ([TypedRouteParams[T]] extends [never] ? {} : { params: TypedRouteParams[T] });
69
+
70
+ type TypedRouteLocationRaw = RouteQueryAndHash & TypedRouteNamedMapper & RouteLocationOptions;
71
+
72
+ type _TypedRoute = Omit<RouteLocationNormalizedLoaded, 'name' | 'params'> &
73
+ ResolvedTypedRouteNamedMapper;
74
+ type _TypedNamedRoute<T extends TypedRouteList> = Omit<
75
+ RouteLocationNormalizedLoaded,
76
+ 'name' | 'params'
77
+ > &
78
+ ResolvedTypedLocationAsRelativeRaw<T>;
79
+
80
+ /** Augmented Router interface */
81
+ interface _TypedRouter
82
+ extends Omit<Router, 'removeRoute' | 'hasRoute' | 'resolve' | 'push' | 'replace', 'currentRoute'> {
83
+ readonly currentRoute: _TypedRoute;
84
+ /**
85
+ * Remove an existing route by its name.
86
+ *
87
+ * @param name - Name of the route to remove
88
+ */
89
+ removeRoute(name: TypedRouteList): void;
90
+ /**
91
+ * Checks if a route with a given name exists
92
+ *
93
+ * @param name - Name of the route to check
94
+ */
95
+ hasRoute(name: TypedRouteList): boolean;
96
+ /**
97
+ * Returns the {@link RouteLocation | normalized version} of a
98
+ * {@link RouteLocationRaw | route location}. Also includes an \`href\` property
99
+ * that includes any existing \`base\`. By default the \`currentLocation\` used is
100
+ * \`route.currentRoute\` and should only be overriden in advanced use cases.
101
+ *
102
+ * @param to - Raw route location to resolve
103
+ * @param currentLocation - Optional current location to resolve against
104
+ */
105
+ resolve(
106
+ to: TypedRouteLocationRaw,
107
+ currentLocation?: RouteLocationNormalizedLoaded
108
+ ): RouteLocation & {
109
+ href: string;
110
+ };
111
+ /**
112
+ * Programmatically navigate to a new URL by pushing an entry in the history
113
+ * stack.
114
+ *
115
+ * @param to - Route location to navigate to
116
+ */
117
+ push(to: TypedRouteLocationRaw): Promise<NavigationFailure | void | undefined>;
118
+ /**
119
+ * Programmatically navigate to a new URL by replacing the current entry in
120
+ * the history stack.
121
+ *
122
+ * @param to - Route location to navigate to
123
+ */
124
+ replace(to: TypedRouteLocationRaw): Promise<NavigationFailure | void | undefined>;
125
+ }
126
+
127
+ export interface TypedRouter extends _TypedRouter {}
128
+ export type TypedRoute = _TypedRoute;
129
+ export type TypedNamedRoute<T extends TypedRouteList> = _TypedNamedRoute<T>;
130
+
131
+ declare global {
132
+ export interface TypedRouter extends _TypedRouter {}
133
+ export type TypedRoute = _TypedRoute;
134
+ export type TypedNamedRoute<T extends TypedRouteList> = _TypedNamedRoute<T>;
135
+
136
+ ${autoImport ? `const useRoute: typeof _useRoute;
137
+ const useRouter: typeof _useRouter;` : ""}
138
+ }
139
+
140
+ type TypedNuxtLinkProps = Omit<NuxtLinkProps, 'to'> & {
141
+ to: string | Omit<Exclude<RouteLocationRaw, string>, 'name'> & TypedRouteNamedMapper;
142
+ };
143
+
144
+ type _NuxtLink = DefineComponent<
145
+ TypedNuxtLinkProps,
146
+ {},
147
+ {},
148
+ import('vue').ComputedOptions,
149
+ import('vue').MethodOptions,
150
+ import('vue').ComponentOptionsMixin,
151
+ import('vue').ComponentOptionsMixin,
152
+ {},
153
+ string,
154
+ import('vue').VNodeProps &
155
+ import('vue').AllowedComponentProps &
156
+ import('vue').ComponentCustomProps,
157
+ Readonly<TypedNuxtLinkProps>,
158
+ {}
159
+ >;
160
+
161
+ declare module '@vue/runtime-core' {
162
+ export interface GlobalComponents {
163
+ NuxtLink: _NuxtLink;
164
+ }
165
+ }
166
+ `;
167
+ }
168
+
169
+ function createDeclarationRoutesFile(autoImport) {
170
+ return `
171
+ ${watermarkTemplate}
172
+
173
+ ${staticTypesImports}
174
+
175
+ ${createRuntimeTypeUtils(autoImport)}
176
+ `;
177
+ }
178
+
179
+ function createRuntimeIndexFile() {
180
+ return `
181
+ ${watermarkTemplate}
182
+ export { routesNames } from './__routes';
183
+ export type { TypedRouteList } from './__routes';
184
+ export {useRouter} from './__useTypedRouter';
185
+ export {useRoute} from './__useTypedRoute';
186
+ `;
187
+ }
188
+
189
+ function createRuntimePluginFile(routesDeclTemplate) {
190
+ return `
191
+ ${watermarkTemplate}
192
+ import { defineNuxtPlugin } from '#app';
193
+
194
+ export default defineNuxtPlugin(() => {
195
+ const router = useRouter();
196
+ const route = useRoute();
197
+ const routesNames = ${routesDeclTemplate};
198
+
199
+ return {
200
+ provide: {
201
+ typedRouter: router as TypedRouter,
202
+ typedRoute: route as TypedRoute,
203
+ routesNames,
204
+ },
205
+ };
206
+ });
207
+ `;
208
+ }
209
+
210
+ function createTypedRouteListExport(routesList) {
211
+ return `export type TypedRouteList = ${routesList.map((m) => `'${m}'`).join("|\n")}`;
212
+ }
213
+ function createTypedRouteParamsExport(routesParams) {
214
+ return `export type TypedRouteParams = {
215
+ ${routesParams.map(
216
+ ({ name, params }) => `"${name}": ${params.length ? `{
217
+ ${params.map(
218
+ ({ key, required, catchAll }) => `"${key}"${required ? "" : "?"}: (string | number)${catchAll ? "[]" : ""}`
219
+ ).join(",\n")}
220
+ }` : "never"}`
221
+ ).join(",\n")}
222
+ }`;
223
+ }
224
+ function createTypedRouteNamedMapperExport(routesParams) {
225
+ return `export type TypedRouteNamedMapper =
226
+ ${routesParams.map(
227
+ ({ name, params }) => `{name: "${name}" ${params.length ? `, params${params.some((s) => s.required) ? "" : "?"}: {
228
+ ${params.map(
229
+ ({ key, required, catchAll }) => `"${key}"${required ? "" : "?"}: (string | number)${catchAll ? "[]" : ""}`
230
+ ).join(",\n")}
231
+ }` : ""}}`
232
+ ).join("|\n")}
233
+ `;
234
+ }
235
+ function createResolvedTypedRouteNamedMapperExport(routesParams) {
236
+ return `export type ResolvedTypedRouteNamedMapper =
237
+ {
238
+ name: TypedRouteList;
239
+ params: unknown;
240
+ } & (
241
+ ${routesParams.map(
242
+ ({ name, params }) => `{name: "${name}" ${params.length ? `, params: {
243
+ ${params.map(
244
+ ({ key, notRequiredOnPage, catchAll }) => `"${key}"${notRequiredOnPage ? "?" : ""}: string${catchAll ? "[]" : ""}`
245
+ ).join(",\n")}
246
+ }` : ""}}`
247
+ ).join("|\n")}
248
+ )
249
+ `;
250
+ }
251
+
252
+ function createRuntimeRoutesFile({
253
+ routesList,
254
+ routesObjectTemplate,
255
+ routesDeclTemplate,
256
+ routesParams
257
+ }) {
258
+ return `
259
+ ${watermarkTemplate}
260
+
261
+ export const routesNames = ${routesObjectTemplate};
262
+
263
+ ${createTypedRouteListExport(routesList)}
264
+
265
+ export type RouteListDecl = ${routesDeclTemplate};
266
+
267
+ /**
268
+ * Routes params are only required for the exact targeted route name,
269
+ * vue-router behaviour allow to navigate between children routes without the need to provide all the params every time.
270
+ * So we can't enforce params when navigating between routes, only a \`[xxx].vue\` page will have required params in the type definition
271
+ *
272
+ *
273
+ * */
274
+
275
+ ${createTypedRouteParamsExport(routesParams)}
276
+
277
+ ${createTypedRouteNamedMapperExport(routesParams)}
278
+
279
+ ${createResolvedTypedRouteNamedMapperExport(routesParams)}
280
+ `;
281
+ }
282
+
283
+ function createUseTypedRouteFile(routesDeclTemplate) {
284
+ return `
285
+ ${watermarkTemplate}
286
+ import { useRoute as defaultRoute } from '#app';
287
+ import type { TypedRouteList } from './__routes';
288
+
289
+ /** Acts the same as \`useRoute\`, but typed.
290
+ *
291
+ * @exemple
292
+ *
293
+ * \`\`\`ts
294
+ * const route = useRoute();
295
+ * \`\`\`
296
+ *
297
+ * \`\`\`ts
298
+ * const route = useRoute('my-route-with-param-id');
299
+ * route.params.id // autocompletes!
300
+ * \`\`\`
301
+ *
302
+ * \`\`\`ts
303
+ * const route = useRoute();
304
+ * if (route.name === 'my-route-with-param-id') {
305
+ * route.params.id // autocompletes!
306
+ * }
307
+ * \`\`\`
308
+ */
309
+ export function useRoute<T extends TypedRouteList = never>(
310
+ name?: T
311
+ ): [T] extends [never] ? TypedRoute : TypedNamedRoute<T> {
312
+ const route = defaultRoute();
313
+
314
+ return route as any;
315
+ }
316
+
317
+
318
+ `;
319
+ }
320
+
321
+ function createRuntimeUseTypedRouterFile(routesDeclTemplate) {
322
+ return `
323
+ ${watermarkTemplate}
324
+ import { useRouter as defaultRouter } from '#app';
325
+ import type { TypedRouter } from './typed-router';
326
+
327
+ /** Returns instances of $typedRouter and $routesList fully typed to use in your components or your Vuex/Pinia store
328
+ *
329
+ * @exemple
330
+ *
331
+ * \`\`\`ts
332
+ * const router = useRouter();
333
+ * \`\`\`
334
+ */
335
+ export function useRouter(): TypedRouter {
336
+ const router = defaultRouter();
337
+
338
+ return router;
339
+ };
340
+
341
+ `;
342
+ }
343
+
344
+ const { resolveConfig, format } = prettier;
345
+ const defaultPrettierOptions = {
346
+ printWidth: 100,
347
+ tabWidth: 2,
348
+ trailingComma: "es5",
349
+ singleQuote: true,
350
+ semi: true,
351
+ bracketSpacing: true,
352
+ htmlWhitespaceSensitivity: "strict"
353
+ };
354
+ async function formatOutputWithPrettier(template) {
355
+ try {
356
+ let prettierFoundOptions = await resolveConfig(process.cwd());
357
+ if (!prettierFoundOptions) {
358
+ prettierFoundOptions = defaultPrettierOptions;
359
+ }
360
+ const formatedTemplate = format(template, {
361
+ ...prettierFoundOptions,
362
+ parser: "typescript"
363
+ });
364
+ return formatedTemplate;
365
+ } catch (e) {
366
+ console.error(logSymbols.error, chalk.red("Error while formatting the output"), "\n" + e);
367
+ return Promise.reject(e);
368
+ }
369
+ }
370
+
371
+ dirname(fileURLToPath(import.meta.url));
372
+ async function processPathAndWriteFile({
373
+ content,
374
+ fileName,
375
+ rootDir,
376
+ outDir
377
+ }) {
378
+ try {
379
+ const finalOutDir = outDir ?? `.nuxt/typed-router`;
380
+ const processedOutDir = resolve(rootDir, finalOutDir);
381
+ const outputFile = resolve(process.cwd(), `${processedOutDir}/${fileName}`);
382
+ const formatedContent = await formatOutputWithPrettier(content);
383
+ if (fs.existsSync(outputFile)) {
384
+ await writeFile(outputFile, formatedContent);
385
+ } else {
386
+ let dirList = outputFile.split("/");
387
+ dirList.pop();
388
+ const dirPath = dirList.join("/");
389
+ await mkdirp(dirPath);
390
+ await writeFile(outputFile, formatedContent);
391
+ }
392
+ } catch (e) {
393
+ return Promise.reject(e);
394
+ }
395
+ }
396
+ async function writeFile(path, content) {
397
+ try {
398
+ await fs.writeFileSync(path, content);
399
+ } catch (e) {
400
+ console.log(logSymbols.error, chalk.red(`Error while saving file at ${path}`, e));
401
+ return Promise.reject(e);
402
+ }
403
+ }
404
+
405
+ function handlePluginFileSave({
406
+ nuxt,
407
+ rootDir,
408
+ routesDeclTemplate
409
+ }) {
410
+ const pluginName = "__typed-router.ts";
411
+ nuxt.hook("build:done", async () => {
412
+ const pluginFolder = `${rootDir}/plugins`;
413
+ await processPathAndWriteFile({
414
+ outDir: pluginFolder,
415
+ rootDir,
416
+ fileName: pluginName,
417
+ content: createRuntimePluginFile(routesDeclTemplate)
418
+ });
419
+ });
420
+ }
421
+
422
+ let previousGeneratedRoutes = "";
423
+ async function saveGeneratedFiles({
424
+ rootDir,
425
+ autoImport,
426
+ outputData: { routesDeclTemplate, routesList, routesObjectTemplate, routesParams }
427
+ }) {
428
+ const filesMap = [
429
+ {
430
+ fileName: "__useTypedRouter.ts",
431
+ content: createRuntimeUseTypedRouterFile()
432
+ },
433
+ {
434
+ fileName: "__useTypedRoute.ts",
435
+ content: createUseTypedRouteFile()
436
+ },
437
+ {
438
+ fileName: `__routes.ts`,
439
+ content: createRuntimeRoutesFile({
440
+ routesList,
441
+ routesObjectTemplate,
442
+ routesDeclTemplate,
443
+ routesParams
444
+ })
445
+ },
446
+ {
447
+ fileName: `typed-router.d.ts`,
448
+ content: createDeclarationRoutesFile(autoImport)
449
+ },
450
+ {
451
+ fileName: "index.ts",
452
+ content: createRuntimeIndexFile()
453
+ }
454
+ ];
455
+ await Promise.all(
456
+ filesMap.map(({ content, fileName }) => processPathAndWriteFile({ rootDir, content, fileName }))
457
+ );
458
+ if (previousGeneratedRoutes !== routesList.join(",")) {
459
+ previousGeneratedRoutes = routesList.join(",");
460
+ console.log(logSymbols.success, `Router autocompletions generated \u{1F6A6}`);
461
+ }
462
+ }
463
+
464
+ function isItemLast(array, index) {
465
+ return array ? index === array.length - 1 : false;
466
+ }
467
+
468
+ const routeParamExtractRegxp = /(:(\w+)(\(.+\)[*+]?)?(\?)?)+/g;
469
+ function extractRouteParamsFromPath(path, isIndexFileForRouting, previousParams) {
470
+ let params = [];
471
+ let matches;
472
+ do {
473
+ matches = routeParamExtractRegxp.exec(path);
474
+ if (matches) {
475
+ const [_, mtch, key, catchAll, optional] = matches;
476
+ if (mtch) {
477
+ params.push({ name: key, optional: !!optional, catchAll: !!catchAll });
478
+ }
479
+ }
480
+ } while (matches);
481
+ let allMergedParams = params.map(
482
+ ({ name, optional, catchAll }) => ({
483
+ key: name,
484
+ required: !optional,
485
+ notRequiredOnPage: optional,
486
+ catchAll
487
+ })
488
+ );
489
+ if (previousParams?.length) {
490
+ allMergedParams = previousParams.map((m) => ({ ...m, required: false })).concat(allMergedParams);
491
+ }
492
+ if (!params.length && isIndexFileForRouting) {
493
+ const lastItem = allMergedParams[allMergedParams.length - 1];
494
+ if (lastItem) {
495
+ lastItem.required = true;
496
+ }
497
+ }
498
+ return allMergedParams;
499
+ }
500
+
501
+ function extractMatchingSiblings(mainRoute, siblingRoutes) {
502
+ return siblingRoutes?.filter((s) => {
503
+ const chunkName = extractChunkMain(mainRoute.file);
504
+ if (chunkName && s.name) {
505
+ const siblingChunkName = extractChunkMain(s.file);
506
+ if (!siblingChunkName)
507
+ return false;
508
+ return chunkName === siblingChunkName;
509
+ }
510
+ return false;
511
+ });
512
+ }
513
+ function extractUnMatchingSiblings(mainRoute, siblingRoutes) {
514
+ return siblingRoutes?.filter((s) => {
515
+ const chunkName = extractChunkMain(mainRoute.file);
516
+ if (chunkName) {
517
+ const siblingChunkName = extractChunkMain(s.file);
518
+ if (!siblingChunkName)
519
+ return false;
520
+ return chunkName !== siblingChunkName;
521
+ }
522
+ return false;
523
+ });
524
+ }
525
+ function extractChunkMain(chunkName) {
526
+ let chunkArray = chunkName?.split("/");
527
+ return chunkArray?.join("/");
528
+ }
529
+
530
+ function walkThoughRoutes({
531
+ route,
532
+ level,
533
+ siblings,
534
+ parentName,
535
+ previousParams,
536
+ output,
537
+ isLast
538
+ }) {
539
+ const matchingSiblings = extractMatchingSiblings(route, siblings);
540
+ const haveMatchingSiblings = !!matchingSiblings?.length && route.path !== "/";
541
+ const chunkArray = route.file?.split("/") ?? [];
542
+ const lastChunkArray = chunkArray[chunkArray?.length - 1].split(".vue")[0];
543
+ const isRootSibling = lastChunkArray === "index";
544
+ if (route.children?.length && !haveMatchingSiblings || !route.children?.length && haveMatchingSiblings && isRootSibling) {
545
+ let childrenChunks = haveMatchingSiblings ? matchingSiblings : route.children;
546
+ const splittedPaths = route.path.split("/");
547
+ const parentPath = splittedPaths[splittedPaths.length - 1];
548
+ const nameKey = camelCase(parentPath || "index");
549
+ output.routesObjectTemplate += `${nameKey}:{`;
550
+ output.routesDeclTemplate += `"${nameKey}":{`;
551
+ const allRouteParams = extractRouteParamsFromPath(route.path, false, previousParams);
552
+ childrenChunks?.map(
553
+ (routeConfig, index) => walkThoughRoutes({
554
+ route: routeConfig,
555
+ level: level + 1,
556
+ siblings: extractUnMatchingSiblings(route, siblings),
557
+ parentName: nameKey,
558
+ previousParams: allRouteParams,
559
+ output,
560
+ isLast: isItemLast(childrenChunks, index)
561
+ })
562
+ );
563
+ output.routesObjectTemplate += "},";
564
+ output.routesDeclTemplate += `}${isLast ? "" : ","}`;
565
+ } else if (route.name) {
566
+ let splitted = [];
567
+ splitted = route.name.split("-");
568
+ splitted = splitted.slice(level, splitted.length);
569
+ if (splitted[0] === parentName) {
570
+ splitted.splice(0, 1);
571
+ }
572
+ const keyName = route.path === "" ? "index" : camelCase(splitted.join("-")) || "index";
573
+ output.routesObjectTemplate += `'${keyName}': '${route.name}' as const,`;
574
+ output.routesDeclTemplate += `"${keyName}": "${route.name}"${isLast ? "" : ","}`;
575
+ output.routesList.push(route.name);
576
+ const isIndexFileForRouting = route.path === "";
577
+ const allRouteParams = extractRouteParamsFromPath(
578
+ route.path,
579
+ isIndexFileForRouting,
580
+ previousParams
581
+ );
582
+ output.routesParams.push({
583
+ name: route.name,
584
+ params: allRouteParams
585
+ });
586
+ }
587
+ }
588
+
589
+ function constructRouteMap(routesConfig) {
590
+ try {
591
+ let routesObjectTemplate = "{";
592
+ let routesDeclTemplate = "{";
593
+ let routesList = [];
594
+ let routesParams = [];
595
+ const output = { routesObjectTemplate, routesDeclTemplate, routesList, routesParams };
596
+ startGenerator({
597
+ output,
598
+ routesConfig
599
+ });
600
+ return output;
601
+ } catch (e) {
602
+ throw new Error("Generation failed");
603
+ }
604
+ }
605
+ function startGenerator({ output, routesConfig }) {
606
+ routesConfig.forEach((route, index) => {
607
+ const rootSiblingsRoutes = routesConfig.filter((rt) => rt.chunkName !== route.chunkName);
608
+ walkThoughRoutes({
609
+ route,
610
+ level: 0,
611
+ output,
612
+ siblings: rootSiblingsRoutes,
613
+ isLast: isItemLast(routesConfig, index)
614
+ });
615
+ });
616
+ output.routesObjectTemplate += "}";
617
+ output.routesDeclTemplate += "}";
618
+ }
619
+
620
+ function createTypedRouter({ plugin, nuxt }) {
621
+ try {
622
+ const rootDir = nuxt.options.rootDir;
623
+ const autoImport = nuxt.options.imports.autoImport ?? true;
624
+ extendPages(async (routes) => {
625
+ if (routes.length) {
626
+ const outputData = constructRouteMap(routes);
627
+ if (plugin) {
628
+ handlePluginFileSave({
629
+ nuxt,
630
+ routesDeclTemplate: outputData.routesDeclTemplate,
631
+ rootDir
632
+ });
633
+ }
634
+ await saveGeneratedFiles({
635
+ autoImport,
636
+ rootDir,
637
+ outputData
638
+ });
639
+ } else {
640
+ console.log(
641
+ logSymbols.warning,
642
+ chalk.yellow(
643
+ `[typed-router] No routes defined. Check if your ${chalk.underline(
644
+ chalk.bold("pages")
645
+ )} folder exists and remove ${chalk.underline(chalk.bold("app.vue"))}`
646
+ )
647
+ );
648
+ }
649
+ });
650
+ } catch (e) {
651
+ console.error(chalk.red("Error while generating routes definitions model"), "\n" + e);
652
+ }
653
+ }
654
+
655
+ const module = defineNuxtModule({
656
+ meta: {
657
+ name: "nuxt-typed-router",
658
+ configKey: "nuxtTypedRouter",
659
+ compatibility: { nuxt: "^3.0.0-rc.1", bridge: false }
660
+ },
661
+ defaults: {
662
+ plugin: false
663
+ },
664
+ setup(moduleOptions, nuxt) {
665
+ const rootDir = nuxt.options.rootDir;
666
+ const { plugin } = moduleOptions;
667
+ const { resolve } = createResolver(import.meta.url);
668
+ nuxt.options.alias = {
669
+ ...nuxt.options.alias,
670
+ "@typed-router": resolve(`${rootDir}/.nuxt/typed-router`)
671
+ };
672
+ nuxt.options.typescript.tsConfig = {
673
+ include: ["./typed-router/typed-router.d.ts"]
674
+ };
675
+ const typedRouterOptions = { nuxt, plugin };
676
+ nuxt.hook("pages:extend", () => createTypedRouter(typedRouterOptions));
677
+ createTypedRouter(typedRouterOptions);
678
+ }
679
+ });
680
+
681
+ export { module as default };
package/dist/types.d.ts CHANGED
@@ -1,13 +1,10 @@
1
1
 
2
- import { ModuleOptions, ModuleHooks, ModuleRuntimeConfig, ModulePublicRuntimeConfig } from './module'
2
+ import { ModuleOptions } from './module'
3
3
 
4
4
  declare module '@nuxt/schema' {
5
5
  interface NuxtConfig { ['nuxtTypedRouter']?: Partial<ModuleOptions> }
6
6
  interface NuxtOptions { ['nuxtTypedRouter']?: ModuleOptions }
7
- interface NuxtHooks extends ModuleHooks {}
8
- interface RuntimeConfig extends ModuleRuntimeConfig {}
9
- interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
10
7
  }
11
8
 
12
9
 
13
- export { default } from './module'
10
+ export { ModuleOptions, default } from './module'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-typed-router",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Provide autocompletion for pages route names generated by Nuxt router",
5
5
  "type": "module",
6
6
  "main": "./dist/module.cjs",
@@ -23,10 +23,9 @@
23
23
  "build:test": "cross-env NUXT_BUILD_TYPE=stub pnpm run prepack && pnpm run dev:build",
24
24
  "test:fixtures": "nuxt-module-build --stub && nuxi prepare test/fixtures/simple && vitest run --dir test",
25
25
  "test:types": "nuxi prepare test/fixtures/simple && cd test/fixtures/simple && vue-tsc --noEmit",
26
- "test": "pnpm run test:fixtures && pnpm run test:types",
26
+ "test": "pnpm run dev:prepare && pnpm run test:fixtures && pnpm run test:types",
27
27
  "docs:dev": "cd docs && pnpm run dev",
28
- "docs:build": "npm run dev:prepare && cd docs && nuxi generate",
29
- "prepare": "pnpm run dev:prepare"
28
+ "docs:build": "npm run dev:prepare && cd docs && nuxi generate"
30
29
  },
31
30
  "publishConfig": {
32
31
  "access": "public"