# vite-plugin-vue-middleware [![npm version](https://img.shields.io/npm/v/vite-plugin-vue-middleware.svg)](https://www.npmjs.com/package/vite-plugin-vue-middleware) [![NPM Downloads](https://img.shields.io/npm/dm/vite-plugin-vue-middleware)](https://www.npmjs.com/package/vite-plugin-vue-middleware) [![build status](https://github.com/awdr74100/vite-plugin-vue-middleware/actions/workflows/ci.yml/badge.svg)](https://github.com/awdr74100/vite-plugin-vue-middleware/actions/workflows/ci.yml) [![license](https://img.shields.io/npm/l/vite-plugin-vue-middleware)](https://github.com/awdr74100/vite-plugin-vue-middleware/blob/main/LICENSE) File-based navigation guards for Vue Router with full type safety and automated middleware injection.

## ✨ Features - 🚀 **Zero-Config**: Automatically scans your middleware directory and generates configurations. - 🛡️ **Type-Safe**: Autogenerates `.d.ts` files to provide full IntelliSense for `vue-router`'s `RouteMeta`. - 📦 **Virtual Module**: Seamlessly integrate with your router using `virtual:vue-middleware`. - 🔄 **HMR Support**: Adding, removing, or renaming middleware files triggers hot updates and type regeneration. - 🛠️ **Flexible Order**: Supports global middleware (.global) and custom execution weight via numeric prefixes. - 🔗 **Async Context**: Automatically preserves Vue injection context (`inject()`) across `await` boundaries in async middleware via a build-time transform. ## 📦 Installation Using npm: ```bash npm install -D vite-plugin-vue-middleware ``` Using yarn: ```bash yarn add -D vite-plugin-vue-middleware ``` Using pnpm: ```bash pnpm add -D vite-plugin-vue-middleware ``` ## 🚀 Quick Start ### 1. Configure the Plugin Add the plugin to your `vite.config.ts`: ```typescript import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import vueMiddleware from 'vite-plugin-vue-middleware'; export default defineConfig({ plugins: [ vue(), vueMiddleware({ // Optional: Custom middleware directory (default: 'src/middleware') middlewareDir: 'src/middleware', // Optional: Custom d.ts generation path (default: 'middleware.d.ts') dts: 'src/types/middleware.d.ts', }), ], }); ``` ### 2. TypeScript Setup (Required) To ensure TypeScript recognizes the virtual module and the generated types, follow these steps: #### **A. Add to `env.d.ts`** Reference the plugin's client types in your global declaration file: ```typescript /// ``` _Alternatively, add it to `compilerOptions.types` in your `tsconfig.json`:_ ```json { "compilerOptions": { "types": ["vite-plugin-vue-middleware/client"] } } ``` #### **B. Include the generated `.d.ts`** Ensure your `tsconfig.json` includes the generated type file. If you use the **default path** (project root), you **must** add it to the `include` array: ```json { "include": [ "src/**/*", "src/**/*.vue", "./middleware.d.ts" // Required if generated at project root (default) ] } ``` _Note: if you generate the file inside `src/` (e.g., `src/types/middleware.d.ts`), it will likely be covered by your existing `"src/**/*"` rule._ ### 3. Create Middleware Create middleware files in your `src/middleware` directory: ```typescript // src/middleware/01.auth.global.ts import { defineMiddleware } from 'virtual:vue-middleware'; export default defineMiddleware((to, from) => { const isLogged = false; // simulate auth state if (!isLogged && to.path !== '/login') { return '/login'; } }); ``` ### 4. Inject into Router Import and use `setupMiddleware` during your router initialization: ```typescript // src/router/index.ts import { createRouter, createWebHistory } from 'vue-router'; import { setupMiddleware } from 'virtual:vue-middleware'; const router = createRouter({ history: createWebHistory(), routes: [ /* your routes */ ], }); // Automatically bind middleware logic to router guards setupMiddleware(router); export default router; ``` --- ## 🛠 How it Works The plugin provides a wrapper around `router.beforeEach` to handle the middleware lifecycle: 1. **Global Middlewares**: Executed first, followed by the order of their numeric prefixes (e.g., `01.log.ts` before `02.auth.ts`). 2. **Named Middlewares**: Executed next, based on the order defined in the route's `meta.middleware` array. ### Return Values Handlers support `async/await` and follow the same logic as `vue-router` guards: - **`return`**: Continue to the next middleware or navigation. - **`return false`**: Abort the navigation. - **`return '/path'`**: Redirect to a specific path. - **`return { name: 'login' }`**: Redirect to a named route. --- ## 📏 Naming Conventions The plugin uses file naming to determine middleware properties: | Rule | Example | Description | | :------------------- | :--------------- | :---------------------------------------------------------- | | **Global Execution** | `auth.global.ts` | Applied to all route navigations automatically. | | **Execution Order** | `01.log.ts` | Numeric prefix determines weight (lower numbers run first). | | **Named Middleware** | `guest.ts` | Manually referenced in route `meta` or SFC `definePage`. | ### Using Named Middleware in Pages ```typescript const routes = [ { path: '/dashboard', component: () => import('./Dashboard.vue'), meta: { // Benefit from generated .d.ts with full IntelliSense middleware: ['auth', '02-analytics'], }, }, ]; ``` ### 🌟 Integration with Vue Router v5 (File-based Routing) This plugin is fully compatible with the modern **Vue Router v5** ecosystem and [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) (which provides the official File-based Routing logic for Vue). You can define middleware directly in your `.vue` files using the `definePage` macro: ```vue ``` ## ⚙️ Configuration | Option | Type | Default | Description | | :-------------- | :------------------ | :----------------- | :------------------------------------------------------------------------------------------------ | | `middlewareDir` | `string` | `'src/middleware'` | Root directory to scan for middleware. | | `exclude` | `string[]` | `[]` | Glob patterns to ignore files. | | `dts` | `boolean \| string` | `true` | Enable/Disable .d.ts generation or specify path. | | `asyncContext` | `boolean` | `true` | Preserve Vue injection context (`inject()`) across `await` boundaries via a build-time transform. | --- ## 🔗 Async Context By default, `asyncContext` is enabled. It applies a **build-time AST transform** to every async middleware function that contains `await`, allowing `inject()` and composables that rely on it (e.g. `useQueryClient()`, `useStore()`) to work correctly even **after** `await` statements. ### The Problem Vue's `inject()` API only works inside an active component or application context. In standard async middleware, calling `inject()` after an `await` boundary fails because the execution context is lost: ```typescript // ❌ inject() called after await — throws outside of Vue context export default defineMiddleware(async (to, from) => { await someAsyncCheck(); const store = useStore(); // Runtime error: inject() must be called inside setup() }); ``` ### How It Works When `asyncContext: true` (default), the plugin transforms async middleware into a **generator-based executor** at build time. Each segment between `yield` points runs inside [`app.runWithContext()`](https://vuejs.org/api/application.html#app-runwithcontext), restoring the Vue injection context on every resume: ```typescript // ✅ Works — inject() is available on every segment after await export default defineMiddleware(async (to, from) => { await someAsyncCheck(); const store = useStore(); // Works correctly }); ``` The transform is transparent — you write standard `async/await` and the plugin handles the rest. No manual changes are required. ### Limitations - **`for await...of`**: Not supported with the async-context transform. A warning is logged and the affected middleware runs without context preservation across `await` boundaries. - **Nested async functions**: Only top-level `await` expressions in the `defineMiddleware` callback are transformed. Nested async functions are left untouched. To opt out of the transform entirely: ```typescript vueMiddleware({ asyncContext: false }); ``` ## 📄 License [MIT](./LICENSE) License © 2026 [Roya](https://github.com/awdr74100)