nuxt-typed-router 1.2.5 โ 2.0.0-beta.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 +15 -174
- package/dist/module.d.ts +1 -9
- package/dist/module.json +1 -1
- package/dist/module.mjs +471 -278
- package/main.d.ts +0 -1
- package/package.json +9 -7
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/
|
|
17
|
+
<img src='https://img.shields.io/npm/l/nuxt-typed-router.svg'>
|
|
13
18
|
|
|
14
|
-
|
|
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/
|
|
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
|
|
|
@@ -42,9 +47,9 @@ yarn add -D nuxt-typed-router
|
|
|
42
47
|
npm install -D nuxt-typed-router
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
###
|
|
50
|
+
### Nuxt 2 legacy
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
Nuxt 2 version is no longer maintained, but still available in [`nuxt2` branch](https://github.com/victorgarciaesgi/nuxt-typed-router/tree/nuxt2)
|
|
48
53
|
|
|
49
54
|
```bash
|
|
50
55
|
yarn add -D nuxt-typed-router@legacy
|
|
@@ -52,188 +57,24 @@ yarn add -D nuxt-typed-router@legacy
|
|
|
52
57
|
npm install -D nuxt-typed-router@legacy
|
|
53
58
|
```
|
|
54
59
|
|
|
55
|
-
#
|
|
60
|
+
# Quick start
|
|
56
61
|
|
|
57
62
|
First, register the module in the `nuxt.config.ts`
|
|
58
63
|
|
|
59
64
|
```ts
|
|
60
65
|
export default defineNuxtConfig({
|
|
61
66
|
modules: ['nuxt-typed-router'],
|
|
62
|
-
nuxtTypedRouter: {
|
|
63
|
-
// options
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Options:
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
interface ModuleOptions {
|
|
72
|
-
/** Output directory where you cant the files to be saved
|
|
73
|
-
* (ex: "./models")
|
|
74
|
-
* @default "<srcDir>/generated"
|
|
75
|
-
*/
|
|
76
|
-
outDir?: string;
|
|
77
|
-
/** Name of the routesNames object (ex: "routesTree")
|
|
78
|
-
* @default "routerPagesNames"
|
|
79
|
-
* */
|
|
80
|
-
routesObjectName?: string;
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
# Generated files
|
|
85
|
-
|
|
86
|
-
The module will generate 4 files each time you modify the `pages` folder :
|
|
87
|
-
|
|
88
|
-
- `~/<outDir>/__routes.ts` with the global object of the route names inside.
|
|
89
|
-
- `~/<outDir>/__useTypedRouter.ts` Composable to simply access your typed routes
|
|
90
|
-
- `~/<outDir>/typed-router.d.ts` containing the global typecript definitions and exports
|
|
91
|
-
- `~/plugins/__typed_router.ts` Plugin that will inject `$typedRouter` and `$routesList` (`@nuxt/kit` has problems registering plugin templates so this is a workaround)
|
|
92
|
-
|
|
93
|
-
# Usage in Vue/Nuxt
|
|
94
|
-
|
|
95
|
-
<br/>
|
|
96
|
-
|
|
97
|
-
### **_Requirements_**
|
|
98
|
-
|
|
99
|
-
You can specify the output dir of the generated files in your configuration. It defaults to `<srcDir>/generated`
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
export default defineNuxtConfig({
|
|
103
|
-
buildModules: ['nuxt-typed-router'],
|
|
104
|
-
nuxtTypedRouter: {
|
|
105
|
-
outDir: './generated',
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
# How it works
|
|
111
|
-
|
|
112
|
-
Given this structure
|
|
113
|
-
|
|
114
|
-
โโโ pages
|
|
115
|
-
โโโ index
|
|
116
|
-
โโโ content
|
|
117
|
-
โโโ [id].vue
|
|
118
|
-
โโโ content.vue
|
|
119
|
-
โโโ index.vue
|
|
120
|
-
โโโ communication.vue
|
|
121
|
-
โโโ statistics.vue
|
|
122
|
-
โโโ [user].vue
|
|
123
|
-
โโโ index.vue
|
|
124
|
-
โโโ forgotpassword.vue
|
|
125
|
-
โโโ reset-password.vue
|
|
126
|
-
โ โโโ login.vue
|
|
127
|
-
โโโ ...
|
|
128
|
-
|
|
129
|
-
The generated route list will look like this
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
export const routerPagesNames = {
|
|
133
|
-
forgotpassword: 'forgotpassword' as const,
|
|
134
|
-
login: 'login' as const,
|
|
135
|
-
resetPassword: 'reset-password' as const,
|
|
136
|
-
index: {
|
|
137
|
-
index: 'index' as const,
|
|
138
|
-
communication: 'index-communication' as const,
|
|
139
|
-
content: {
|
|
140
|
-
id: 'index-content-id' as const,
|
|
141
|
-
},
|
|
142
|
-
statistics: 'index-statistics' as const,
|
|
143
|
-
user: 'index-user' as const,
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
export type TypedRouteList =
|
|
147
|
-
| 'forgotpassword'
|
|
148
|
-
| 'login'
|
|
149
|
-
| 'reset-password'
|
|
150
|
-
| 'index'
|
|
151
|
-
| 'index-communication'
|
|
152
|
-
| 'index-content-id'
|
|
153
|
-
| 'index-statistics'
|
|
154
|
-
| 'index-user';
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
> nuxt-typed-router will also create a plugin in your `<srcDir>/plugins` folder with the injected `$typedRouter` and `$routesList` helpers
|
|
158
|
-
|
|
159
|
-
# Usage with `useTypedRouter` hook
|
|
160
|
-
|
|
161
|
-
`useTypedRouter` is an exported composable from nuxt-typed-router. It contains a clone of `vue-router` but with strictly typed route names and params type-check
|
|
162
|
-
|
|
163
|
-
```vue
|
|
164
|
-
<script lang="ts">
|
|
165
|
-
// The path here is `~/generated` because I set `outDir: './generated'` in my module options
|
|
166
|
-
import { useTypedRouter } from '~/generated';
|
|
167
|
-
|
|
168
|
-
export default defineComponent({
|
|
169
|
-
setup() {
|
|
170
|
-
// Fully typed
|
|
171
|
-
const { router, routes } = useTypedRouter();
|
|
172
|
-
|
|
173
|
-
function navigate() {
|
|
174
|
-
// Autocompletes the name and infer the params
|
|
175
|
-
router.push({ name: routes.index.user, params: { user: 1 } }); // โ
valid
|
|
176
|
-
router.push({ name: routes.index.user, params: { foo: 1 } }); // โ invalid
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return { navigate };
|
|
180
|
-
},
|
|
181
67
|
});
|
|
182
|
-
</script>
|
|
183
68
|
```
|
|
184
69
|
|
|
185
|
-
# Usage with `$typedRouter` and `$routesList` injected helpers
|
|
186
|
-
|
|
187
|
-
`$typedRouter` is an injected clone of vue-router `$router`, but fully typed with all your routes.
|
|
188
|
-
It's available anywhere you have access to Nuxt context
|
|
189
|
-
|
|
190
|
-
```vue
|
|
191
|
-
<script lang="ts">
|
|
192
|
-
import { defineComponent } from 'vue';
|
|
193
|
-
|
|
194
|
-
export default defineComponent({
|
|
195
|
-
name: 'Index',
|
|
196
|
-
setup() {
|
|
197
|
-
const { $typedRouter, $routesList } = useNuxtApp();
|
|
198
|
-
|
|
199
|
-
function navigate() {
|
|
200
|
-
$typedRouter.push({ name: $routesList.activate });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
navigate,
|
|
205
|
-
};
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
</script>
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
# Usage outside Vue component
|
|
212
|
-
|
|
213
|
-
You can import the `useTypedRouter` composable from where it's generated.
|
|
214
|
-
Exemple with `pinia` store here
|
|
215
|
-
|
|
216
|
-
```ts
|
|
217
|
-
import pinia from 'pinia';
|
|
218
|
-
import { useTypedRouter } from '~/generated';
|
|
219
|
-
|
|
220
|
-
export const useFooStore = defineStore('foo', {
|
|
221
|
-
actions: {
|
|
222
|
-
bar() {
|
|
223
|
-
const { router, routes } = useTypedRouter();
|
|
224
|
-
router.push({ name: routes.index.user, params: { user: 2 } });
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
```
|
|
229
70
|
|
|
230
71
|
## Development
|
|
231
72
|
|
|
232
73
|
1. Clone this repository
|
|
233
|
-
2. Install dependencies using `
|
|
234
|
-
3. Build project for local tests `
|
|
235
|
-
4. Start dev playground `
|
|
236
|
-
5. Build project for deploy `
|
|
74
|
+
2. Install dependencies using `pnpm`
|
|
75
|
+
3. Build project for local tests `pnpm build:local`
|
|
76
|
+
4. Start dev playground `pnpm play`
|
|
77
|
+
5. Build project for deploy `pnpm prepack`
|
|
237
78
|
|
|
238
79
|
## ๐ License
|
|
239
80
|
|
package/dist/module.d.ts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
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"
|
|
6
|
-
*/
|
|
7
|
-
outDir?: string;
|
|
8
|
-
/** Name of the routesNames object (ex: "routesTree")
|
|
9
|
-
* @default "routerPagesNames"
|
|
10
|
-
* */
|
|
11
|
-
routesObjectName?: string;
|
|
12
4
|
/**
|
|
13
5
|
* Set to false if you don't want a plugin generated
|
|
14
|
-
* @default
|
|
6
|
+
* @default false
|
|
15
7
|
*/
|
|
16
8
|
plugin?: boolean;
|
|
17
9
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,13 +1,330 @@
|
|
|
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: string | 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 {routesNames, TypedRouteList} 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
|
+
|
|
311
|
+
/** Returns instances of $typedRouter and $routesList fully typed to use in your components or your Vuex/Pinia store
|
|
312
|
+
*
|
|
313
|
+
* @exemple
|
|
314
|
+
*
|
|
315
|
+
* \`\`\`ts
|
|
316
|
+
* const { router, routes } = useTypedRouter();
|
|
317
|
+
* \`\`\`
|
|
318
|
+
*/
|
|
319
|
+
export function useTypedRouter(): TypedRouter {
|
|
320
|
+
const router = useRouter();
|
|
321
|
+
|
|
322
|
+
return router;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
`;
|
|
326
|
+
}
|
|
327
|
+
|
|
11
328
|
const { resolveConfig, format } = prettier;
|
|
12
329
|
const defaultPrettierOptions = {
|
|
13
330
|
printWidth: 100,
|
|
@@ -36,9 +353,15 @@ async function formatOutputWithPrettier(template) {
|
|
|
36
353
|
}
|
|
37
354
|
|
|
38
355
|
dirname(fileURLToPath(import.meta.url));
|
|
39
|
-
async function
|
|
356
|
+
async function processPathAndWriteFile({
|
|
357
|
+
content,
|
|
358
|
+
fileName,
|
|
359
|
+
srcDir,
|
|
360
|
+
outDir
|
|
361
|
+
}) {
|
|
40
362
|
try {
|
|
41
|
-
const
|
|
363
|
+
const finalOutDir = outDir ?? `.nuxt/typed-router`;
|
|
364
|
+
const processedOutDir = resolve(srcDir, finalOutDir);
|
|
42
365
|
const outputFile = resolve(process.cwd(), `${processedOutDir}/${fileName}`);
|
|
43
366
|
const formatedContent = await formatOutputWithPrettier(content);
|
|
44
367
|
if (fs.existsSync(outputFile)) {
|
|
@@ -63,6 +386,100 @@ 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
|
+
let previousGeneratedRoutes = "";
|
|
407
|
+
async function saveGeneratedFiles({
|
|
408
|
+
srcDir,
|
|
409
|
+
outputData: { routesDeclTemplate, routesList, routesObjectTemplate, routesParams }
|
|
410
|
+
}) {
|
|
411
|
+
const filesMap = [
|
|
412
|
+
{
|
|
413
|
+
fileName: "__useTypedRouter.ts",
|
|
414
|
+
content: createRuntimeUseTypedRouterFile()
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
fileName: "__useTypedRoute.ts",
|
|
418
|
+
content: createUseTypedRouteFile()
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
fileName: `__routes.ts`,
|
|
422
|
+
content: createRuntimeRoutesFile({
|
|
423
|
+
routesList,
|
|
424
|
+
routesObjectTemplate,
|
|
425
|
+
routesDeclTemplate,
|
|
426
|
+
routesParams
|
|
427
|
+
})
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
fileName: `typed-router.d.ts`,
|
|
431
|
+
content: createDeclarationRoutesFile()
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
fileName: "index.ts",
|
|
435
|
+
content: createRuntimeIndexFile()
|
|
436
|
+
}
|
|
437
|
+
];
|
|
438
|
+
await Promise.all(
|
|
439
|
+
filesMap.map(({ content, fileName }) => processPathAndWriteFile({ srcDir, content, fileName }))
|
|
440
|
+
);
|
|
441
|
+
if (previousGeneratedRoutes !== routesList.join(",")) {
|
|
442
|
+
previousGeneratedRoutes = routesList.join(",");
|
|
443
|
+
console.log(logSymbols.success, `[typed-router] Routes definitions generated`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function isItemLast(array, index) {
|
|
448
|
+
return array ? index === array.length - 1 : false;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const routeParamExtractRegxp = /(:(\w+)(\?)?)+/g;
|
|
452
|
+
function extractRouteParamsFromPath(path, isIndexFileForRouting, previousParams) {
|
|
453
|
+
let params = [];
|
|
454
|
+
let matches;
|
|
455
|
+
do {
|
|
456
|
+
matches = routeParamExtractRegxp.exec(path);
|
|
457
|
+
if (matches) {
|
|
458
|
+
const [_, mtch, key, optional] = matches;
|
|
459
|
+
if (mtch) {
|
|
460
|
+
params.push({ name: key, required: !optional });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} while (matches);
|
|
464
|
+
let allMergedParams = params.map(
|
|
465
|
+
({ name, required }) => ({
|
|
466
|
+
key: name,
|
|
467
|
+
type: "string | number",
|
|
468
|
+
required
|
|
469
|
+
})
|
|
470
|
+
);
|
|
471
|
+
if (previousParams?.length) {
|
|
472
|
+
allMergedParams = previousParams.map((m) => ({ ...m, required: false })).concat(allMergedParams);
|
|
473
|
+
}
|
|
474
|
+
if (!params.length && isIndexFileForRouting) {
|
|
475
|
+
const lastItem = allMergedParams[allMergedParams.length - 1];
|
|
476
|
+
if (lastItem) {
|
|
477
|
+
lastItem.required = true;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return allMergedParams;
|
|
481
|
+
}
|
|
482
|
+
|
|
66
483
|
function extractMatchingSiblings(mainRoute, siblingRoutes) {
|
|
67
484
|
return siblingRoutes?.filter((s) => {
|
|
68
485
|
const chunkName = extractChunkMain(mainRoute.file);
|
|
@@ -92,61 +509,6 @@ function extractChunkMain(chunkName) {
|
|
|
92
509
|
return chunkArray?.join("/");
|
|
93
510
|
}
|
|
94
511
|
|
|
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
512
|
function walkThoughRoutes({
|
|
151
513
|
route,
|
|
152
514
|
level,
|
|
@@ -168,7 +530,7 @@ function walkThoughRoutes({
|
|
|
168
530
|
const nameKey = camelCase(parentPath || "index");
|
|
169
531
|
output.routesObjectTemplate += `${nameKey}:{`;
|
|
170
532
|
output.routesDeclTemplate += `"${nameKey}":{`;
|
|
171
|
-
const allRouteParams = extractRouteParamsFromPath(route.path, previousParams);
|
|
533
|
+
const allRouteParams = extractRouteParamsFromPath(route.path, false, previousParams);
|
|
172
534
|
childrenChunks?.map(
|
|
173
535
|
(routeConfig, index) => walkThoughRoutes({
|
|
174
536
|
route: routeConfig,
|
|
@@ -193,7 +555,12 @@ function walkThoughRoutes({
|
|
|
193
555
|
output.routesObjectTemplate += `'${keyName}': '${route.name}' as const,`;
|
|
194
556
|
output.routesDeclTemplate += `"${keyName}": "${route.name}"${isLast ? "" : ","}`;
|
|
195
557
|
output.routesList.push(route.name);
|
|
196
|
-
const
|
|
558
|
+
const isIndexFileForRouting = route.path === "";
|
|
559
|
+
const allRouteParams = extractRouteParamsFromPath(
|
|
560
|
+
route.path,
|
|
561
|
+
isIndexFileForRouting,
|
|
562
|
+
previousParams
|
|
563
|
+
);
|
|
197
564
|
output.routesParams.push({
|
|
198
565
|
name: route.name,
|
|
199
566
|
params: allRouteParams
|
|
@@ -201,229 +568,49 @@ function walkThoughRoutes({
|
|
|
201
568
|
}
|
|
202
569
|
}
|
|
203
570
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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 {}
|
|
571
|
+
function constructRouteMap(routesConfig) {
|
|
572
|
+
try {
|
|
573
|
+
let routesObjectTemplate = "{";
|
|
574
|
+
let routesDeclTemplate = "{";
|
|
575
|
+
let routesList = [];
|
|
576
|
+
let routesParams = [];
|
|
577
|
+
const output = { routesObjectTemplate, routesDeclTemplate, routesList, routesParams };
|
|
578
|
+
startGenerator({
|
|
579
|
+
output,
|
|
580
|
+
routesConfig
|
|
581
|
+
});
|
|
582
|
+
return output;
|
|
583
|
+
} catch (e) {
|
|
584
|
+
throw new Error("Generation failed");
|
|
286
585
|
}
|
|
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
586
|
}
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
}
|
|
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
|
-
}`;
|
|
587
|
+
function startGenerator({ output, routesConfig }) {
|
|
588
|
+
routesConfig.forEach((route, index) => {
|
|
589
|
+
const rootSiblingsRoutes = routesConfig.filter((rt) => rt.chunkName !== route.chunkName);
|
|
590
|
+
walkThoughRoutes({
|
|
591
|
+
route,
|
|
592
|
+
level: 0,
|
|
593
|
+
output,
|
|
594
|
+
siblings: rootSiblingsRoutes,
|
|
595
|
+
isLast: isItemLast(routesConfig, index)
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
output.routesObjectTemplate += "}";
|
|
599
|
+
output.routesDeclTemplate += "}";
|
|
386
600
|
}
|
|
387
601
|
|
|
388
|
-
function
|
|
602
|
+
function createTypedRouter({ srcDir, plugin, nuxt }) {
|
|
389
603
|
try {
|
|
390
604
|
extendPages(async (routes) => {
|
|
391
605
|
if (routes.length) {
|
|
392
|
-
const
|
|
606
|
+
const outputData = constructRouteMap(routes);
|
|
393
607
|
if (plugin) {
|
|
394
|
-
|
|
395
|
-
nuxt.hook("build:done", async () => {
|
|
396
|
-
const pluginFolder = `${srcDir}/plugins`;
|
|
397
|
-
await saveRouteFiles(
|
|
398
|
-
pluginFolder,
|
|
399
|
-
srcDir,
|
|
400
|
-
pluginName,
|
|
401
|
-
createRuntimePluginFile(routesDeclTemplate)
|
|
402
|
-
);
|
|
403
|
-
});
|
|
608
|
+
handlePluginFileSave({ nuxt, routesDeclTemplate: outputData.routesDeclTemplate, srcDir });
|
|
404
609
|
}
|
|
405
|
-
await
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
"__useTypedRouter.ts",
|
|
410
|
-
createRuntimeHookFile(routesDeclTemplate)
|
|
411
|
-
),
|
|
412
|
-
saveRouteFiles(
|
|
413
|
-
outDir,
|
|
414
|
-
srcDir,
|
|
415
|
-
`__routes.ts`,
|
|
416
|
-
createRuntimeRoutesFile({ routesList, routesObjectTemplate, routesObjectName })
|
|
417
|
-
),
|
|
418
|
-
saveRouteFiles(
|
|
419
|
-
outDir,
|
|
420
|
-
srcDir,
|
|
421
|
-
`typed-router.d.ts`,
|
|
422
|
-
createDeclarationRoutesFile({ routesDeclTemplate, routesList, routesParams })
|
|
423
|
-
),
|
|
424
|
-
saveRouteFiles(outDir, srcDir, "index.ts", createRuntimeIndexFile())
|
|
425
|
-
]);
|
|
426
|
-
console.log(logSymbols.success, `[typed-router] Routes definitions generated`);
|
|
610
|
+
await saveGeneratedFiles({
|
|
611
|
+
srcDir,
|
|
612
|
+
outputData
|
|
613
|
+
});
|
|
427
614
|
} else {
|
|
428
615
|
console.log(
|
|
429
616
|
logSymbols.warning,
|
|
@@ -447,13 +634,19 @@ const module = defineNuxtModule({
|
|
|
447
634
|
compatibility: { nuxt: "^3.0.0-rc.1", bridge: false }
|
|
448
635
|
},
|
|
449
636
|
defaults: {
|
|
450
|
-
|
|
451
|
-
routesObjectName: "routerPagesNames"
|
|
637
|
+
plugin: false
|
|
452
638
|
},
|
|
453
639
|
setup(moduleOptions, nuxt) {
|
|
454
640
|
const srcDir = nuxt.options.srcDir;
|
|
455
|
-
const { plugin
|
|
456
|
-
nuxt.
|
|
641
|
+
const { plugin } = moduleOptions;
|
|
642
|
+
nuxt.options.alias = {
|
|
643
|
+
...nuxt.options.alias,
|
|
644
|
+
"@typed-router": fileURLToPath(
|
|
645
|
+
new URL(`${nuxt.options.rootDir}/.nuxt/typed-router`, import.meta.url)
|
|
646
|
+
)
|
|
647
|
+
};
|
|
648
|
+
nuxt.hook("pages:extend", () => createTypedRouter({ srcDir, nuxt, plugin }));
|
|
649
|
+
createTypedRouter({ srcDir, nuxt, plugin });
|
|
457
650
|
}
|
|
458
651
|
});
|
|
459
652
|
|
package/main.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-typed-router",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
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
|
|
24
|
-
"test": "
|
|
25
|
-
"test:watch": "
|
|
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.
|
|
71
|
-
"eslint-config-prettier": "^8.
|
|
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.
|
|
76
|
+
"vitest": "^0.27.0",
|
|
75
77
|
"vue-router": "^4.1.6"
|
|
76
78
|
}
|
|
77
79
|
}
|