Bỏ qua

Hot Module Reload - Vite

Changes in code are not updated on the client, requiring manual full page reload.

Steps to Reproduce

  1. Add HMR support to {zsh} vite.config.js

    export default defineConfig(({ command, mode }) => {
    	return {
    	    // ...
    		server: {
    			watch: {
    				usePolling: true,
    			}
    		},
    		// ...
    	}
    }
    
  2. Start application in debug mode and update file Settings.vue

    vite --debug hmr
    

HMR%20Log.webp - components are lazy loaded in router and their module graph is loaded separately from the main bundle - hmr needs to track and update this separate module boundary, using the new hmr reload async feature in vue runtime-core

Code Update

Currently, we are using node 16 module resolution, default to commonjs. Here are the files of package @vue/runtime-core:

├──  runtime-core.d.ts  
├──  runtime-core.cjs.prod.js  
├──  runtime-core.cjs.js  
└──  runtime-core.esm-bundler.js

Only {bash} runtime-core.esm-bundler.js contains: - Full HMR state management - Dynamic import boundary handling - Module graph tracking for updates - Hot reload handlers for Vue components

We need to update module resolution in {zsh} vite.config.js

import { defineConfig, loadEnv } from 'vite'
import { resolve } from 'path'
import alias from '@rollup/plugin-alias'

import vue from '@vitejs/plugin-vue'
import svgLoader from 'vite-svg-loader'
import childProcess from 'child_process'
import Components from 'unplugin-vue-components/vite'

const commitHash = childProcess.execSync('git rev-parse --short HEAD').toString().slice(0, 8).replace('\n', '')

export default defineConfig(({ command, mode }) => {
  // Load env file based on `mode` in the current working directory.
  // Set the third parameter to '' to load all env regardless of the `VITE_` prefix.

  return {
    server: {
      watch: {
        usePolling: true,
      },
      headers: {
        'Cross-Origin-Opener-Policy': 'unsafe-none',
        'Cross-Origin-Embedder-Policy': 'unsafe-none',
      }
    },
    resolve: {
      alias: [
        {
          find: '@vue/runtime-core',
          replacement: '@vue/runtime-core/dist/runtime-core.esm-bundler.js',
        },
      ],
    },
    rollupPluginVueOptions: {
      cssModulesOptions: {
        generateScopedName: '[hash:base64:8]',
      }
    },
    build: {
      manifest: true,
      target: 'esnext',
      emptyOutDir: false,
      rollupOptions: {
        input: {
          main: resolve(__dirname, 'index.html'),
          theme: resolve(__dirname, 'index_themes.html')
        },
        output: {
          assetFileNames: `assets/[name].${commitHash}[extname]`,
          chunkFileNames: `assets/[name].${commitHash}.js`,
          entryFileNames: `assets/[name].${commitHash}.js`
        }
      },
      minify: 'esbuild'
    },
    plugins: [
      Components({
        dts: false,
        globs: ['!src/components/*.{vue}'],
        resolvers: [
          (name) => {
            const componentName = name
            const regex = /^Ph[A-Z][a-zA-Z]*$/
            if (regex.test(componentName)) {
              return {
                importName: componentName,
                path: '@phosphor-icons/vue',
                sideEffect: true
              }
            }
          }
        ]
      }),
      vue(
        {
          template: {
            compilerOptions: {
              isCustomElement: (tag) => ['a-col', 'a-row'].includes(tag),
            }
          }
        }
      ),
      svgLoader(),
      alias({
        entries: [
          {
            find: '@',
            replacement: resolve(__dirname, 'src')
          }
        ]
      }),
    ]
  }
})

Results

  • All files now are hot reloaded dynamically in development
  • Fix full page reload takes 5-10 seconds to 0 second.