vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
68.14k stars 6.15k forks source link

Lost connection retry hits backend integration server rather than the Vite server #2345

Closed mwojtul closed 2 years ago

mwojtul commented 3 years ago

Describe the bug

When running Vite with a traditional backend (https://vitejs.dev/guide/backend-integration.html) and the Vite server connection is lost, the traditional backend server is pinged to retry the connection rather than the Vite server.

I've tried setting the port and HMR port in the config, but looking at the code it seems config.base is used with a fallback to / for the ping location, so there's never a chance to customize it when using a separate backend server.

Reproduction

https://github.com/mwojtul/vite-backend-integration-polling

This example is contrived, but you can see it pinging the Express server rather than the Vite server. It just fails and simply reloads the page, but in our real world Django application, the Django dev server continually gets polled and has to be restarted after the connection to the Vite server is lost.

System Info

leevigraham commented 3 years ago

@mwojtul I had the same issue… my solution was to set server.hmr.host to the same host as the dev server. By default I think that would be 127.0.0.1.

The https://github.com/vitejs/vite/blob/main/packages/vite/src/client/client.ts#L18 script replaces __HMR_HOSTNAME__ with config.server.hmr.host in some part of the build process I don't fully understand.

The fact config.server.hmr.host is null means the client falls back to the location.hostname

mwojtul commented 3 years ago

@leevigraham Thanks, I've tried setting config.server.hmr.host as you suggested but when the connection is lost, it still hits port 3000 (the separate backend server) rather than port 3001 (vite dev server).

The event listener https://github.com/vitejs/vite/blob/main/packages/vite/src/client/client.ts#L176 that pings the server uses base, so the __HMR_PORT__ never makes it into the URL, unless I'm missing something.

leevigraham commented 3 years ago

@mwojtul hmmm…

here's my config and debug output if that helps:

my php server is: 127.0.0.1:8000 my vite dev server is: local.mytiki.life:3000 (custom host) my vite hmr server is: local.mytiki.life:3000 (custom host)

import * as dotenvFlow  from 'dotenv-flow'
dotenvFlow.config();

import { defineConfig } from 'vite'

export default defineConfig({
    // https://vitejs.dev/config/#root
    // Project root directory (where index.html is located).
    // Can be an absolute path, or a path relative from the location of the config file itself.
    root: './theme/',
    // https://vitejs.dev/config/#base
    // Base public path when served in development or production.
    // We're serving out of /public and building in /public/theme so we need to add /theme
    base: '/theme/',
    build: {
        // The output directory relative to project root.
        outDir: '../public/theme/',
        // Remove everything from the outputDir when building
        emptyOutDir: true,
        // the directory to nest generated assets under (relative to build.outDir)
        assetsDir: './',
        // create a manifest
        manifest: true,
        // Directly customize the underlying Rollup bundle.
        rollupOptions: {
            // Instead of checking
            input: './theme/scripts/index.js'
        }
    },
    server: {
        https: {
            key: process.env.SSL_KEY,
            cert: process.env.SSL_CERT,
        },
        host: 'local.mytiki.life',
        hmr: {
            host: 'local.mytiki.life',
        },
        open: '/theme/index.html'
    }
})
  vite:config using resolved config: {
  vite:config   root: '/Volumes/Sites/Personal/mytiki.life-twig/theme',
  vite:config   base: '/theme/',
  vite:config   build: {
  vite:config     target: [ 'es2019', 'edge16', 'firefox60', 'chrome61', 'safari11' ],
  vite:config     polyfillDynamicImport: true,
  vite:config     outDir: '../public/theme/',
  vite:config     assetsDir: './',
  vite:config     assetsInlineLimit: 4096,
  vite:config     cssCodeSplit: true,
  vite:config     sourcemap: false,
  vite:config     rollupOptions: { input: './theme/scripts/index.js' },
  vite:config     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     minify: 'terser',
  vite:config     terserOptions: {},
  vite:config     cleanCssOptions: {},
  vite:config     write: true,
  vite:config     emptyOutDir: true,
  vite:config     manifest: true,
  vite:config     lib: false,
  vite:config     ssr: false,
  vite:config     ssrManifest: false,
  vite:config     brotliSize: true,
  vite:config     chunkSizeWarningLimit: 500
  vite:config   },
  vite:config   server: {
  vite:config     https: {
  vite:config       key: '/Volumes/Sites/Personal/mytiki.life-twig/local.mytiki.life-key.pem',
  vite:config       cert: '/Volumes/Sites/Personal/mytiki.life-twig/local.mytiki.life.pem'
  vite:config     },
  vite:config     host: 'local.mytiki.life',
  vite:config     hmr: { host: 'local.mytiki.life' },
  vite:config     open: '/theme/index.html'
  vite:config   },
  vite:config   configFile: '/Volumes/Sites/Personal/mytiki.life-twig/vite.config.js',
  vite:config   inlineConfig: {
  vite:config     root: undefined,
  vite:config     base: undefined,
  vite:config     mode: undefined,
  vite:config     configFile: undefined,
  vite:config     logLevel: undefined,
  vite:config     clearScreen: undefined,
  vite:config     server: {}
  vite:config   },
  vite:config   resolve: { dedupe: undefined, alias: [ [Object] ] },
  vite:config   publicDir: '/Volumes/Sites/Personal/mytiki.life-twig/theme/public',
  vite:config   command: 'serve',
  vite:config   mode: 'development',
  vite:config   isProduction: false,
  vite:config   optimizeCacheDir: '/Volumes/Sites/Personal/mytiki.life-twig/node_modules/.vite',
  vite:config   plugins: [
  vite:config     'vite:pre-alias',
  vite:config     'alias',
  vite:config     'vite:dynamic-import-polyfill',
  vite:config     'vite:resolve',
  vite:config     'vite:html',
  vite:config     'vite:css',
  vite:config     'vite:esbuild',
  vite:config     'vite:json',
  vite:config     'vite:wasm',
  vite:config     'vite:worker',
  vite:config     'vite:asset',
  vite:config     'vite:define',
  vite:config     'vite:css-post',
  vite:config     'vite:client-inject',
  vite:config     'vite:import-analysis'
  vite:config   ],
  vite:config   env: { BASE_URL: '/theme/', MODE: 'development', DEV: true, PROD: false },
  vite:config   assetsInclude: [Function: assetsInclude],
  vite:config   logger: {
  vite:config     hasWarned: false,
  vite:config     info: [Function: info],
  vite:config     warn: [Function: warn],
  vite:config     error: [Function: error],
  vite:config     clearScreen: [Function: clearScreen]
  vite:config   },
  vite:config   createResolver: [Function: createResolver]
  vite:config } +6ms
Bohreromir commented 3 years ago

What helped me was setting server: { host: "127.0.0.1", hmr: { host: "127.0.0.1", protocol: "ws" } } If if the protocol is wss I get an error that its an insecure connection

mbacalan commented 3 years ago

I'm having this issue while using Vite with a ASP.NET Razor Pages project. I tried above suggestions to set the server.host server.hmr but they seem to be ignored?

Here is my config:

import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    // generate manifest.json in outDir
    manifest: true,
    root: './Pages/Shared/',
    rollupOptions: {
      // overwrite default .html entry
      input: './wwwroot/js/site.js',
      outDir: './wwwroot/',
    },
    server: {
      host: "127.0.0.1",
      protocol: "ws",
      port: 3000,
      watch: {
        usePolling: true
      },
      hmr: {
        host: "127.0.0.1",
        protocol: "ws",
        port: 3000
      }
    }
  }
});

But it kept sending the __vite_ping request to the .NET server. I had to edit Vite's client file in node_modules to point it to ws://localhost:3000

const socketProtocol = 'ws'
const socketHost = 'localhost:3000'
const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr')
const base = __BASE__ || '/'
RentecTravis commented 2 years ago

I was having a similar problem. Our dev servers are remote, but Vite runs locally. Vite was trying to connect to the websocket at dev.example.com:3000 instead of to localhost:3000.

Just setting server.hmr.host to localhost and stopping and rerunning Vite fixed the problem

jessarcher commented 2 years ago

I'm having the same issue and I don't believe any of the suggested fixes are for the same problem originally mentioned by @mwojtul.

In my scenario, the local web server is running on http://127.0.0.1:8000 and Vite is running on http://127.0.0.1:3000 (the default). Everything works fine when loading the page.

The problem occurs when the connection to Vite is lost (for example when restarting Vite).

The client continually pings ${base}/__vite_ping, where ${base} comes from the base config option or defaults to /. In development mode, if a full URL is provided, Vite will only use the pathname component.

This means the ping URI is always relative (e.g. /__vite_ping) and so the browser makes the request to the webserver, instead of the Vite server. This results in an endless 404 that doesn't rectify itself once Vite is running again.

I'm happy to have a go at PRing this, but I'm currently confused between server.port, server.hmr.port, and server.hmr.clientPort.

patak-dev commented 2 years ago

This should be fixed by https://github.com/vitejs/vite/pull/6819, let us know here if that PR isn't working for you or create a new issue. Thanks!

Note: #6819 will be included in vite@3.0.0-beta.0 to be released in the next weeks