nuxt-typed-router 1.2.4 โ†’ 2.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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # ๐Ÿš—๐Ÿšฆ Typed Router for Nuxt
2
2
 
3
+ <p align="center">
4
+ <img width='100' src="https://raw.githubusercontent.com/victorgarciaesgi/nuxt-typed-router/master/.github/images/logo.png" alt="nuxt-typed-router logo">
5
+ </p>
6
+
7
+
3
8
  [npm-version-src]: https://img.shields.io/npm/v/nuxt-typed-router.svg
4
9
  [npm-version-href]: https://www.npmjs.com/package/nuxt-typed-router
5
10
  [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-typed-router.svg
@@ -9,9 +14,9 @@
9
14
  [![npm version][npm-version-src]][npm-version-href]
10
15
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
11
16
  [![npm downloads][npm-total-downloads-src]][npm-downloads-href]
12
- <img src='https://img.shields.io/npm/l/simple-graphql-to-typescript.svg'>
17
+ <img src='https://img.shields.io/npm/l/nuxt-typed-router.svg'>
13
18
 
14
- > Provide a type safe router to Nuxt with auto-generated typed definitions for route names and autocompletion for route params
19
+ ## Provide a type safe router to Nuxt with auto-generated typed definitions for route names and autocompletion for route params
15
20
 
16
21
  - ๐ŸŽ Provides a hook `useTypedRouter` that returns an alias of `$typedRouter` and also a typed list of your routes
17
22
  - ๐Ÿšš Expose a global method `$typedRouter` (clone of vue-router), but typed with the routes defined in `pages` directory
@@ -28,7 +33,7 @@ Demo ๐Ÿงช : [nuxt-typed-router-demo](https://github.com/victorgarciaesgi/nuxt-ty
28
33
 
29
34
  <br/>
30
35
  <p align="center">
31
- <img src="https://github.com/victorgarciaesgi/nuxt-typed-router/blob/master/medias/in-action.gif?raw=true"/>
36
+ <img src="https://github.com/victorgarciaesgi/nuxt-typed-router/blob/master/.github/images/in-action.gif?raw=true"/>
32
37
  </p>
33
38
  <br/>
34
39
 
@@ -78,6 +83,11 @@ interface ModuleOptions {
78
83
  * @default "routerPagesNames"
79
84
  * */
80
85
  routesObjectName?: string;
86
+ /**
87
+ * Set to false if you don't want a plugin generated
88
+ * @default false
89
+ */
90
+ plugin?: boolean;
81
91
  }
82
92
  ```
83
93
 
@@ -100,7 +110,7 @@ You can specify the output dir of the generated files in your configuration. It
100
110
 
101
111
  ```ts
102
112
  export default defineNuxtConfig({
103
- buildModules: ['nuxt-typed-router'],
113
+ modules: ['nuxt-typed-router'],
104
114
  nuxtTypedRouter: {
105
115
  outDir: './generated',
106
116
  },
package/dist/module.d.ts CHANGED
@@ -1,16 +1,11 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
- /** Output directory where you cant the files to be saved (ex: "./generated")
5
- * @default "<srcDir>/generated"
4
+ /**
5
+ * Set to false if you don't want a plugin generated
6
+ * @default false
6
7
  */
7
- outDir?: string;
8
- /** Name of the routesNames object (ex: "routesTree")
9
- * @default "routerPagesNames"
10
- * */
11
- routesObjectName?: string;
12
- /** @deprecated */
13
- stripAtFromName?: boolean;
8
+ plugin?: boolean;
14
9
  }
15
10
 
16
11
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
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": "1.2.4"
8
+ "version": "2.0.0-beta.0"
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,13 +1,331 @@
1
+ import { fileURLToPath } from 'url';
1
2
  import { extendPages, defineNuxtModule } from '@nuxt/kit';
2
3
  import chalk from 'chalk';
3
4
  import logSymbols from 'log-symbols';
4
5
  import prettier from 'prettier';
5
6
  import fs from 'fs';
6
- import { fileURLToPath } from 'url';
7
7
  import { dirname, resolve } from 'pathe';
8
8
  import mkdirp from 'mkdirp';
9
9
  import { camelCase } from 'lodash-es';
10
10
 
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
+ `;
30
+
31
+ const staticTypeUtils = `
32
+ // Type utils
33
+ type ExtractRequiredParameters<T extends Record<string, any>> = Pick<
34
+ T,
35
+ { [K in keyof T]: undefined extends T[K] ? never : K }[keyof T]
36
+ >;
37
+
38
+ type HasOneRequiredParameter<T extends TypedRouteList> = [TypedRouteParams[T]] extends [never]
39
+ ? false
40
+ : [keyof ExtractRequiredParameters<TypedRouteParams[T]>] extends [undefined]
41
+ ? false
42
+ : true;
43
+
44
+ type TypedLocationAsRelativeRaw<T extends TypedRouteList> = {
45
+ name?: T;
46
+ } & ([TypedRouteParams[T]] extends [never]
47
+ ? {}
48
+ : HasOneRequiredParameter<T> extends false
49
+ ? { params?: TypedRouteParams[T] }
50
+ : { params: TypedRouteParams[T] });
51
+
52
+ type ResolvedTypedLocationAsRelativeRaw<T extends TypedRouteList> = {
53
+ name?: T;
54
+ } & ([TypedRouteParams[T]] extends [never] ? {} : { params: Required<TypedRouteParams[T]> });
55
+
56
+ type TypedRouteLocationRaw = RouteQueryAndHash & TypedRouteNamedMapper & RouteLocationOptions;
57
+
58
+ type _TypedRoute = Omit<RouteLocationNormalizedLoaded, 'name' | 'params'> &
59
+ ResolvedTypedRouteNamedMapper;
60
+ type _TypedNamedRoute<T extends TypedRouteList> = Omit<
61
+ RouteLocationNormalizedLoaded,
62
+ 'name' | 'params'
63
+ > &
64
+ ResolvedTypedLocationAsRelativeRaw<T>;
65
+
66
+ /** Augmented Router interface */
67
+ interface _TypedRouter
68
+ extends Omit<Router, 'removeRoute' | 'hasRoute' | 'resolve' | 'push' | 'replace'> {
69
+ /**
70
+ * Remove an existing route by its name.
71
+ *
72
+ * @param name - Name of the route to remove
73
+ */
74
+ removeRoute(name: TypedRouteList): void;
75
+ /**
76
+ * Checks if a route with a given name exists
77
+ *
78
+ * @param name - Name of the route to check
79
+ */
80
+ hasRoute(name: TypedRouteList): boolean;
81
+ /**
82
+ * Returns the {@link RouteLocation | normalized version} of a
83
+ * {@link RouteLocationRaw | route location}. Also includes an \`href\` property
84
+ * that includes any existing \`base\`. By default the \`currentLocation\` used is
85
+ * \`route.currentRoute\` and should only be overriden in advanced use cases.
86
+ *
87
+ * @param to - Raw route location to resolve
88
+ * @param currentLocation - Optional current location to resolve against
89
+ */
90
+ resolve(
91
+ to: TypedRouteLocationRaw,
92
+ currentLocation?: RouteLocationNormalizedLoaded
93
+ ): RouteLocation & {
94
+ href: string;
95
+ };
96
+ /**
97
+ * Programmatically navigate to a new URL by pushing an entry in the history
98
+ * stack.
99
+ *
100
+ * @param to - Route location to navigate to
101
+ */
102
+ push(to: TypedRouteLocationRaw): Promise<NavigationFailure | void | undefined>;
103
+ /**
104
+ * Programmatically navigate to a new URL by replacing the current entry in
105
+ * the history stack.
106
+ *
107
+ * @param to - Route location to navigate to
108
+ */
109
+ replace(to: TypedRouteLocationRaw): Promise<NavigationFailure | void | undefined>;
110
+ }
111
+
112
+ export interface TypedRouter extends _TypedRouter {}
113
+ export type TypedRoute = _TypedRoute;
114
+ export type TypedNamedRoute<T extends TypedRouteList> = _TypedNamedRoute<T>;
115
+
116
+ declare global {
117
+ export interface TypedRouter extends _TypedRouter {}
118
+ export type TypedRoute = _TypedRoute;
119
+ export type TypedNamedRoute<T extends TypedRouteList> = _TypedNamedRoute<T>;
120
+ }
121
+
122
+ type TypedNuxtLinkProps = Omit<NuxtLinkProps, 'to'> & {
123
+ to: Omit<Exclude<RouteLocationRaw, string>, 'name'> & TypedRouteNamedMapper;
124
+ };
125
+
126
+ type _NuxtLink = DefineComponent<
127
+ TypedNuxtLinkProps,
128
+ {},
129
+ {},
130
+ import('vue').ComputedOptions,
131
+ import('vue').MethodOptions,
132
+ import('vue').ComponentOptionsMixin,
133
+ import('vue').ComponentOptionsMixin,
134
+ {},
135
+ string,
136
+ import('vue').VNodeProps &
137
+ import('vue').AllowedComponentProps &
138
+ import('vue').ComponentCustomProps,
139
+ Readonly<TypedNuxtLinkProps>,
140
+ {}
141
+ >;
142
+
143
+ declare module '@vue/runtime-core' {
144
+ export interface GlobalComponents {
145
+ NuxtLink: _NuxtLink;
146
+ }
147
+ }
148
+ `;
149
+
150
+ const watermarkTemplate = `
151
+ // @ts-nocheck
152
+ // eslint-disable
153
+ /**
154
+ * ---------------------------------------------------
155
+ * \u{1F697}\u{1F6A6} Generated by nuxt-typed-router. Do not modify !
156
+ * ---------------------------------------------------
157
+ * */
158
+
159
+ `;
160
+
161
+ function createDeclarationRoutesFile() {
162
+ return `
163
+ ${watermarkTemplate}
164
+
165
+ ${staticTypesImports}
166
+
167
+ ${staticTypeUtils}
168
+ `;
169
+ }
170
+
171
+ function createRuntimeIndexFile() {
172
+ return `
173
+ ${watermarkTemplate}
174
+ export * from './__routes';
175
+ export * from './__useTypedRouter';
176
+ export * from './__useTypedRoute';
177
+ `;
178
+ }
179
+
180
+ function createRuntimePluginFile(routesDeclTemplate) {
181
+ return `
182
+ ${watermarkTemplate}
183
+ import { defineNuxtPlugin } from '#app';
184
+
185
+ export default defineNuxtPlugin(() => {
186
+ const router = useRouter();
187
+ const route = useRoute();
188
+ const routesNames = ${routesDeclTemplate};
189
+
190
+ return {
191
+ provide: {
192
+ typedRouter: router as TypedRouter,
193
+ typedRoute: route as TypedRoute,
194
+ routesNames,
195
+ },
196
+ };
197
+ });
198
+ `;
199
+ }
200
+
201
+ function createTypedRouteListExport(routesList) {
202
+ return `export type TypedRouteList = ${routesList.map((m) => `'${m}'`).join("|\n")}`;
203
+ }
204
+ function createTypedRouteParamsExport(routesParams) {
205
+ return `export type TypedRouteParams = {
206
+ ${routesParams.map(
207
+ ({ name, params }) => `"${name}": ${params.length ? `{
208
+ ${params.map(({ key, required, type }) => `"${key}"${required ? "" : "?"}: ${type}`).join(",\n")}
209
+ }` : "never"}`
210
+ ).join(",\n")}
211
+ }`;
212
+ }
213
+ function createTypedRouteNamedMapperExport(routesParams) {
214
+ return `export type TypedRouteNamedMapper =
215
+ ${routesParams.map(
216
+ ({ name, params }) => `{name: "${name}" ${params.length ? `, params${params.some((s) => s.required) ? "" : "?"}: {
217
+ ${params.map(({ key, required, type }) => `"${key}"${required ? "" : "?"}: ${type}`).join(",\n")}
218
+ }` : ""}}`
219
+ ).join("|\n")}
220
+ `;
221
+ }
222
+ function createResolvedTypedRouteNamedMapperExport(routesParams) {
223
+ return `export type ResolvedTypedRouteNamedMapper =
224
+ {
225
+ name: TypedRouteList;
226
+ params: unknown;
227
+ } & (
228
+ ${routesParams.map(
229
+ ({ name, params }) => `{name: "${name}" ${params.length ? `, params: {
230
+ ${params.map(({ key, type }) => `"${key}": ${type}`).join(",\n")}
231
+ }` : ""}}`
232
+ ).join("|\n")}
233
+ )
234
+ `;
235
+ }
236
+
237
+ function createRuntimeRoutesFile({
238
+ routesList,
239
+ routesObjectTemplate,
240
+ routesDeclTemplate,
241
+ routesParams
242
+ }) {
243
+ return `
244
+ ${watermarkTemplate}
245
+
246
+ export const routesNames = ${routesObjectTemplate};
247
+
248
+ ${createTypedRouteListExport(routesList)}
249
+
250
+ export type RouteListDecl = ${routesDeclTemplate};
251
+
252
+ /**
253
+ * Routes params are only required for the exact targeted route name,
254
+ * vue-router behaviour allow to navigate between children routes without the need to provide all the params every time.
255
+ * So we can't enforce params when navigating between routes, only a \`[xxx].vue\` page will have required params in the type definition
256
+ *
257
+ *
258
+ * */
259
+
260
+ ${createTypedRouteParamsExport(routesParams)}
261
+
262
+ ${createTypedRouteNamedMapperExport(routesParams)}
263
+
264
+ ${createResolvedTypedRouteNamedMapperExport(routesParams)}
265
+ `;
266
+ }
267
+
268
+ function createUseTypedRouteFile(routesDeclTemplate) {
269
+ return `
270
+ ${watermarkTemplate}
271
+ import { useRoute } from '#app';
272
+ import { TypedRouteList } from './__routes';
273
+
274
+ /** Acts the same as \`useRoute\`, but typed.
275
+ *
276
+ * @exemple
277
+ *
278
+ * \`\`\`ts
279
+ * const route = useTypedRoute();
280
+ * \`\`\`
281
+ *
282
+ * \`\`\`ts
283
+ * const route = useTypedRoute('my-route-with-param-id');
284
+ * route.params.id // autocompletes!
285
+ * \`\`\`
286
+ *
287
+ * \`\`\`ts
288
+ * const route = useTypedRoute();
289
+ * if (route.name === 'my-route-with-param-id') {
290
+ * route.params.id // autocompletes!
291
+ * }
292
+ * \`\`\`
293
+ */
294
+ export function useTypedRoute<T extends TypedRouteList = never>(
295
+ name?: T
296
+ ): [T] extends [never] ? TypedRoute : TypedNamedRoute<T> {
297
+ const route = useRoute();
298
+
299
+ return route as any;
300
+ }
301
+
302
+ `;
303
+ }
304
+
305
+ function createRuntimeUseTypedRouterFile(routesDeclTemplate) {
306
+ return `
307
+ ${watermarkTemplate}
308
+ import { useRouter } from '#app';
309
+ import { TypedRouter } from './typed-router';
310
+ import { RouteListDecl } from './__routes';
311
+
312
+ /** Returns instances of $typedRouter and $routesList fully typed to use in your components or your Vuex/Pinia store
313
+ *
314
+ * @exemple
315
+ *
316
+ * \`\`\`ts
317
+ * const { router, routes } = useTypedRouter();
318
+ * \`\`\`
319
+ */
320
+ export function useTypedRouter(): TypedRouter {
321
+ const router = useRouter();
322
+
323
+ return router;
324
+ };
325
+
326
+ `;
327
+ }
328
+
11
329
  const { resolveConfig, format } = prettier;
12
330
  const defaultPrettierOptions = {
13
331
  printWidth: 100,
@@ -36,8 +354,13 @@ async function formatOutputWithPrettier(template) {
36
354
  }
37
355
 
38
356
  dirname(fileURLToPath(import.meta.url));
39
- async function saveRouteFiles(outDir, srcDir, fileName, content) {
357
+ async function processPathAndWriteFile({
358
+ content,
359
+ fileName,
360
+ srcDir
361
+ }) {
40
362
  try {
363
+ const outDir = `.nuxt/typed-router`;
41
364
  const processedOutDir = resolve(srcDir, outDir);
42
365
  const outputFile = resolve(process.cwd(), `${processedOutDir}/${fileName}`);
43
366
  const formatedContent = await formatOutputWithPrettier(content);
@@ -63,6 +386,87 @@ async function writeFile(path, content) {
63
386
  }
64
387
  }
65
388
 
389
+ function handlePluginFileSave({
390
+ nuxt,
391
+ srcDir,
392
+ routesDeclTemplate
393
+ }) {
394
+ const pluginName = "__typed-router.ts";
395
+ nuxt.hook("build:done", async () => {
396
+ const pluginFolder = `${srcDir}/plugins`;
397
+ await processPathAndWriteFile({
398
+ outDir: pluginFolder,
399
+ srcDir,
400
+ fileName: pluginName,
401
+ content: createRuntimePluginFile(routesDeclTemplate)
402
+ });
403
+ });
404
+ }
405
+
406
+ async function saveGeneratedFiles({
407
+ srcDir,
408
+ outputData: { routesDeclTemplate, routesList, routesObjectTemplate, routesParams }
409
+ }) {
410
+ const filesMap = [
411
+ {
412
+ fileName: "__useTypedRouter.ts",
413
+ content: createRuntimeUseTypedRouterFile()
414
+ },
415
+ {
416
+ fileName: "__useTypedRoute.ts",
417
+ content: createUseTypedRouteFile()
418
+ },
419
+ {
420
+ fileName: `__routes.ts`,
421
+ content: createRuntimeRoutesFile({
422
+ routesList,
423
+ routesObjectTemplate,
424
+ routesDeclTemplate,
425
+ routesParams
426
+ })
427
+ },
428
+ {
429
+ fileName: `typed-router.d.ts`,
430
+ content: createDeclarationRoutesFile()
431
+ },
432
+ {
433
+ fileName: "index.ts",
434
+ content: createRuntimeIndexFile()
435
+ }
436
+ ];
437
+ await Promise.all(
438
+ filesMap.map(({ content, fileName }) => processPathAndWriteFile({ srcDir, content, fileName }))
439
+ );
440
+ console.log(logSymbols.success, `[typed-router] Routes definitions generated`);
441
+ }
442
+
443
+ function isItemLast(array, index) {
444
+ return array ? index === array.length - 1 : false;
445
+ }
446
+
447
+ const routeParamExtractRegxp = /:(\w+)/;
448
+ function extractRouteParamsFromPath(path, isIndexFileForRouting, previousParams) {
449
+ const params = path.match(routeParamExtractRegxp) ?? [];
450
+ params?.shift();
451
+ let allMergedParams = params.map(
452
+ (m) => ({
453
+ key: m,
454
+ type: "string | number",
455
+ required: true
456
+ })
457
+ );
458
+ if (previousParams?.length) {
459
+ allMergedParams = previousParams.map((m) => ({ ...m, required: false })).concat(allMergedParams);
460
+ }
461
+ if (!params.length && isIndexFileForRouting) {
462
+ const lastItem = allMergedParams[allMergedParams.length - 1];
463
+ if (lastItem) {
464
+ lastItem.required = true;
465
+ }
466
+ }
467
+ return allMergedParams;
468
+ }
469
+
66
470
  function extractMatchingSiblings(mainRoute, siblingRoutes) {
67
471
  return siblingRoutes?.filter((s) => {
68
472
  const chunkName = extractChunkMain(mainRoute.file);
@@ -92,61 +496,6 @@ function extractChunkMain(chunkName) {
92
496
  return chunkArray?.join("/");
93
497
  }
94
498
 
95
- const routeParamExtractRegxp = /:(\w+)/;
96
- function extractRouteParamsFromPath(path, previousParams) {
97
- const params = path.match(routeParamExtractRegxp) ?? [];
98
- params?.shift();
99
- let allMergedParams = params.map(
100
- (m) => ({
101
- key: m,
102
- type: "string | number",
103
- required: true
104
- })
105
- );
106
- if (previousParams?.length) {
107
- allMergedParams = allMergedParams.concat(
108
- previousParams.map((m) => ({ ...m, required: false }))
109
- );
110
- }
111
- return allMergedParams;
112
- }
113
-
114
- function isItemLast(array, index) {
115
- return index === array.length - 1;
116
- }
117
- function constructRouteMap(routesConfig) {
118
- try {
119
- let routesObjectTemplate = "{";
120
- let routesDeclTemplate = "{";
121
- let routesList = [];
122
- let routesParams = [];
123
- const output = { routesObjectTemplate, routesDeclTemplate, routesList, routesParams };
124
- startGeneratorProcedure({
125
- output,
126
- routesConfig
127
- });
128
- return output;
129
- } catch (e) {
130
- throw new Error("Generation failed");
131
- }
132
- }
133
- function startGeneratorProcedure({
134
- output,
135
- routesConfig
136
- }) {
137
- routesConfig.forEach((route, index) => {
138
- const rootSiblingsRoutes = routesConfig.filter((rt) => rt.chunkName !== route.chunkName);
139
- walkThoughRoutes({
140
- route,
141
- level: 0,
142
- output,
143
- siblings: rootSiblingsRoutes,
144
- isLast: isItemLast(routesConfig, index)
145
- });
146
- });
147
- output.routesObjectTemplate += "}";
148
- output.routesDeclTemplate += "}";
149
- }
150
499
  function walkThoughRoutes({
151
500
  route,
152
501
  level,
@@ -168,7 +517,7 @@ function walkThoughRoutes({
168
517
  const nameKey = camelCase(parentPath || "index");
169
518
  output.routesObjectTemplate += `${nameKey}:{`;
170
519
  output.routesDeclTemplate += `"${nameKey}":{`;
171
- const allRouteParams = extractRouteParamsFromPath(route.path, previousParams);
520
+ const allRouteParams = extractRouteParamsFromPath(route.path, false, previousParams);
172
521
  childrenChunks?.map(
173
522
  (routeConfig, index) => walkThoughRoutes({
174
523
  route: routeConfig,
@@ -193,7 +542,12 @@ function walkThoughRoutes({
193
542
  output.routesObjectTemplate += `'${keyName}': '${route.name}' as const,`;
194
543
  output.routesDeclTemplate += `"${keyName}": "${route.name}"${isLast ? "" : ","}`;
195
544
  output.routesList.push(route.name);
196
- const allRouteParams = extractRouteParamsFromPath(route.path, previousParams);
545
+ const isIndexFileForRouting = route.path === "";
546
+ const allRouteParams = extractRouteParamsFromPath(
547
+ route.path,
548
+ isIndexFileForRouting,
549
+ previousParams
550
+ );
197
551
  output.routesParams.push({
198
552
  name: route.name,
199
553
  params: allRouteParams
@@ -201,227 +555,49 @@ function walkThoughRoutes({
201
555
  }
202
556
  }
203
557
 
204
- const signatureTemplate = `/**
205
- * ---------------------
206
- * \u{1F697}\u{1F6A6} Generated by nuxt-typed-router. Do not modify !
207
- * ---------------------
208
- * */
209
-
210
- `;
211
- const staticDeclImports = `
212
- import type {
213
- NavigationFailure,
214
- RouteLocation,
215
- RouteLocationNormalizedLoaded,
216
- RouteLocationOptions,
217
- RouteQueryAndHash,
218
- } from 'vue-router';
219
- import type { TypedRouteList } from './__routes'
220
- `;
221
- const staticDeclarations = `
222
- type TypedRouteParamsStructure = {
223
- [K in TypedRouteList]: Record<string, string | number> | never;
224
- };
225
-
226
- type TypedLocationAsRelativeRaw<T extends TypedRouteList> = {
227
- name?: T;
228
- params?: TypedRouteParams[T];
229
- };
230
-
231
- type TypedRouteLocationRaw<T extends TypedRouteList> = RouteQueryAndHash &
232
- TypedLocationAsRelativeRaw<T> &
233
- RouteLocationOptions;
234
-
235
- interface _TypedRouter {
236
- /**
237
- * Remove an existing route by its name.
238
- *
239
- * @param name - Name of the route to remove
240
- */
241
- removeRoute(name: TypedRouteList): void;
242
- /**
243
- * Checks if a route with a given name exists
244
- *
245
- * @param name - Name of the route to check
246
- */
247
- hasRoute(name: TypedRouteList): boolean;
248
- /**
249
- * Returns the {@link RouteLocation | normalized version} of a
250
- * {@link RouteLocationRaw | route location}. Also includes an \`href\` property
251
- * that includes any existing \`base\`. By default the \`currentLocation\` used is
252
- * \`route.currentRoute\` and should only be overriden in advanced use cases.
253
- *
254
- * @param to - Raw route location to resolve
255
- * @param currentLocation - Optional current location to resolve against
256
- */
257
- resolve<T extends TypedRouteList>(
258
- to: TypedRouteLocationRaw<T>,
259
- currentLocation?: RouteLocationNormalizedLoaded
260
- ): RouteLocation & {
261
- href: string;
262
- };
263
- /**
264
- * Programmatically navigate to a new URL by pushing an entry in the history
265
- * stack.
266
- *
267
- * @param to - Route location to navigate to
268
- */
269
- push<T extends TypedRouteList>(
270
- to: TypedRouteLocationRaw<T>
271
- ): Promise<NavigationFailure | void | undefined>;
272
- /**
273
- * Programmatically navigate to a new URL by replacing the current entry in
274
- * the history stack.
275
- *
276
- * @param to - Route location to navigate to
277
- */
278
- replace<T extends TypedRouteList>(
279
- to: TypedRouteLocationRaw<T>
280
- ): Promise<NavigationFailure | void | undefined>;
281
- }
282
-
283
- export interface TypedRouter extends _TypedRouter {}
284
- declare global {
285
- export interface TypedRouter extends _TypedRouter {}
558
+ function constructRouteMap(routesConfig) {
559
+ try {
560
+ let routesObjectTemplate = "{";
561
+ let routesDeclTemplate = "{";
562
+ let routesList = [];
563
+ let routesParams = [];
564
+ const output = { routesObjectTemplate, routesDeclTemplate, routesList, routesParams };
565
+ startGenerator({
566
+ output,
567
+ routesConfig
568
+ });
569
+ return output;
570
+ } catch (e) {
571
+ throw new Error("Generation failed");
286
572
  }
287
- `;
288
-
289
- function createRuntimePluginFile(routesDeclTemplate) {
290
- return `
291
- ${signatureTemplate}
292
- import { defineNuxtPlugin } from '#app';
293
-
294
- export default defineNuxtPlugin(() => {
295
- const router = useRouter();
296
- const routesList = ${routesDeclTemplate};
297
-
298
- return {
299
- provide: {
300
- typedRouter: router as TypedRouter,
301
- routesList,
302
- },
303
- };
304
- });
305
- `;
306
- }
307
- function createRuntimeHookFile(routesDeclTemplate) {
308
- return `
309
- ${signatureTemplate}
310
- import { useNuxtApp } from '#app';
311
- import { TypedRouter, RouteListDecl } from './typed-router';
312
-
313
- /** Returns instances of $typedRouter and $routesList fully typed to use in your components or your Vuex/Pinia store
314
- *
315
- * @exemple
316
- *
317
- * \`\`\`ts
318
- * const { router, routes } = useTypedRouter();
319
- * \`\`\`
320
- */
321
- export const useTypedRouter = (): {
322
- /** Export of $router with type check */
323
- router: TypedRouter,
324
- /** Contains a typed dictionnary of all your route names (for syntax sugar) */
325
- routes: RouteListDecl
326
- } => {
327
- const { $router } = useNuxtApp();
328
-
329
- const routesList = ${routesDeclTemplate};
330
-
331
- return {
332
- router: $router,
333
- routes: routesList,
334
- } as any;
335
- };
336
-
337
- `;
338
573
  }
339
- function createRuntimeIndexFile() {
340
- return `
341
- ${signatureTemplate}
342
- export * from './__routes';
343
- export * from './__useTypedRouter';
344
- `;
345
- }
346
- function createRuntimeRoutesFile({
347
- routesList,
348
- routesObjectTemplate,
349
- routesObjectName
350
- }) {
351
- return `
352
- ${signatureTemplate}
353
-
354
- export const ${routesObjectName} = ${routesObjectTemplate};
355
-
356
- ${createTypedRouteListExport(routesList)}
357
- `;
358
- }
359
- function createDeclarationRoutesFile({
360
- routesDeclTemplate,
361
- routesList,
362
- routesParams
363
- }) {
364
- return `
365
- ${signatureTemplate}
366
- ${staticDeclImports}
367
-
368
- export type RouteListDecl = ${routesDeclTemplate};
369
-
370
- ${createTypedRouteParamsExport(routesParams)}
371
-
372
- ${staticDeclarations}
373
- `;
374
- }
375
- function createTypedRouteListExport(routesList) {
376
- return `export type TypedRouteList = ${routesList.map((m) => `'${m}'`).join("|\n")}`;
377
- }
378
- function createTypedRouteParamsExport(routesParams) {
379
- return `export type TypedRouteParams = {
380
- ${routesParams.map(
381
- ({ name, params }) => `"${name}": ${params.length ? `{
382
- ${params.map(({ key, required, type }) => `"${key}"${required ? "" : "?"}: ${type}`).join(",\n")}
383
- }` : "never"}`
384
- ).join(",\n")}
385
- }`;
574
+ function startGenerator({ output, routesConfig }) {
575
+ routesConfig.forEach((route, index) => {
576
+ const rootSiblingsRoutes = routesConfig.filter((rt) => rt.chunkName !== route.chunkName);
577
+ walkThoughRoutes({
578
+ route,
579
+ level: 0,
580
+ output,
581
+ siblings: rootSiblingsRoutes,
582
+ isLast: isItemLast(routesConfig, index)
583
+ });
584
+ });
585
+ output.routesObjectTemplate += "}";
586
+ output.routesDeclTemplate += "}";
386
587
  }
387
588
 
388
- function routeHook(outDir, routesObjectName, srcDir, nuxt) {
589
+ function createTypedRouter({ srcDir, plugin, nuxt }) {
389
590
  try {
390
591
  extendPages(async (routes) => {
391
592
  if (routes.length) {
392
- const { routesDeclTemplate, routesList, routesObjectTemplate, routesParams } = constructRouteMap(routes);
393
- const pluginName = "__typed-router.ts";
394
- nuxt.hook("build:done", async () => {
395
- const pluginFolder = `${srcDir}/plugins`;
396
- await saveRouteFiles(
397
- pluginFolder,
398
- srcDir,
399
- pluginName,
400
- createRuntimePluginFile(routesDeclTemplate)
401
- );
593
+ const outputData = constructRouteMap(routes);
594
+ if (plugin) {
595
+ handlePluginFileSave({ nuxt, routesDeclTemplate: outputData.routesDeclTemplate, srcDir });
596
+ }
597
+ await saveGeneratedFiles({
598
+ srcDir,
599
+ outputData
402
600
  });
403
- await Promise.all([
404
- saveRouteFiles(
405
- outDir,
406
- srcDir,
407
- "__useTypedRouter.ts",
408
- createRuntimeHookFile(routesDeclTemplate)
409
- ),
410
- saveRouteFiles(
411
- outDir,
412
- srcDir,
413
- `__routes.ts`,
414
- createRuntimeRoutesFile({ routesList, routesObjectTemplate, routesObjectName })
415
- ),
416
- saveRouteFiles(
417
- outDir,
418
- srcDir,
419
- `typed-router.d.ts`,
420
- createDeclarationRoutesFile({ routesDeclTemplate, routesList, routesParams })
421
- ),
422
- saveRouteFiles(outDir, srcDir, "index.ts", createRuntimeIndexFile())
423
- ]);
424
- console.log(logSymbols.success, `[typed-router] Routes definitions generated`);
425
601
  } else {
426
602
  console.log(
427
603
  logSymbols.warning,
@@ -445,14 +621,18 @@ const module = defineNuxtModule({
445
621
  compatibility: { nuxt: "^3.0.0-rc.1", bridge: false }
446
622
  },
447
623
  defaults: {
448
- outDir: `./generated`,
449
- routesObjectName: "routerPagesNames"
624
+ plugin: false
450
625
  },
451
626
  setup(moduleOptions, nuxt) {
452
627
  const srcDir = nuxt.options.srcDir;
453
- const { outDir, routesObjectName } = moduleOptions;
454
- nuxt.hook("pages:extend", () => routeHook(outDir, routesObjectName, srcDir, nuxt));
455
- routeHook(outDir, routesObjectName, srcDir, nuxt);
628
+ const { plugin } = moduleOptions;
629
+ nuxt.options.alias = {
630
+ ...nuxt.options.alias,
631
+ "@typed-router": fileURLToPath(
632
+ new URL(`${nuxt.options.rootDir}/.nuxt/typed-router`, import.meta.url)
633
+ )
634
+ };
635
+ nuxt.hook("pages:extend", () => createTypedRouter({ srcDir, nuxt, plugin }));
456
636
  }
457
637
  });
458
638
 
package/main.d.ts CHANGED
@@ -1,2 +1 @@
1
- import './dist/types';
2
1
  export { default } from './dist/module';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-typed-router",
3
- "version": "1.2.4",
3
+ "version": "2.0.0-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",
@@ -20,9 +20,11 @@
20
20
  "dev": "nuxi dev playground",
21
21
  "dev:build": "nuxi build playground",
22
22
  "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
23
- "build:test": "cross-env NUXT_BUILD_TYPE=stub yarn prepack && yarn dev:build",
24
- "test": "yarn build:test && vitest run",
25
- "test:watch": "yarn build:test && vitest"
23
+ "build:test": "cross-env NUXT_BUILD_TYPE=stub pnpm run prepack && pnpm run dev:build",
24
+ "test": "pnpm run dev:prepare && pnpm run build:test && vitest run",
25
+ "test:watch": "pnpm run build:test && vitest",
26
+ "docs:dev": "cd docs && pnpm run dev",
27
+ "docs:buid": "(cd docs && nuxi build)"
26
28
  },
27
29
  "publishConfig": {
28
30
  "access": "public"
@@ -67,11 +69,11 @@
67
69
  "@types/node": "^17.0.23",
68
70
  "@types/prettier": "^2.7.2",
69
71
  "cross-env": "^7.0.3",
70
- "eslint": "8.30.0",
71
- "eslint-config-prettier": "^8.5.0",
72
+ "eslint": "8.31.0",
73
+ "eslint-config-prettier": "^8.6.0",
72
74
  "nuxt": "3.0.0",
73
75
  "typescript": "^4.9.4",
74
- "vitest": "^0.26.1",
76
+ "vitest": "^0.27.0",
75
77
  "vue-router": "^4.1.6"
76
78
  }
77
79
  }