阿里云主机折上折
  • 微信号
Current Site:Index > Middleware plugin development

Middleware plugin development

Author:Chuan Chen 阅读数:10824人阅读 分类: 构建工具

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.