nuxt-typed-router 2.1.0 โ†’ 2.1.2-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 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.2-beta.0"
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 watermarkTemplate = `
12
+ // @ts-nocheck
13
+ // eslint-disable
14
+ /**
15
+ * ---------------------------------------------------
16
+ * \u{1F697}\u{1F6A6} Generated by nuxt-typed-router. Do not modify !
17
+ * ---------------------------------------------------
18
+ * */
19
+
20
+ `;
5
21
 
6
- export default _module;
22
+ function createDeclarationRoutesFile(autoImport) {
23
+ return `
24
+ ${watermarkTemplate}
25
+
26
+ import type { NuxtLinkProps } from '#app';
27
+ import type { DefineComponent } from 'vue';
28
+ import type { RouteLocationRaw } from 'vue-router';
29
+ import type { TypedRouteNamedMapper } from './__routes';
30
+ import { useRoute as _useRoute } from './__useTypedRoute';
31
+ import { useRouter as _useRouter } from './__useTypedRouter';
32
+
33
+ declare global {
34
+
35
+ ${autoImport ? `const useRoute: typeof _useRoute;
36
+ const useRouter: typeof _useRouter;` : ""}
37
+ }
38
+
39
+ type TypedNuxtLinkProps = Omit<NuxtLinkProps, 'to'> & {
40
+ to: string | Omit<Exclude<RouteLocationRaw, string>, 'name'> & TypedRouteNamedMapper;
41
+ };
42
+
43
+ export type _NuxtLink = DefineComponent<
44
+ TypedNuxtLinkProps,
45
+ {},
46
+ {},
47
+ import('vue').ComputedOptions,
48
+ import('vue').MethodOptions,
49
+ import('vue').ComponentOptionsMixin,
50
+ import('vue').ComponentOptionsMixin,
51
+ {},
52
+ string,
53
+ import('vue').VNodeProps &
54
+ import('vue').AllowedComponentProps &
55
+ import('vue').ComponentCustomProps,
56
+ Readonly<TypedNuxtLinkProps>,
57
+ {}
58
+ >;
59
+
60
+ declare module '@vue/runtime-core' {
61
+ export interface GlobalComponents {
62
+ NuxtLink: _NuxtLink;
63
+ }
64
+ }
65
+ `;
66
+ }
67
+
68
+ function createRuntimeIndexFile() {
69
+ return `
70
+ ${watermarkTemplate}
71
+ export { routesNames } from './__routes';
72
+ export type { TypedRouteList, TypedRouteNamedMapper, TypedRouteParams } from './__routes';
73
+ export {useRouter} from './__useTypedRouter';
74
+ export {useRoute} from './__useTypedRoute';
75
+ export type {TypedRoute, TypedRouter, TypedNamedRoute } from './__router'
76
+ `;
77
+ }
78
+
79
+ function createRuntimePluginFile(routesDeclTemplate) {
80
+ return `
81
+ ${watermarkTemplate}
82
+ import { defineNuxtPlugin } from '#app';
83
+ import {TypedRouter, TypedRoute} from '@typed-router';
84
+
85
+ export default defineNuxtPlugin(() => {
86
+ const router = useRouter();
87
+ const route = useRoute();
88
+ const routesNames = ${routesDeclTemplate};
89
+
90
+ return {
91
+ provide: {
92
+ typedRouter: router as TypedRouter,
93
+ typedRoute: route as TypedRoute,
94
+ routesNames,
95
+ },
96
+ };
97
+ });
98
+ `;
99
+ }
100
+
101
+ function createTypedRouteListExport(routesList) {
102
+ return `export type TypedRouteList = ${routesList.map((m) => `'${m}'`).join("|\n")}`;
103
+ }
104
+ function createTypedRouteParamsExport(routesParams) {
105
+ return `export type TypedRouteParams = {
106
+ ${routesParams.map(
107
+ ({ name, params }) => `"${name}": ${params.length ? `{
108
+ ${params.map(
109
+ ({ key, required, catchAll }) => `"${key}"${required ? "" : "?"}: (string | number)${catchAll ? "[]" : ""}`
110
+ ).join(",\n")}
111
+ }` : "never"}`
112
+ ).join(",\n")}
113
+ }`;
114
+ }
115
+ function createTypedRouteNamedMapperExport(routesParams) {
116
+ return `export type TypedRouteNamedMapper =
117
+ ${routesParams.map(
118
+ ({ name, params }) => `{name: "${name}" ${params.length ? `, params${params.some((s) => s.required) ? "" : "?"}: {
119
+ ${params.map(
120
+ ({ key, required, catchAll }) => `"${key}"${required ? "" : "?"}: (string | number)${catchAll ? "[]" : ""}`
121
+ ).join(",\n")}
122
+ }` : ""}}`
123
+ ).join("|\n")}
124
+ `;
125
+ }
126
+ function createResolvedTypedRouteNamedMapperExport(routesParams) {
127
+ return `export type ResolvedTypedRouteNamedMapper =
128
+ {
129
+ name: TypedRouteList;
130
+ params: unknown;
131
+ } & (
132
+ ${routesParams.map(
133
+ ({ name, params }) => `{name: "${name}" ${params.length ? `, params: {
134
+ ${params.map(
135
+ ({ key, notRequiredOnPage, catchAll }) => `"${key}"${notRequiredOnPage ? "?" : ""}: string${catchAll ? "[]" : ""}`
136
+ ).join(",\n")}
137
+ }` : ""}}`
138
+ ).join("|\n")}
139
+ )
140
+ `;
141
+ }
142
+
143
+ function createRuntimeRoutesFile({
144
+ routesList,
145
+ routesObjectTemplate,
146
+ routesDeclTemplate,
147
+ routesParams
148
+ }) {
149
+ return `
150
+ ${watermarkTemplate}
151
+
152
+ export const routesNames = ${routesObjectTemplate};
153
+
154
+ ${createTypedRouteListExport(routesList)}
155
+
156
+ export type RouteListDecl = ${routesDeclTemplate};
157
+
158
+ /**
159
+ * Routes params are only required for the exact targeted route name,
160
+ * vue-router behaviour allow to navigate between children routes without the need to provide all the params every time.
161
+ * So we can't enforce params when navigating between routes, only a \`[xxx].vue\` page will have required params in the type definition
162
+ *
163
+ *
164
+ * */
165
+ ${createTypedRouteParamsExport(routesParams)}
166
+
167
+ ${createTypedRouteNamedMapperExport(routesParams)}
168
+
169
+ ${createResolvedTypedRouteNamedMapperExport(routesParams)}
170
+ `;
171
+ }
172
+
173
+ function createUseTypedRouteFile(routesDeclTemplate) {
174
+ return `
175
+ ${watermarkTemplate}
176
+ import { useRoute as defaultRoute } from '#app';
177
+ import type { TypedRouteList } from './__routes';
178
+ import type {TypedRoute, TypedNamedRoute} from './__router'
179
+
180
+ /** Acts the same as \`useRoute\`, but typed.
181
+ *
182
+ * @exemple
183
+ *
184
+ * \`\`\`ts
185
+ * const route = useRoute();
186
+ * \`\`\`
187
+ *
188
+ * \`\`\`ts
189
+ * const route = useRoute('my-route-with-param-id');
190
+ * route.params.id // autocompletes!
191
+ * \`\`\`
192
+ *
193
+ * \`\`\`ts
194
+ * const route = useRoute();
195
+ * if (route.name === 'my-route-with-param-id') {
196
+ * route.params.id // autocompletes!
197
+ * }
198
+ * \`\`\`
199
+ */
200
+ export function useRoute<T extends TypedRouteList = never>(
201
+ name?: T
202
+ ): [T] extends [never] ? TypedRoute : TypedNamedRoute<T> {
203
+ const route = defaultRoute();
204
+
205
+ return route as any;
206
+ }
207
+
208
+
209
+ `;
210
+ }
211
+
212
+ function createRuntimeUseTypedRouterFile(routesDeclTemplate) {
213
+ return `
214
+ ${watermarkTemplate}
215
+ import { useRouter as defaultRouter } from '#app';
216
+ import type { TypedRouter } from './__router';
217
+
218
+ /** Returns instances of $typedRouter and $routesList fully typed to use in your components or your Vuex/Pinia store
219
+ *
220
+ * @exemple
221
+ *
222
+ * \`\`\`ts
223
+ * const router = useRouter();
224
+ * \`\`\`
225
+ */
226
+ export function useRouter(): TypedRouter {
227
+ const router = defaultRouter();
228
+
229
+ return router;
230
+ };
231
+
232
+ `;
233
+ }
234
+
235
+ function createRuntimeRouterTypes() {
236
+ return `
237
+ import type {
238
+ NavigationFailure,
239
+ RouteLocation,
240
+ RouteLocationNormalizedLoaded,
241
+ RouteLocationOptions,
242
+ RouteLocationRaw,
243
+ RouteQueryAndHash,
244
+ Router,
245
+ } from 'vue-router';
246
+ import type {
247
+ ResolvedTypedRouteNamedMapper,
248
+ TypedRouteList,
249
+ TypedRouteNamedMapper,
250
+ TypedRouteParams,
251
+ } from './__routes';
252
+
253
+ // Type utils
254
+ type ExtractRequiredParameters<T extends Record<string, any>> = Pick<
255
+ T,
256
+ { [K in keyof T]: undefined extends T[K] ? never : K }[keyof T]
257
+ >;
258
+
259
+ type HasOneRequiredParameter<T extends TypedRouteList> = [TypedRouteParams[T]] extends [never]
260
+ ? false
261
+ : [keyof ExtractRequiredParameters<TypedRouteParams[T]>] extends [undefined]
262
+ ? false
263
+ : true;
264
+
265
+ type TypedLocationAsRelativeRaw<T extends TypedRouteList> = {
266
+ name?: T;
267
+ } & ([TypedRouteParams[T]] extends [never]
268
+ ? {}
269
+ : HasOneRequiredParameter<T> extends false
270
+ ? { params?: TypedRouteParams[T] }
271
+ : { params: TypedRouteParams[T] });
272
+
273
+ type ResolvedTypedLocationAsRelativeRaw<T extends TypedRouteList> = {
274
+ name?: T;
275
+ } & ([TypedRouteParams[T]] extends [never] ? {} : { params: TypedRouteParams[T] });
276
+
277
+ type TypedRouteLocationRaw = RouteQueryAndHash & TypedRouteNamedMapper & RouteLocationOptions;
278
+
279
+ type _TypedRoute = Omit<RouteLocationNormalizedLoaded, 'name' | 'params'> &
280
+ ResolvedTypedRouteNamedMapper;
281
+ type _TypedNamedRoute<T extends TypedRouteList> = Omit<
282
+ RouteLocationNormalizedLoaded,
283
+ 'name' | 'params'
284
+ > &
285
+ ResolvedTypedLocationAsRelativeRaw<T>;
286
+
287
+ /** Augmented Router interface */
288
+ interface _TypedRouter
289
+ extends Omit<Router, 'removeRoute' | 'hasRoute' | 'resolve' | 'push' | 'replace' | 'currentRoute'> {
290
+ readonly currentRoute: _TypedRoute;
291
+ /**
292
+ * Remove an existing route by its name.
293
+ *
294
+ * @param name - Name of the route to remove
295
+ */
296
+ removeRoute(name: TypedRouteList): void;
297
+ /**
298
+ * Checks if a route with a given name exists
299
+ *
300
+ * @param name - Name of the route to check
301
+ */
302
+ hasRoute(name: TypedRouteList): boolean;
303
+ /**
304
+ * Returns the {@link RouteLocation | normalized version} of a
305
+ * {@link RouteLocationRaw | route location}. Also includes an \`href\` property
306
+ * that includes any existing \`base\`. By default the \`currentLocation\` used is
307
+ * \`route.currentRoute\` and should only be overriden in advanced use cases.
308
+ *
309
+ * @param to - Raw route location to resolve
310
+ * @param currentLocation - Optional current location to resolve against
311
+ */
312
+ resolve(
313
+ to: TypedRouteLocationRaw,
314
+ currentLocation?: RouteLocationNormalizedLoaded
315
+ ): RouteLocation & {
316
+ href: string;
317
+ };
318
+ /**
319
+ * Programmatically navigate to a new URL by pushing an entry in the history
320
+ * stack.
321
+ *
322
+ * @param to - Route location to navigate to
323
+ */
324
+ push(to: TypedRouteLocationRaw): Promise<NavigationFailure | void | undefined>;
325
+ /**
326
+ * Programmatically navigate to a new URL by replacing the current entry in
327
+ * the history stack.
328
+ *
329
+ * @param to - Route location to navigate to
330
+ */
331
+ replace(to: TypedRouteLocationRaw): Promise<NavigationFailure | void | undefined>;
332
+ }
333
+
334
+ export interface TypedRouter extends _TypedRouter {}
335
+ export type TypedRoute = _TypedRoute;
336
+ export type TypedNamedRoute<T extends TypedRouteList> = _TypedNamedRoute<T>;
337
+ `;
338
+ }
339
+
340
+ const { resolveConfig, format } = prettier;
341
+ const defaultPrettierOptions = {
342
+ printWidth: 100,
343
+ tabWidth: 2,
344
+ trailingComma: "es5",
345
+ singleQuote: true,
346
+ semi: true,
347
+ bracketSpacing: true,
348
+ htmlWhitespaceSensitivity: "strict"
349
+ };
350
+ async function formatOutputWithPrettier(template) {
351
+ try {
352
+ let prettierFoundOptions = await resolveConfig(process.cwd());
353
+ if (!prettierFoundOptions) {
354
+ prettierFoundOptions = defaultPrettierOptions;
355
+ }
356
+ const formatedTemplate = format(template, {
357
+ ...prettierFoundOptions,
358
+ parser: "typescript"
359
+ });
360
+ return formatedTemplate;
361
+ } catch (e) {
362
+ console.error(logSymbols.error, chalk.red("Error while formatting the output"), "\n" + e);
363
+ return Promise.reject(e);
364
+ }
365
+ }
366
+
367
+ dirname(fileURLToPath(import.meta.url));
368
+ async function processPathAndWriteFile({
369
+ content,
370
+ fileName,
371
+ rootDir,
372
+ outDir
373
+ }) {
374
+ try {
375
+ const finalOutDir = outDir ?? `.nuxt/typed-router`;
376
+ const processedOutDir = resolve(rootDir, finalOutDir);
377
+ const outputFile = resolve(process.cwd(), `${processedOutDir}/${fileName}`);
378
+ const formatedContent = await formatOutputWithPrettier(content);
379
+ if (fs.existsSync(outputFile)) {
380
+ await writeFile(outputFile, formatedContent);
381
+ } else {
382
+ let dirList = outputFile.split("/");
383
+ dirList.pop();
384
+ const dirPath = dirList.join("/");
385
+ await mkdirp(dirPath);
386
+ await writeFile(outputFile, formatedContent);
387
+ }
388
+ } catch (e) {
389
+ return Promise.reject(e);
390
+ }
391
+ }
392
+ async function writeFile(path, content) {
393
+ try {
394
+ await fs.writeFileSync(path, content);
395
+ } catch (e) {
396
+ console.log(logSymbols.error, chalk.red(`Error while saving file at ${path}`, e));
397
+ return Promise.reject(e);
398
+ }
399
+ }
400
+
401
+ function handlePluginFileSave({
402
+ nuxt,
403
+ rootDir,
404
+ routesDeclTemplate
405
+ }) {
406
+ const pluginName = "__typed-router.ts";
407
+ nuxt.hook("build:done", async () => {
408
+ const pluginFolder = `${rootDir}/plugins`;
409
+ await processPathAndWriteFile({
410
+ outDir: pluginFolder,
411
+ rootDir,
412
+ fileName: pluginName,
413
+ content: createRuntimePluginFile(routesDeclTemplate)
414
+ });
415
+ });
416
+ }
417
+
418
+ let previousGeneratedRoutes = "";
419
+ async function saveGeneratedFiles({
420
+ rootDir,
421
+ autoImport,
422
+ outputData: { routesDeclTemplate, routesList, routesObjectTemplate, routesParams }
423
+ }) {
424
+ const filesMap = [
425
+ {
426
+ fileName: "__useTypedRouter.ts",
427
+ content: createRuntimeUseTypedRouterFile()
428
+ },
429
+ {
430
+ fileName: "__useTypedRoute.ts",
431
+ content: createUseTypedRouteFile()
432
+ },
433
+ {
434
+ fileName: `__routes.ts`,
435
+ content: createRuntimeRoutesFile({
436
+ routesList,
437
+ routesObjectTemplate,
438
+ routesDeclTemplate,
439
+ routesParams
440
+ })
441
+ },
442
+ {
443
+ fileName: `__router.d.ts`,
444
+ content: createRuntimeRouterTypes()
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.2-beta.0",
4
4
  "description": "Provide autocompletion for pages route names generated by Nuxt router",
5
5
  "type": "module",
6
6
  "main": "./dist/module.cjs",
@@ -19,14 +19,15 @@
19
19
  "prepack": "nuxt-module-build",
20
20
  "dev": "nuxi dev playground",
21
21
  "dev:build": "nuxi build playground",
22
- "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
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
- "test:fixtures": "nuxt-module-build --stub && nuxi prepare test/fixtures/simple && vitest run --dir test",
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",
24
+ "test:prepare-fixtures": "nuxi prepare test/fixtures/simple",
25
+ "test:fixtures": " vitest run --dir test",
26
+ "test:types": "pnpm run test:vue && vitest typecheck --run --dir test",
27
+ "test:vue": "vue-tsc -p test/fixtures/simple/tsconfig.json --noEmit && vue-tsc -p test/fixtures/complex/tsconfig.json --noEmit",
28
+ "test": "pnpm run dev:prepare && pnpm run test:fixtures && pnpm run test:types",
27
29
  "docs:dev": "cd docs && pnpm run dev",
28
- "docs:build": "npm run dev:prepare && cd docs && nuxi generate",
29
- "prepare": "pnpm run dev:prepare"
30
+ "docs:build": "npm run dev:prepare && cd docs && nuxi generate"
30
31
  },
31
32
  "publishConfig": {
32
33
  "access": "public"