@virou/nuxt 0.1.1 β†’ 1.1.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 ADDED
@@ -0,0 +1,337 @@
1
+ # 🧭 Virou
2
+
3
+ <p>
4
+ <a href="https://www.npmjs.com/package/@virou/core"><img src="https://img.shields.io/npm/v/@virou/core.svg?style=flat&colorA=18181B&colorB=39737d" alt="Version"></a>
5
+ <a href="https://www.npmjs.com/package/@virou/core"><img src="https://img.shields.io/npm/dm/@virou/core.svg?style=flat&colorA=18181B&colorB=39737d" alt="Downloads"></a>
6
+ <a href="https://bundlephobia.com/package/@virou/core"><img src="https://img.shields.io/bundlephobia/min/@virou/core?style=flat&colorA=18181B&colorB=39737d" alt="Bundle Size"><a/>
7
+ <a href="https://github.com/tankosinn/virou/tree/main/LICENSE"><img src="https://img.shields.io/github/license/tankosinn/virou.svg?style=flat&colorA=18181B&colorB=39737d" alt="License"></a>
8
+ </p>
9
+
10
+ Virou is a high-performance, lightweight virtual router for Vue with dynamic routing capabilities.
11
+
12
+ > Perfect for modals, wizards, embeddable widgets, or any scenario requiring routing without altering the browser's URL or history.
13
+
14
+ ## ✨ Features
15
+
16
+ - πŸͺ„ **Dynamic Virtual Routing**: Navigate without altering the browser's URL or history
17
+ - πŸ‚ **Multiple Router Instances**: Manage independent routing contexts within the same app
18
+ - πŸͺ† **Nested Routing**: Seamlessly handle complex, nested routes
19
+ - 🦾 **Type-Safe**: Written in [TypeScript](https://www.typescriptlang.org/)
20
+ - ⚑ **SSR-Friendly**: Compatible with server-side rendering
21
+
22
+ ## 🐣 Usage
23
+ ```vue
24
+ <script setup lang="ts">
25
+ import { useVRouter } from '@virou/core'
26
+
27
+ // Define your routes
28
+ const routes = [
29
+ {
30
+ path: '/',
31
+ component: () => import('./views/Home.vue'),
32
+ },
33
+ {
34
+ path: '/about',
35
+ component: () => import('./views/About.vue'),
36
+ },
37
+ ]
38
+
39
+ // Create a virtual router instance
40
+ const { router, route } = useVRouter(routes)
41
+ </script>
42
+
43
+ <template>
44
+ <!-- Renders the current route's component -->
45
+ <VRouterView />
46
+ </template>
47
+ ```
48
+
49
+ ## πŸ“¦ Installation
50
+
51
+ Install Virou with your package manager:
52
+
53
+ ```bash
54
+ pnpm add @virou/core
55
+ ```
56
+
57
+ Register the `virou` plugin in your Vue app:
58
+
59
+ ```typescript
60
+ import { virou } from '@virou/core'
61
+ import { createApp } from 'vue'
62
+ import App from './App.vue'
63
+
64
+ createApp(App)
65
+ .use(virou)
66
+ .mount('#app')
67
+ ```
68
+
69
+ > ⚠️ Virou doesn’t globally register any components (including `VRouterView`); it only adds a `$virou` global property to store router instances in a `Map<string, VRouterData>`, which are automatically removed when no longer in use.
70
+
71
+ ### 🧩 Using with Nuxt
72
+
73
+ Install Virou Nuxt module with your package manager:
74
+
75
+ ```bash
76
+ pnpm add @virou/nuxt
77
+ ```
78
+
79
+ Add the module to your Nuxt configuration:
80
+
81
+ ```typescript
82
+ // nuxt.config.ts
83
+ export default defineNuxtConfig({
84
+ modules: [
85
+ '@virou/nuxt',
86
+ ],
87
+ })
88
+ ```
89
+
90
+ ## 🧱 Essentials
91
+
92
+ ### 🌿 Routes
93
+
94
+ Declare your routes as an array of objects with required `path` and `component`, and optional `meta` and `children` properties.
95
+
96
+ ```typescript
97
+ const routes: VRouterRaw[] = [
98
+ {
99
+ path: '/', // static path
100
+ component: Home,
101
+ },
102
+ {
103
+ path: '/user/:id', // dynamic path with parameter
104
+ component: () => import('./views/User.vue'),
105
+ meta: {
106
+ foo: 'bar',
107
+ }
108
+ },
109
+ {
110
+ path: '/**:notFound', // Named wildcard path
111
+ component: defineAsyncComponent(() => import('./views/NotFound.vue')),
112
+ }
113
+ ]
114
+ ```
115
+
116
+ **Props**:
117
+ - `path`: the URL pattern to match. Supports:
118
+ - Static ("/about") for exact matches
119
+ - Dynamic ("/user/:id") for named parameters
120
+ - Wildcard ("/**") for catch-all segments
121
+ - Named wildcard ("/**:notFound") for catch-all with a name
122
+ - `component`: the Vue component to render when this route matches. Can be synchronous or an async loader.
123
+ - `meta`: metadata for the route.
124
+ - `children`: an array of child routes.
125
+
126
+ > Virou uses [rou3](https://github.com/h3js/rou3) under the hood for create router context and route matching.
127
+
128
+ ### πŸͺ† Nested Routes
129
+
130
+ To define nested routes, add a children array to a route record. Child path values are relative to their parent (leading `/` is ignored).
131
+
132
+ ```typescript
133
+ const routes: VRouterRaw[] = [
134
+ // ...
135
+ {
136
+ path: '/user/:id',
137
+ component: User,
138
+ children: [
139
+ {
140
+ path: '', // /user/:id -> default child route
141
+ component: UserProfile,
142
+ },
143
+ {
144
+ path: '/settings', // /user/:id/settings
145
+ component: UserSettings,
146
+ children: [
147
+ {
148
+ path: '/', // /user/:id/settings -> deep default child route
149
+ component: UserSettingsGeneral,
150
+ },
151
+ {
152
+ path: '/notifications', // /user/:id/settings/notifications
153
+ component: UserSettingsNotifications,
154
+ },
155
+ ],
156
+ },
157
+ ]
158
+ },
159
+ // ...
160
+ ]
161
+ ```
162
+
163
+ ### 🌳 Router
164
+
165
+ Create (or access) a virtual router instance with `useVRouter` composable.
166
+
167
+ ```typescript
168
+ const { router, route } = useVRouter('my-wizard', routes)
169
+ ```
170
+
171
+ > `useVRouter` must be called inside `setup()`.
172
+
173
+ **Params:**
174
+ - `key`: a unique key for the router instance. If you do not provide a key, Virou will generate one via `useId()`.
175
+ - `routes`: an array of route objects.
176
+ - `options`:
177
+ - `initialPath`: the path to render on initialization (defaults to `/`).
178
+
179
+ **Returns:**
180
+ - `route`: a Vue shallow ref that contains the current route object.
181
+ ```typescript
182
+ export interface VRoute {
183
+ fullPath: string
184
+ path: string
185
+ search: string
186
+ hash: string
187
+ meta?: Record<PropertyKey, unknown>
188
+ params?: Record<string, string>
189
+ _renderList: Component[] | null
190
+ }
191
+ ```
192
+ - `router`:
193
+ - `replace(path: string): void`: navigate to a new path.
194
+ - `addRoute(route: VRouteRaw): void`: add a route at runtime.
195
+
196
+ #### πŸƒ Multiple Router Instances
197
+
198
+ Virou allows you to create multiple independent router instances within the same app.
199
+
200
+ ```vue
201
+ <script setup lang="ts">
202
+ // Settings modal router
203
+ useVRouter('settings-modal', [
204
+ {
205
+ path: '/profile',
206
+ component: UserProfile,
207
+ },
208
+ {
209
+ path: '/preferences',
210
+ component: UserPreferences,
211
+ },
212
+ // ...
213
+ ], { initialPath: '/profile' })
214
+
215
+ // Onboarding wizard router
216
+ useVRouter('onboarding-wizard', [
217
+ {
218
+ path: '/profile',
219
+ component: OnboardingProfile,
220
+ },
221
+ {
222
+ path: '/teamspace',
223
+ component: OnboardingTeamspace,
224
+ },
225
+ // ...
226
+ ], { initialPath: '/profile' })
227
+ </script>
228
+
229
+ <template>
230
+ <SettingsModal>
231
+ <VRouterView router-key="settings-modal" />
232
+ </SettingsModal>
233
+
234
+ <Wizard>
235
+ <VRouterView router-key="onboarding-wizard" />
236
+ </Wizard>
237
+ </template>
238
+ ```
239
+
240
+ ## πŸ“Ί Rendering
241
+
242
+ `<VRouterView>` mounts the matched component at its current nesting depth.
243
+
244
+ ```vue
245
+ <template>
246
+ <VRouterView router-key="my-router">
247
+ :keep-alive="true" <!-- preserve component state -->
248
+ :view-key="(route, key) => `${key}|${route.fullPath}`" <!-- custom vnode key -->
249
+ >
250
+ <!-- default slot receives { Component, route } -->
251
+ <template #default="{ Component, route }">
252
+ <Transition name="fade" mode="out-in">
253
+ <component :is="Component" v-bind="route.params" />
254
+ </Transition>
255
+ </template>
256
+
257
+ <!-- fallback slot shown while async loader resolves -->
258
+ <template #fallback>
259
+ <div class="spinner">
260
+ Loading...
261
+ </div>
262
+ </template>
263
+ </VRouterView>
264
+ </template>
265
+ ```
266
+
267
+ **Props:**
268
+ - `routerKey`: key of the router instance to render. If not provided, uses the nearest router instance.
269
+ - `keepAlive`: wraps the rendered component in `<KeepAlive>` when set to true, preserving its state across navigations.
270
+ - `viewKey`: accepts either a string or a function `(route, key) => string` to compute the vnode key for the rendered component.
271
+
272
+ **Slots:**
273
+ - `default`: slot receives `{ Component, route }` so you can wrap or decorate the active component.
274
+ - `fallback`: receives `{ route }` and is displayed inside `<Suspense>` while an async component is resolving.
275
+
276
+ > Virou wraps components in `<Suspense>` by default. To combine `<Suspense>` with other components, see the [Vue docs](https://vuejs.org/guide/built-ins/suspense#combining-with-other-components).
277
+
278
+ ## πŸ› οΈ Advanced
279
+
280
+ ### 🌐 Global Routers
281
+
282
+ By default, routers created with `useVRouter(key, routes)` are disposableβ€”they unregister themselves automatically once no components reference them.
283
+
284
+ To keep a router alive for your app’s entire lifecycle, register it as a global router.
285
+
286
+ #### Plugin-Registered Globals
287
+
288
+ Defined routers in the `virou` plugin options are registered as global routers:
289
+
290
+ ```ts
291
+ createApp(App)
292
+ .use(virou, {
293
+ routers: {
294
+ 'embedded-widget-app': {
295
+ routes: [
296
+ { path: '/chat', component: () => import('./views/Chat.vue') },
297
+ { path: '/settings', component: () => import('./views/Settings.vue') },
298
+ ],
299
+ options: { initialPath: '/chat' }
300
+ },
301
+ // add more global routers here...
302
+ }
303
+ })
304
+ .mount('#app')
305
+ ```
306
+
307
+ Later:
308
+
309
+ ```ts
310
+ const { router, route } = useVRouter('embedded-widget-app')
311
+ ```
312
+
313
+ #### Runtime-Registered Globals
314
+
315
+ You may also mark a router as global at runtime by passing the `_isGlobal` option:
316
+
317
+ ```ts
318
+ useVRouter(routes, { _isGlobal: true })
319
+ ```
320
+
321
+ That router will stay registered even after components that use it unmount.
322
+
323
+ ### πŸ§ͺ Create Standalone Virtual Router
324
+
325
+ You can create a standalone virtual router with `createVRouter`:
326
+
327
+ ```ts
328
+ export function useCustomRouter() {
329
+ const { router, route } = createVRouter(routes)
330
+
331
+ // Custom logic here...
332
+ }
333
+ ```
334
+
335
+ ## πŸ“ License
336
+
337
+ [MIT License](https://github.com/tankosinn/virou/blob/main/LICENSE)
package/dist/module.d.mts CHANGED
@@ -1,5 +1,14 @@
1
- import * as nuxt_schema from 'nuxt/schema';
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { VirouPluginOptions } from '@virou/core';
2
3
 
3
- declare const _default: nuxt_schema.NuxtModule<nuxt_schema.ModuleOptions, nuxt_schema.ModuleOptions, false>;
4
+ interface ModuleOptions extends VirouPluginOptions {
5
+ }
6
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
4
7
 
5
- export { _default as default };
8
+ declare module '@nuxt/schema' {
9
+ interface PublicRuntimeConfig {
10
+ virou?: ModuleOptions;
11
+ }
12
+ }
13
+
14
+ export { type ModuleOptions, _default as default };
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "name": "virou",
2
+ "name": "@virou/nuxt",
3
3
  "configKey": "virou",
4
- "version": "0.1.1",
4
+ "version": "1.1.0",
5
5
  "builder": {
6
- "@nuxt/module-builder": "0.8.4",
7
- "unbuild": "3.3.1"
6
+ "@nuxt/module-builder": "1.0.1",
7
+ "unbuild": "3.5.0"
8
8
  }
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { defineNuxtModule, addImports, addComponent } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addPlugin, addImports, addComponent } from '@nuxt/kit';
2
+ import { defu } from 'defu';
2
3
 
3
4
  const functions = [
4
5
  "useVRouter"
@@ -8,14 +9,18 @@ const components = [
8
9
  ];
9
10
  const module = defineNuxtModule({
10
11
  meta: {
11
- name: "virou"
12
+ name: "@virou/nuxt",
13
+ configKey: "virou"
12
14
  },
13
- setup(_options, _nuxt) {
15
+ setup(options, nuxt) {
16
+ const resolver = createResolver(import.meta.url);
17
+ nuxt.options.runtimeConfig.public.virou = defu(nuxt.options.runtimeConfig.public.virou, options);
18
+ addPlugin(resolver.resolve("./runtime/plugin"));
14
19
  functions.forEach((name) => {
15
20
  addImports({ name, as: name, from: "@virou/core", priority: -1 });
16
21
  });
17
22
  components.forEach((name) => {
18
- void addComponent({ name, export: name, filePath: "@virou/core", priority: -1 });
23
+ addComponent({ name, export: name, filePath: "@virou/core", priority: -1 });
19
24
  });
20
25
  }
21
26
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,6 @@
1
+ import { virou } from "@virou/core";
2
+ import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
3
+ export default defineNuxtPlugin((nuxtApp) => {
4
+ const options = useRuntimeConfig().public.virou;
5
+ nuxtApp.vueApp.use(virou, options);
6
+ });
package/dist/types.d.mts CHANGED
@@ -1,7 +1 @@
1
- import type { NuxtModule } from '@nuxt/schema'
2
-
3
- import type { default as Module } from './module.js'
4
-
5
- export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
-
7
- export { default } from './module.js'
1
+ export { type ModuleOptions, default } from './module.mjs'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@virou/nuxt",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "1.1.0",
5
5
  "description": "Virou Nuxt Module",
6
6
  "author": "Tankosin<https://github.com/tankosinn>",
7
7
  "license": "MIT",
@@ -25,28 +25,25 @@
25
25
  ],
26
26
  "sideEffects": false,
27
27
  "exports": {
28
- ".": {
29
- "types": "./dist/types.d.ts",
30
- "import": "./dist/module.mjs",
31
- "require": "./dist/module.cjs"
32
- }
28
+ "types": "./dist/types.d.mts",
29
+ "default": "./dist/module.mjs"
33
30
  },
34
- "main": "./dist/module.cjs",
35
- "types": "./dist/types.d.ts",
31
+ "main": "./dist/module.mjs",
36
32
  "files": [
37
33
  "dist"
38
34
  ],
39
35
  "engines": {
40
- "node": ">=18.0.0"
36
+ "node": ">=18.12.0"
41
37
  },
42
38
  "dependencies": {
43
- "@nuxt/kit": "^3.15.2",
44
- "@virou/core": "0.1.1"
39
+ "@nuxt/kit": "^3.17.3",
40
+ "defu": "^6.1.4",
41
+ "@virou/core": "1.1.0"
45
42
  },
46
43
  "devDependencies": {
47
- "@nuxt/module-builder": "0.8.4",
48
- "@nuxt/schema": "3.15.2",
49
- "nuxt": "3.15.2"
44
+ "@nuxt/module-builder": "1.0.1",
45
+ "@nuxt/schema": "3.17.3",
46
+ "nuxt": "3.17.3"
50
47
  },
51
48
  "scripts": {
52
49
  "build": "nuxt-module-build build"
package/dist/module.cjs DELETED
@@ -1,5 +0,0 @@
1
- module.exports = function(...args) {
2
- return import('./module.mjs').then(m => m.default.call(this, ...args))
3
- }
4
- const _meta = module.exports.meta = require('./module.json')
5
- module.exports.getMeta = () => Promise.resolve(_meta)
package/dist/module.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import * as nuxt_schema from 'nuxt/schema';
2
-
3
- declare const _default: nuxt_schema.NuxtModule<nuxt_schema.ModuleOptions, nuxt_schema.ModuleOptions, false>;
4
-
5
- export { _default as default };
package/dist/types.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import type { NuxtModule } from '@nuxt/schema'
2
-
3
- import type { default as Module } from './module'
4
-
5
- export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
-
7
- export { default } from './module'