nuxt-typed-router 2.2.0 → 2.2.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
@@ -93,6 +93,7 @@ export default defineNuxtConfig({
93
93
 
94
94
  - [ ] Add `path` autocomplete with TS string templates
95
95
  - [ ] Enforce strong params typing depending of origin route
96
+ - [ ] Add support for `validate` in `definePageMeta`
96
97
 
97
98
 
98
99
  ## Development
package/dist/module.d.ts CHANGED
@@ -6,6 +6,30 @@ interface ModuleOptions {
6
6
  * @default false
7
7
  */
8
8
  plugin?: boolean;
9
+ /**
10
+ * Prevent passing a string path to `router` or `<NuxtLink/>`
11
+ * Ex:
12
+ *
13
+ * ```ts
14
+ * router.push('/login'); // Error ❌
15
+ * ```
16
+ * @default false
17
+ */
18
+ strict?: boolean | StrictOptions;
19
+ }
20
+ interface StrictOptions {
21
+ NuxtLink: StrictParamsOptions;
22
+ router: StrictParamsOptions;
23
+ }
24
+ interface StrictParamsOptions {
25
+ /**
26
+ * @default false
27
+ */
28
+ strictToArgument?: boolean;
29
+ /**
30
+ * @default false
31
+ */
32
+ strictRouteLocation?: boolean;
9
33
  }
10
34
 
11
35
  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": "2.2.0"
8
+ "version": "2.2.2-beta.0"
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { addPluginTemplate, extendPages, defineNuxtModule, createResolver } from '@nuxt/kit';
2
2
  import chalk from 'chalk';
3
3
  import logSymbols from 'log-symbols';
4
+ import { defu } from 'defu';
4
5
  import prettier from 'prettier';
5
6
  import fs from 'fs';
6
7
  import { fileURLToPath } from 'url';
@@ -8,6 +9,66 @@ import { dirname, resolve } from 'pathe';
8
9
  import mkdirp from 'mkdirp';
9
10
  import { camelCase } from 'lodash-es';
10
11
 
12
+ class ModuleOptionsStore {
13
+ constructor() {
14
+ this.plugin = false;
15
+ this.strict = false;
16
+ this.autoImport = false;
17
+ this.rootDir = "";
18
+ }
19
+ updateOptions(options) {
20
+ if (options.plugin != null)
21
+ this.plugin = options.plugin;
22
+ if (options.strict != null)
23
+ this.strict = options.strict;
24
+ if (options.autoImport != null)
25
+ this.autoImport = options.autoImport;
26
+ if (options.rootDir != null)
27
+ this.rootDir = options.rootDir;
28
+ }
29
+ getResolvedStrictOptions() {
30
+ let resolved;
31
+ if (typeof this.strict === "boolean") {
32
+ if (this.strict) {
33
+ resolved = {
34
+ NuxtLink: {
35
+ strictRouteLocation: true,
36
+ strictToArgument: true
37
+ },
38
+ router: {
39
+ strictRouteLocation: true,
40
+ strictToArgument: true
41
+ }
42
+ };
43
+ } else {
44
+ resolved = {
45
+ NuxtLink: {
46
+ strictRouteLocation: false,
47
+ strictToArgument: false
48
+ },
49
+ router: {
50
+ strictRouteLocation: false,
51
+ strictToArgument: false
52
+ }
53
+ };
54
+ }
55
+ } else {
56
+ resolved = defu(this.strict, {
57
+ NuxtLink: {
58
+ strictRouteLocation: false,
59
+ strictToArgument: false
60
+ },
61
+ router: {
62
+ strictRouteLocation: false,
63
+ strictToArgument: false
64
+ }
65
+ });
66
+ }
67
+ return resolved;
68
+ }
69
+ }
70
+ const moduleOptionStore = new ModuleOptionsStore();
71
+
11
72
  function createRoutesNamesListExport(routesList) {
12
73
  return `
13
74
  /**
@@ -74,6 +135,23 @@ function createRoutesNamedLocationsResolvedExport(routesParams) {
74
135
  `;
75
136
  }
76
137
 
138
+ function createRoutesParamsRecordResolvedExport(routesParams) {
139
+ return `
140
+ /**
141
+ * Record resolved used for resolved routes
142
+ *
143
+ * */
144
+ export type RoutesParamsRecordResolved = {
145
+ ${routesParams.map(
146
+ ({ name, params }) => `"${name}": ${params.length ? `{
147
+ ${params.map(
148
+ ({ key, notRequiredOnPage, catchAll }) => `"${key}"${notRequiredOnPage ? "?" : ""}: string${catchAll ? "[]" : ""}`
149
+ ).join(",\n")}
150
+ }` : "never"}`
151
+ ).join(",\n")}
152
+ }`;
153
+ }
154
+
77
155
  function createRoutesTypesFile({
78
156
  routesList,
79
157
  routesObjectTemplate,
@@ -86,6 +164,8 @@ function createRoutesTypesFile({
86
164
  ${createRoutesNamesListExport(routesList)}
87
165
 
88
166
  ${createRoutesParamsRecordExport(routesParams)}
167
+
168
+ ${createRoutesParamsRecordResolvedExport(routesParams)}
89
169
 
90
170
  ${createRoutesNamedLocationsExport(routesParams)}
91
171
 
@@ -100,7 +180,24 @@ function createRoutesTypesFile({
100
180
  );
101
181
  }
102
182
 
183
+ function isItemLast(array, index) {
184
+ return array ? index === array.length - 1 : false;
185
+ }
186
+ function returnIfTrue(condition, template, otherwise) {
187
+ if (condition) {
188
+ return template;
189
+ }
190
+ return otherwise ?? "";
191
+ }
192
+ function returnIfFalse(condition, template, otherwise) {
193
+ if (!condition) {
194
+ return template;
195
+ }
196
+ return otherwise ?? "";
197
+ }
198
+
103
199
  function createTypedRouterFile() {
200
+ const strictOptions = moduleOptionStore.getResolvedStrictOptions();
104
201
  return (
105
202
  /* typescript */
106
203
  `
@@ -118,6 +215,7 @@ function createTypedRouterFile() {
118
215
  RoutesNamedLocationsResolved,
119
216
  RoutesNamesList,
120
217
  RoutesParamsRecord,
218
+ RoutesParamsRecordResolved
121
219
  } from './__routes';
122
220
  import type { HasOneRequiredParameter } from './__types_utils';
123
221
 
@@ -129,15 +227,22 @@ function createTypedRouterFile() {
129
227
  * {@link RouteLocationRaw}
130
228
  * */
131
229
  export type TypedRouteLocationRaw =
132
- | (Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params'> & RoutesNamedLocations)
133
- | string;
230
+ | (Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params' ${returnIfTrue(
231
+ strictOptions.NuxtLink.strictRouteLocation,
232
+ `| 'path'`
233
+ )}> & RoutesNamedLocations)
234
+ ${returnIfFalse(strictOptions.NuxtLink.strictToArgument, "| string")};
235
+
134
236
 
135
237
  /**
136
238
  * Alternative version of {@link TypedRouteLocationRaw} but with a name generic
137
239
  */
138
240
  export type TypedRouteLocationRawFromName<T extends RoutesNamesList> =
139
- | (Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params'> & TypedLocationAsRelativeRaw<T>)
140
- | string;
241
+ | (Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params' ${returnIfTrue(
242
+ strictOptions.NuxtLink.strictRouteLocation,
243
+ `| 'path'`
244
+ )}> & TypedLocationAsRelativeRaw<T>)
245
+ ${returnIfFalse(strictOptions.NuxtLink.strictToArgument, "| string")};
141
246
 
142
247
  /**
143
248
  * Generic providing inference and dynamic inclusion of \`params\` property
@@ -226,7 +331,7 @@ function createTypedRouterFile() {
226
331
  * */
227
332
  export type TypedResolvedMatcherLocation<T extends RoutesNamesList> = {
228
333
  name: T;
229
- } & ([RoutesParamsRecord[T]] extends [never] ? {} : { params: RoutesParamsRecord[T] });
334
+ } & ([RoutesParamsRecordResolved[T]] extends [never] ? {} : { params: RoutesParamsRecordResolved[T] });
230
335
 
231
336
 
232
337
  /**
@@ -238,7 +343,9 @@ function createTypedRouterFile() {
238
343
  );
239
344
  }
240
345
 
241
- function createTypedRouterDefinitionFile({ autoImport, plugin }) {
346
+ function createTypedRouterDefinitionFile() {
347
+ const { plugin, autoImport } = moduleOptionStore;
348
+ const strictOptions = moduleOptionStore.getResolvedStrictOptions();
242
349
  return (
243
350
  /* typescript */
244
351
  `
@@ -254,17 +361,24 @@ function createTypedRouterDefinitionFile({ autoImport, plugin }) {
254
361
 
255
362
  declare global {
256
363
 
257
- ${autoImport ? (
364
+ ${returnIfTrue(
365
+ autoImport,
258
366
  /* typescript */
259
367
  `
260
368
  const useRoute: typeof _useRoute;
261
369
  const useRouter: typeof _useRouter;
262
370
  const navigateTo: typeof _navigateTo;`
263
- ) : ""}
371
+ )}
264
372
  }
265
373
 
266
374
  type TypedNuxtLinkProps = Omit<NuxtLinkProps, 'to'> & {
267
- to: string | Omit<Exclude<RouteLocationRaw, string>, 'name'> & RoutesNamedLocations;
375
+ to: ${returnIfFalse(
376
+ strictOptions.NuxtLink.strictToArgument,
377
+ "string |"
378
+ )} Omit<Exclude<RouteLocationRaw, string>, 'name' | 'params' ${returnIfTrue(
379
+ strictOptions.NuxtLink.strictRouteLocation,
380
+ `| 'path'`
381
+ )}> & RoutesNamedLocations;
268
382
  };
269
383
 
270
384
  export type TypedNuxtLink = DefineComponent<
@@ -290,7 +404,8 @@ function createTypedRouterDefinitionFile({ autoImport, plugin }) {
290
404
  }
291
405
  }
292
406
 
293
- ${plugin ? (
407
+ ${returnIfTrue(
408
+ plugin,
294
409
  /* typescript */
295
410
  `
296
411
  interface CustomPluginProperties {
@@ -305,7 +420,7 @@ function createTypedRouterDefinitionFile({ autoImport, plugin }) {
305
420
  interface ComponentCustomProperties extends CustomPluginProperties {}
306
421
  }
307
422
  `
308
- ) : ""}
423
+ )}
309
424
  `
310
425
  );
311
426
  }
@@ -523,10 +638,10 @@ dirname(fileURLToPath(import.meta.url));
523
638
  async function processPathAndWriteFile({
524
639
  content,
525
640
  fileName,
526
- rootDir,
527
641
  outDir
528
642
  }) {
529
643
  try {
644
+ const { rootDir } = moduleOptionStore;
530
645
  const finalOutDir = outDir ?? `.nuxt/typed-router`;
531
646
  const processedOutDir = resolve(rootDir, finalOutDir);
532
647
  const outputFile = resolve(process.cwd(), `${processedOutDir}/${fileName}`);
@@ -566,9 +681,6 @@ const watermarkTemplate = `
566
681
 
567
682
  let previousGeneratedRoutes = "";
568
683
  async function saveGeneratedFiles({
569
- rootDir,
570
- autoImport,
571
- plugin,
572
684
  outputData: { routesDeclTemplate, routesList, routesObjectTemplate, routesParams }
573
685
  }) {
574
686
  const filesMap = [
@@ -603,7 +715,7 @@ async function saveGeneratedFiles({
603
715
  },
604
716
  {
605
717
  fileName: `typed-router.d.ts`,
606
- content: createTypedRouterDefinitionFile({ autoImport, plugin })
718
+ content: createTypedRouterDefinitionFile()
607
719
  },
608
720
  {
609
721
  fileName: "index.ts",
@@ -617,7 +729,7 @@ async function saveGeneratedFiles({
617
729
 
618
730
  ${content}
619
731
  `;
620
- return processPathAndWriteFile({ rootDir, content: waterMakeredContent, fileName });
732
+ return processPathAndWriteFile({ content: waterMakeredContent, fileName });
621
733
  })
622
734
  );
623
735
  if (previousGeneratedRoutes !== routesList.join(",")) {
@@ -626,10 +738,6 @@ async function saveGeneratedFiles({
626
738
  }
627
739
  }
628
740
 
629
- function isItemLast(array, index) {
630
- return array ? index === array.length - 1 : false;
631
- }
632
-
633
741
  const routeParamExtractRegxp = /(:(\w+)(\(.+\)[*+]?)?(\?)?)+/g;
634
742
  function extractRouteParamsFromPath(path, isIndexFileForRouting, previousParams) {
635
743
  let params = [];
@@ -785,7 +893,6 @@ function startGenerator({ output, routesConfig }) {
785
893
  let hasLoggedNoPages = false;
786
894
  let hasRoutesDefined = false;
787
895
  async function createTypedRouter({
788
- plugin,
789
896
  nuxt,
790
897
  routesConfig,
791
898
  isHookCall = false
@@ -793,18 +900,19 @@ async function createTypedRouter({
793
900
  try {
794
901
  const rootDir = nuxt.options.rootDir;
795
902
  const autoImport = nuxt.options.imports.autoImport ?? true;
903
+ moduleOptionStore.updateOptions({ rootDir, autoImport });
796
904
  if (!isHookCall) {
797
905
  if (routesConfig) {
798
906
  await nuxt.callHook("pages:extend", routesConfig);
799
907
  return;
800
908
  }
801
909
  nuxt.hook("pages:extend", (routesConfig2) => {
802
- createTypedRouter({ nuxt, plugin, routesConfig: routesConfig2, isHookCall: true });
910
+ createTypedRouter({ nuxt, routesConfig: routesConfig2, isHookCall: true });
803
911
  });
804
912
  nuxt.hook("modules:done", () => {
805
- createTypedRouter({ nuxt, plugin, isHookCall: true });
913
+ createTypedRouter({ nuxt, isHookCall: true });
806
914
  });
807
- if (plugin) {
915
+ if (moduleOptionStore.plugin) {
808
916
  await handleAddPlugin();
809
917
  }
810
918
  return;
@@ -813,10 +921,7 @@ async function createTypedRouter({
813
921
  hasRoutesDefined = true;
814
922
  const outputData = constructRouteMap(routes);
815
923
  await saveGeneratedFiles({
816
- autoImport,
817
- rootDir,
818
- outputData,
819
- plugin
924
+ outputData
820
925
  });
821
926
  });
822
927
  setTimeout(() => {
@@ -844,11 +949,12 @@ const module = defineNuxtModule({
844
949
  compatibility: { nuxt: "^3.0.0-rc.1", bridge: false }
845
950
  },
846
951
  defaults: {
847
- plugin: false
952
+ plugin: false,
953
+ strict: false
848
954
  },
849
955
  setup(moduleOptions, nuxt) {
850
956
  const rootDir = nuxt.options.rootDir;
851
- const { plugin } = moduleOptions;
957
+ moduleOptionStore.updateOptions(moduleOptions);
852
958
  const { resolve } = createResolver(import.meta.url);
853
959
  nuxt.options.alias = {
854
960
  ...nuxt.options.alias,
@@ -857,8 +963,7 @@ const module = defineNuxtModule({
857
963
  nuxt.options.typescript.tsConfig = {
858
964
  include: ["./typed-router/typed-router.d.ts"]
859
965
  };
860
- const typedRouterOptions = { nuxt, plugin };
861
- createTypedRouter({ ...typedRouterOptions });
966
+ createTypedRouter({ nuxt });
862
967
  }
863
968
  });
864
969
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-typed-router",
3
- "version": "2.2.0",
3
+ "version": "2.2.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",
@@ -63,7 +63,8 @@
63
63
  "log-symbols": "^5.1.0",
64
64
  "mkdirp": "^1.0.4",
65
65
  "pathe": "1.1.0",
66
- "prettier": "2.8.3"
66
+ "prettier": "2.8.3",
67
+ "defu": "^6.1.2"
67
68
  },
68
69
  "devDependencies": {
69
70
  "@nuxt/module-builder": "^0.2.1",
@@ -74,14 +75,17 @@
74
75
  "@types/mkdirp": "^1.0.2",
75
76
  "@types/node": "^17.0.23",
76
77
  "@types/prettier": "^2.7.2",
78
+ "@typescript-eslint/eslint-plugin": "^5.49.0",
79
+ "@typescript-eslint/parser": "^5.49.0",
77
80
  "cross-env": "^7.0.3",
78
- "eslint": "8.32.0",
81
+ "eslint": "8.33.0",
79
82
  "eslint-config-prettier": "^8.6.0",
80
83
  "eslint-plugin-vue": "^9.9.0",
81
84
  "nuxt": "3.1.1",
82
85
  "playwright": "1.30.0",
83
86
  "typescript": "^4.9.4",
84
- "vitest": "^0.28.2",
87
+ "vitest": "^0.28.3",
88
+ "vue-eslint-parser": "^9.1.0",
85
89
  "vue-router": "^4.1.6",
86
90
  "vue-tsc": "^1.0.24"
87
91
  }