Search code examples
node.jsvue.jsvuejs3vitenodejs-server

Vite SSR + Node.js native HTTP server?


Is it possible to create a Vite SSR app with Node.js native HTTP server? The guide from Vite documentation is using Express. But I prefer using Node.js native HTTP server as follows:

const port = 3000
const host = 'localhost'
const requestListener = async function (req, res) {

  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom'
  })

  // Use vite's connect instance as middleware
  vite.middlewares

  // Serve index.html as follows:
  const url = req.url

  let template
  template = fs.readFileSync(
    path.resolve(__dirname, 'index.html'),
    'utf-8'
  )
  template = await vite.transformIndexHtml(url, template)

  const render = (await vite.ssrLoadModule('/src/entry-server.js')).render

  const { 
    appHtml
  } = await render(url)

  const html = template
    .replace(`{{ appHtml }}`, appHtml)

  res.setHeader("Content-Type", "text/html")
  res.writeHead(200)
  res.end(html)
}

const server = createServer(requestListener)
server.listen(port, host, () => {
    console.log(`Server is running on http://${host}:${port}`)
})

My attemp has the following error on the terminal:

$ npm run dev

> dev
> node server

Server is running on http://localhost:3000
WebSocket server error: Port is already in use

Browser errors:

Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
entry-client.js:1 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.

Any ideas?


Solution

  • If you add the Vite middleware and provide static files, it should work with Vite SSR and Node.js native HTTP server:

    const { createServer } = require('http');
    const { createViteServer } = require('vite');
    const fs = require('fs');
    const path = require('path');
    const url = require('url');
    
    const port = 3000;
    const host = 'localhost';
    
    async function createRequestListener() {
      const vite = await createViteServer({
        server: { middlewareMode: 'ssr' },
      });
    
      return async function (req, res) {
        try {
          const parsedUrl = new url.URL(req.url, `http://${host}:${port}`);
          let serveUrl = parsedUrl.pathname;
    
          if (vite.middlewares) {
            await new Promise((resolve) =>
              vite.middlewares(req, res, resolve)
            );
          }
    
          if (req.method === 'GET' && serveUrl.endsWith('.js')) {
            res.setHeader('Content-Type', 'application/javascript');
          }
    
          if (serveUrl === '/') {
            serveUrl = '/index.html';
          }
    
          if (serveUrl.endsWith('.html')) {
            const template = fs.readFileSync(
              path.resolve(__dirname, './index.html'),
              'utf-8'
            );
    
            const transformedTemplate = await vite.transformIndexHtml(
              serveUrl,
              template
            );
    
            const render = (await vite.ssrLoadModule('/src/entry-server.js')).render;
            const { appHtml } = await render(parsedUrl);
    
            const html = transformedTemplate.replace('{{ appHtml }}', appHtml);
    
            res.setHeader('Content-Type', 'text/html');
            res.writeHead(200);
            res.end(html);
          } else {
            res.end();
          }
        } catch (err) {
          console.error(err);
          res.writeHead(500);
          res.end(err.message);
        }
      };
    }
    
    createRequestListener().then((requestListener) => {
      const server = createServer(requestListener);
      server.listen(port, host, () => {
        console.log(`Server is running on http://${host}:${port}`);
      });
    });