Middleware plugin development
Basic Concepts of Middleware Plugin Development
Vite.js middleware plugins are essentially functions that intercept and transform requests/responses. They operate between the development server and the browser, capable of modifying requests, responses, or executing custom logic. The core of a middleware plugin is a factory function that takes a server
object and returns a middleware function.
interface VitePluginMiddleware {
(server: ViteDevServer): Connect.NextHandleFunction;
}
The typical middleware function signature follows the Connect/Express style, including req
, res
, and next
parameters. This design allows developers from the Node.js ecosystem to get started quickly.
Creating a Basic Middleware Plugin
Develop a simple logging middleware plugin:
// vite-plugin-logger.ts
import type { Plugin, ViteDevServer } from 'vite';
export default function loggerPlugin(): Plugin {
return {
name: 'vite-plugin-logger',
configureServer(server) {
server.middlewares.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
}
};
}
Using this plugin in vite.config.ts:
import { defineConfig } from 'vite';
import loggerPlugin from './vite-plugin-logger';
export default defineConfig({
plugins: [loggerPlugin()]
});
This basic example outputs each request's method and URL to the console, demonstrating the most fundamental interception capability of middleware plugins.
Request/Response Handling Techniques
Modifying Request Headers
Middleware can modify requests before they are sent to the Vite server:
server.middlewares.use((req, res, next) => {
req.headers['x-custom-header'] = 'vite-middleware';
next();
});
Intercepting Specific Requests
A common pattern for implementing an API request proxy:
server.middlewares.use('/api', (req, res, next) => {
if (req.url?.startsWith('/api/users')) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify([{ id: 1, name: 'Test User' }]));
return;
}
next();
});
Response Transformation
Modifying the response content returned by the Vite server:
const transformMiddleware = (req, res, next) => {
const originalWrite = res.write;
res.write = function (chunk) {
if (typeof chunk === 'string' && res.getHeader('content-type')?.includes('text/html')) {
chunk = chunk.replace('</head>', '<script>console.log("Injected!")</script></head>');
}
return originalWrite.call(this, chunk);
};
next();
};
Advanced Middleware Patterns
Middleware Composition
Combining multiple middlewares into a processing chain:
function composeMiddlewares(middlewares: Connect.NextHandleFunction[]) {
return (req, res, next) => {
let index = -1;
function dispatch(i: number) {
if (i <= index) return next(new Error('next() called multiple times'));
index = i;
const middleware = middlewares[i] || next;
middleware(req, res, (err?: any) => {
if (err) return next(err);
dispatch(i + 1);
});
}
dispatch(0);
};
}
// Usage example
server.middlewares.use(composeMiddlewares([
authMiddleware,
loggingMiddleware,
transformMiddleware
]));
Hot Module Replacement (HMR) Integration
Middleware can integrate with Vite's HMR mechanism:
server.middlewares.use((req, res, next) => {
if (req.url === '/__custom_hmr') {
server.ws.send({ type: 'custom-update', data: Date.now() });
res.end('HMR triggered');
return;
}
next();
});
Practical Application Examples
Mock API Server
A complete example of a mock API server for development environments:
// vite-plugin-mock-api.ts
import type { Plugin, ViteDevServer } from 'vite';
import { createMockServer } from 'vite-plugin-mock';
interface MockApiOptions {
mockDir?: string;
enable?: boolean;
}
export default function mockApiPlugin(options: MockApiOptions = {}): Plugin {
return {
name: 'vite-plugin-mock-api',
configureServer(server) {
if (options.enable === false) return;
const mockServer = createMockServer({
mockPath: options.mockDir || 'mock',
watchFiles: true
});
server.middlewares.use('/api', (req, res, next) => {
if (mockServer.has(req.url!)) {
mockServer.handle(req, res);
} else {
next();
}
});
}
};
}
Role-Based Access Control Middleware
Implementing role-based access control:
// vite-plugin-auth.ts
import type { Plugin, ViteDevServer } from 'vite';
interface AuthOptions {
roles: string[];
}
export default function authPlugin(options: AuthOptions): Plugin {
return {
name: 'vite-plugin-auth',
configureServer(server) {
server.middlewares.use((req, res, next) => {
const token = req.headers['authorization'];
const userRole = getUserRoleFromToken(token); // Hypothetical implementation
if (!options.roles.includes(userRole)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
next();
});
}
};
}
function getUserRoleFromToken(token?: string | string[]): string {
// In a real project, implement JWT parsing logic here
return 'admin';
}
Performance Optimization Techniques
Caching Strategy
Implementing static resource caching:
server.middlewares.use('/assets', (req, res, next) => {
if (req.method === 'GET') {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
next();
});
Lazy-Loading Middleware
Loading resource-intensive middleware on demand:
function lazyMiddleware(factory: () => Connect.NextHandleFunction) {
let middleware: Connect.NextHandleFunction | null = null;
return (req, res, next) => {
if (!middleware) {
middleware = factory();
}
middleware(req, res, next);
};
}
// Usage example
server.middlewares.use(lazyMiddleware(() => {
const heavyMiddleware = require('heavy-middleware');
return heavyMiddleware({ /* configuration */ });
}));
Debugging and Error Handling
Error-Catching Middleware
Uniformly handling errors in the middleware chain:
server.middlewares.use((err: Error, req, res, next) => {
console.error('Middleware error:', err);
res.writeHead(500);
res.end('Internal Server Error');
});
// Example middleware that triggers an error
server.middlewares.use('/error', (req, res, next) => {
next(new Error('Test error'));
});
Development Debugging Tools
Integrating debug logs:
function createDebugMiddleware(namespace: string) {
return (req, res, next) => {
const start = Date.now();
const originalEnd = res.end;
res.end = function (...args: any[]) {
const duration = Date.now() - start;
console.log(`[${namespace}] ${req.method} ${req.url} - ${duration}ms`);
originalEnd.apply(res, args);
};
next();
};
}
Integration with the Vite Ecosystem
Collaborating with Existing Plugins
Handling middleware added by other plugins:
server.middlewares.stack.unshift({
route: '',
handle: yourMiddleware,
id: 'your-plugin-id'
});
Accessing Vite Internal Modules
Leveraging Vite's internal APIs for advanced functionality:
import { transformWithEsbuild } from 'vite';
server.middlewares.use('/transform', async (req, res) => {
const code = await getRequestBody(req);
const result = await transformWithEsbuild(code, 'file.js', {
loader: 'jsx',
minify: true
});
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(result.code);
});
Production Environment Considerations
Conditional Middleware Registration
Enabling certain middleware only in development:
export default function devOnlyPlugin(): Plugin {
return {
name: 'vite-plugin-dev-only',
apply: 'serve', // Key configuration
configureServer(server) {
server.middlewares.use(devMiddleware);
}
};
}
Security Middleware
Adding basic security protections:
import helmet from 'helmet';
server.middlewares.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", 'https:'],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}
}));
Testing Middleware Plugins
Unit Test Example
Using supertest to test middleware:
import request from 'supertest';
import { createServer } from 'vite';
describe('Middleware Plugin', () => {
let server: ViteDevServer;
beforeAll(async () => {
server = await createServer({
configFile: false,
plugins: [yourMiddlewarePlugin()]
});
await server.listen(3000);
});
afterAll(async () => {
await server.close();
});
it('should handle /api route', async () => {
const response = await request(server.httpServer)
.get('/api/test')
.expect(200);
expect(response.body).toEqual({ success: true });
});
});
Integration Testing Strategy
Simulating the complete request lifecycle:
function testMiddleware(middleware: Connect.NextHandleFunction) {
return (req: any = {}, res: any = {}) => {
req.url = req.url || '/';
req.method = req.method || 'GET';
req.headers = req.headers || {};
return new Promise((resolve, reject) => {
res.end = (data: any) => {
res.body = data;
resolve(res);
};
res.writeHead = (status: number, headers: any) => {
res.statusCode = status;
res.headers = { ...res.headers, ...headers };
};
middleware(req, res, (err?: any) => {
if (err) return reject(err);
resolve(res);
});
});
};
}
Advanced Topics: Middleware Metaprogramming
Dynamic Middleware Registration
Registering middleware conditionally at runtime:
function dynamicMiddlewareRouter(routes: Record<string, Connect.NextHandleFunction>) {
return (req, res, next) => {
const match = Object.entries(routes).find(([path]) =>
req.url?.startsWith(path)
);
if (match) {
return match[1](req, res, next);
}
next();
};
}
// Usage example
server.middlewares.use(dynamicMiddlewareRouter({
'/api': apiMiddleware,
'/graphql': graphqlMiddleware,
'/ws': websocketMiddleware
}));
Hot Middleware Replacement
Updating middleware logic during development without restarting the server:
function hotMiddlewareWrapper(factory: () => Connect.NextHandleFunction) {
let currentMiddleware = factory();
if (import.meta.hot) {
import.meta.hot.accept('./middleware-implementation', () => {
currentMiddleware = factory();
});
}
return (req, res, next) => currentMiddleware(req, res, next);
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:Rollup插件兼容性处理