vite-ssr-middleware
Version:
"Middleware support for plugin vite-ssr"
268 lines (210 loc) • 8.83 kB
Markdown
This plugin adds [Nuxt](https://nuxtjs.org/) like middleware feature to [Vite-SSR](https://github.com/frandiox/vite-ssr) only for Vue.
This package requires `vite-ssr` and `vue-router@next` as peer dependency.<br>
Download it from npm `(p)npm install vite-ssr-middleware` or `yarn add vite-ssr-middleware`.<br>
Usage is simple, there is no need to register this package like `vueApp.use()`.
A middleware can have 3 arguments, 2 of them are necessary and 1 is optional.<br>
First parameter is unique name to middleware `string`<br>
Second parameter is handler `(context, properties) => boolean | Promise<boolean>`<br>
Third parameter is optional object, which can be defined in root handler `object`
Every middleware has to return `boolean`. If middleware returns `true` chaining will continue.<br>
If middleware returns `false` chaining will stop.<br>
Return `false` to use `next()` and `redirect()` inside middlewares. If returned value is `true` and `next()` or `redirect()` is used `vue-router` will throw error.
```ts
import { defineMiddleware } from 'vite-ssr-middleware';
import { useStore } from '../stores/mainStore';
// context is Vite-SSR context. Simply return type of useContext() from vite-ssr.
// Properties are empty object by default and can be customized in root handler.
export const authMiddleware = defineMiddleware('auth', (context, properties) => {
// Vite-SSR 0.12.0 available context values
const {
next, // from vue-router
to, // from vue-router
from, // from vue-router
isClient,
request,
redirect,
initialState,
url,
writeResponse,
response
} = context;
// Check if middleware runs on client side.
// import.meta.env.SSR can be used here.
if (isClient) {
// we are going to talk about this later.
const store = useStore(properties.pinia);
if (store.auth) return true; // continue chaining
// if user not signed in
next('/login');
return false; // Stop chaining because NEXT is used.
}
// if not client, it is server
const authorization = request.headers.authorization;
const isOK = CheckUserTokenAndReturnFalseOrTrue(authorization);
if (isOk) return true; // token is correct, return true to continue chaining
// In server side we have to use redirect() function and next() together.
// on server side redirect() handles navigation but if next() is not called after,
// it does not work and stuck on same page.
redirect('/login');
next();
return false; // return false and stop chaining because next is used.
});
```
There are 2 middleware handlers. One is simple and the other one is needed to be handled.<br>
Handlers need `vite-ssr` context, `vue-router` guard arguments and all middlewares as array.<br>
And can take `properties` as argument. `properties` can be accessible from any middleware.
```ts
import { viteSSR } from 'vite-ssr';
import { middlewareHandler } from 'vite-ssr-middleware';
import App from './App.vue';
import { routes } from './routes';
// Store example with pinia
import { createPinia } from 'pinia';
// Import middlewares
import { authMiddleware } from '../middlewares/authMiddleware';
export default viteSSR(App, { routes }, (context) => {
const { app, router } = context;
const pinia = createPinia();
app.use(pinia);
// middlewareHandler must be used inside router.beforeEach
const handler = middlewareHandler(context, [authMiddleware], {
// this property is accessible in each middleware
pinia
});
router.beforeEach(handler);
//...
});
```
This handler is to create a handler function. Handler function will return `true` or `false`.<br><br>
If handler function returns `true` it means a middleware returned `false` and blocked chaining.<br>
Basically in this tutorial if `false` is returned from middleware it means `next()` or `redirect()` is called. You can use it for your own purpose<br><br>
If handler returns `false` it means none of middlewares called `next()`, so we have to call it manually.
```ts
import { viteSSR } from 'vite-ssr';
import { createMiddlewareHandler } from 'vite-ssr-middleware';
import App from './App.vue';
import { routes } from './routes';
// Store example with pinia
import { createPinia } from 'pinia';
// Import middlewares
import { authMiddleware } from '../middlewares/authMiddleware';
export default viteSSR(App, { routes }, (context) => {
const { app, router } = context;
const pinia = createPinia();
app.use(pinia);
// middlewareHandler must be used inside router.beforeEach
// I pass pinia as property here because some middlewares need it.
const handler = createMiddlewareHandler(context, [authMiddleware], {
pinia
});
router.beforeEach(async (to, from, next) => {
// if any one of middlewares returned false, isNextHandled = true
// in this tutorial, return false means NEXT is used.
// you can customize this for your own purpose.
const isNextHandled = await handler({ to, from, next });
if (isNextHandled) return;
next();
});
//...
});
```
To define middleware for routes we have to use `middlewares` array inside of `route.meta`.<br>
Middlewares array can store the `value` of middleware and `name` of it as string.<br>
This package does not remove middleware duplications!!!!
```ts
import { RouteRecordRaw } from 'vue-router';
import { authMiddleware } from '../middlewares/authMiddleware';
const routes = [
{
path: '/settings',
component: () => import('./Settings.vue'),
meta: {
middlewares: [authMiddleware]
}
},
{
path: '/topsecret',
component: () => import('./SecretPage.vue'),
meta: {
middlewares: ['auth'] // name of the middleware (first parameter)
}
}
];
```
You can create your own types for properties inside middlewares.
This file should `ALWAYS!` import something or export something. If no import or export provided this will break all types.
```ts
//or import something
import { App } from 'vue';
declare module 'vite-ssr-middleware' {
interface CustomProperties {
propertyForMiddleware: string;
}
}
// a garbage type just to make this file as a module.
export type Integer = number;
```
Thanks to vite's `import.meta.globEager` function we can import files inside a directory so we won't import middlewares by hand.
I am assuming that we have a directory called `middlewares` and every file inside of this folder has `default` export which is middleware.<br>
Check: [Glob Import](https://vitejs.dev/guide/features.html#glob-import)
```ts
import { viteSSR } from 'vite-ssr/vue';
import { middlewareHandler, Middleware } from 'vite-ssr-middleware';
import App from './App.vue';
import { routes } from './routes';
import type { Middleware } from 'vite-ssr-middleware';
export default viteSSR(App, { routes }, (context) => {
const { router } = context;
// glob import will return an object of imports.
// key is path and value is exported data.
/**
* Example: {
* './middlewares/authMiddleware.ts': {
* default: middlewareData,
* },
* './middlewares/loggerMiddleware.ts': {
* default: loggerMiddlewareData,
* },
* }
*/
const midwareGlob = import.meta.globEager('./middlewares/*.ts');
const middlewares: Middleware[] = Object.keys(midwareGlob).map((key) => midwareGlob[key].default);
const handler = middlewareHandler(context, middlewares);
router.beforeEach(handler);
});
```
```ts
export default defineMiddleware('auth', () => true);
```
```ts
export default defineMiddleware('logger', () => true);
```
If you are using [vite-plugin-pages](https://github.com/hannoeru/vite-plugin-pages) we cannot import middlewares and directly use them.<br>
But this library supports name of middlewares so we can use them.
```html
<!-- I have to use prettier-ignore thanks to prettier :) -->
<!-- prettier-ignore -->
<route lang="yaml"> <!-- OR json or json5 -->
meta:
middlewares:
- auth
- logger
</route>
```
Middlewares are called by array queue.<br>
Use handler inside of `viteSSR` function.