banner
AgedCoffee

AgedCoffee

Exploring the original build tool Vite

Introduction#

Vite is a brand new front-end build tool, and adding the word meta before the build tool in this metaverse wave seems to endow it with some mysterious power.

With React introducing features like Server-Side Streaming and React Server Components in version 18, the author of the Vue framework, Evan You, who is also the author of Vite, jokingly referred to React as a Meta Framework.

Of course, calling Vite a meta build tool also has its legitimate meaning.

  • The word meta comes from Greek, meaning beyond, and the emergence of Vite signifies surpassing the current state where all projects are based on webpack.
  • In Chinese, the term "meta" can also mean the smallest functional unit, and one of the biggest features of Vite compared to webpack is that it does not require bundling all source code into a single bundle for the browser to run during the development phase. Instead, it directly hands over the code to modern browsers based on esm, where an esm can be understood as that smallest functional unit.

To get to the point, the idea of transforming the project to be driven by Vite arose because with the increasing amount of code and files in the development project, it was clearly felt that the speed of cold starts and hot reloads in the webpack dev environment was slowing down. Additionally, I had heard about Vite's smooth and fast development experience, which made me eager to try it out.

Next, we will mainly discuss the process of transforming the existing webpack-based react+antd project to be driven by Vite.

Significant Reduction of package.json#

Adjustments to the Entry index.html File#

index.html is at the outermost layer of the project and not in the public folder

webpack

<!DOCTYPE html>
<html>
  <head>
    <link rel="icon" href="%PUBLIC_URL%/logo.ico" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root" style="height: 100%"></div>
  </body>
</html>

vite

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="icon" href="/src/assets/logo.ico" />
  </head>
  <body>
    <div id="root"></div>
    <!-- Vite's unique esm entry file -->
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Support for React and TypeScript#

The official plugin @vitejs/plugin-react provides complete support for React.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://cn.vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

Vite has built-in support for compiling TypeScript files using esbuild, and you just need to provide your own tsconfig file in the project.

Support for Less and Module-CSS#

Vite has built-in support for Less and Module-CSS; you just need to install the Less preprocessor dependency. Since antd's Less uses Less's function capabilities, you need to enable the javascriptEnabled configuration in preprocessorOptions.

export default defineConfig({
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
})

Basic Server and Proxy Configuration for the Project#

export default defineConfig({
  server: {
    port: 3000,
    host: 'localhost',
    open: true,
    https: true,
    hmr: {
      host: 'localhost',
    },
    proxy: {
      '/api': "https://target-server",
      '/special/api': {
        target: "https://special-target-server",
        changeOrigin: true,
        rewrite: path => path.replace(/^\/special/, ''),
      },
    },
  },
});

Alias Configuration for Absolute Paths#

  • In the project, we generally use absolute paths to import modules, using @ to replace the path of the src folder under the root directory.
  • Since some dependencies in node_modules contain @ symbols, such imports should not be matched.
  • Some older Less imports use ~ to indicate absolute imports, which need special handling.
export default defineConfig({
  resolve: {
    alias: [
      {
        find: /^@\//,
        replacement: `${path.resolve(__dirname, 'src')}${path.sep}`,
      },
      { find: /^~/, replacement: '' },
    ],
  },
})

Importing Static Files#

Vite supports multiple methods for importing general static resources. During the migration process, we mainly handled a logic that requires dynamically concatenating static resource references based on variables. Since the require method cannot be used outside of the webpack environment, we need to utilize Vite's native esm functionality import.meta.url.

// Dynamically concatenate the path to the flag resource based on short
const getFlag = (short: string) => {
  return new URL(`./flags/${short.toLowerCase()}.png`, import.meta.url).href
}

Configuration of Environment Variables#

Import dotenv-related dependencies and add dotenv to the startup command.

{
  "scripts": {
    "dev": "dotenv -e .env.dev vite",
    "build": "tsc && dotenv -e .env.dev vite build",
    "serve": "vite preview"
  },
  "devDependencies": {
    "dotenv": "^8.2.0",
    "dotenv-cli": "^4.0.0",
    "dotenv-expand": "^5.1.0"
  }
}

In Vite, you need to follow the convention of prefixing with VITE_.

VITE_VAR=SOME_VALUE

In src, define a TypeScript file for static verification env.d.ts.

interface ImportMetaEnv extends Readonly<Record<string, string>> {
  readonly VITE_VAR: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

Manual Chunk Packaging Optimization Logic#

Using lazy loading with React-Router routes in the project generally allows for automatic code splitting, which can solve most scenarios. If code splitting is still unreasonable, you can add manual handling logic for chunk generation in the configuration.

import path from 'path'
import { dependencies } from './package.json'

function renderChunks(deps) {
  let chunks = {}
  Object.keys(deps).forEach((key) => {
    if (key.includes('@types')) return
    if (['react', 'react-router-dom', 'react-dom'].includes(key)) return
    chunks[key] = [key]
  })
  return chunks
}

export default defineConfig({
  // The logic under build only runs for production packaging
  build: {
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-router-dom', 'react-dom'],
          ...renderChunks(dependencies),
        },
      },
    },
  },
})

Strange Pitfalls#

  • The moment internationalization package needs to be imported in a different way to take effect.
// webpack
import 'moment/locale/zh-cn'
// vite
import 'moment/dist/locale/zh-cn'
  • Some dependency packages that work fine in the webpack production environment may throw errors in Vite. The react-response package will throw an error about global being undefined in production, requiring error handling logic to be added in the script tag.
<script>
  if (global === undefined) {
    var global = window
  }
</script>

Complete Vite Configuration#

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { dependencies } from './package.json';

function renderChunks(deps) {
  let chunks = {};
  Object.keys(deps).forEach(key => {
    if (key.includes('@types')) return;
    if (['react', 'react-router-dom', 'react-dom'].includes(key)) return;
    chunks[key] = [key];
  });
  return chunks;
}

export default defineConfig({
  plugins: [
    react(),
  ],
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
  server: {
    port: 3000,
    host: 'localhost',
    open: true,
    https: true,
    hmr: {
      host: 'localhost',
    },
    proxy: {
      '/api': "https://target-server",
      '/special/api': {
        target: "https://special-target-server",
        changeOrigin: true,
        rewrite: path => path.replace(/^\/special/, ''),
      },
    },
  },
  resolve: {
    alias: [
      {
        find: /^@\//,
        replacement: `${path.resolve(__dirname, 'src')}${path.sep}`,
      },
      { find: /^~/, replacement: '' },
    ],
  },
  build: {
    sourcemap: false,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-router-dom', 'react-dom'],
          ...renderChunks(dependencies),
        },
      },
    },
  },
});

Conclusion#

  1. The migrated project runs without issues on the latest Chrome, but support for some older browsers still needs to be verified.
    (Vite itself provides support for older browsers through plugins)

  2. Due to the differences between Vite's development and production build methods, there may be some confusion regarding the final online deployment.

  3. Additionally, for the migrated project, the way antd styles are referenced involves fully importing the source code of Less and adding custom Less theme variable overrides. Since browsers do not support running Less files, Vite still needs to fully compile Less files before the project can start. In cases with many Less files, the compilation speed may be slow. In the future, we can configure on-demand imports for Less and inject plugins for Less theme variables.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.