一文带你打开Vite的新世界

vite 初识

创建一个 vite 项目,只需要:

shell 复制代码
yarn create vite

然后按照提示进行操作即可(这里选的是 react + js)。生成的项目目录如下:

js 复制代码
├─ public
│ └─ vite.svg
├─ src
│ ├─ assets
│ │ └─ react.svg
│ ├─ App.css
│ ├─ App.jsx
│ ├─ index.css
│ └─ main.jsx
├─ index.html
├─ package.json
└─ vite.config.js

这个命令先是安装一个全局依赖 create-vite,然后运行 create-vite命令。等同于:

shell 复制代码
npm install create-vite -g && create-vite

create-vite 就是一个 vite 的脚手架,会根据你的需要选择不同模板来克隆项目。

我们可以通过npm view create-vite来查看 create-vite 的相关信息。

需要注意的是, vite 对 node 版本有要求, 要求 node 版本是 ^14.18.0 || >=16.0.0

可以看到根目录有个 index.html。这个是 vite 项目的入口文件。 Vite 解析 <script type="module" src="..."> ,这个标签指向我们的 JavaScript 源码。

为什么选择 vite

Vite 是一种新型前端构建工具,能够 显著提升 前端开发体验 为什么能够显著提升开发体验呢?首先我们了解结构建工具做了哪些工作。

1. 传统构建工具所做的工作(自动化)

  • 模块化开发支持:支持直接从 node_modules 引入代码,支持多重模块化
  • 处理代码的兼容性:比如 ES6 的代码降级,jsx 转换为 js, less/sass 转换为 css(不是构建工具做的,构建工具将这些工具集成进来自动化处理)
  • 提高项目性能:压缩代码,代码分割
  • 提高开发体验:提供开发服务器,能够解决服务跨域的问题(本地代理)。监听文件的变化,文件变化后能够自动调用相应的工具重新处理、打包,在浏览器重新运行(热更新)

这样,我们就不用管理代码如何处理,如何在浏览器运行,只需要关注开发工作即可。

目前的构建工具,通常是这个流程:从入口构建依赖图 => 对所有模块打包 => 浏览器运行。如下图:

当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。

Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。

2.vite 的设计理念

2.1 开发服务器

Vite 通过在一开始将应用中的模块区分为 依赖源码 两类,改进了开发服务器启动时间。

  • 依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。Vite 将会使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
  • 源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。

Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。如下图:

2.2 热更新(HMR)

传统的 HMR:当我们对代码做修改并保存后,webpack 会对修改的代码块以及该模块的依赖重新编译打包,并将新的模块发送至浏览器端,浏览器用新的模块代替旧的模块,从而实现了在不刷新浏览器的前提下更新页面。相比起直接刷新页面的方案,HMR 的优点是可以保存应用的状态。当然,随着项目体积的增长,热更新的速度也会随之下降。

在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。 Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。

2.3 为什么生产环境仍需打包

尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。

依赖预构建

1. 为什么需要依赖预构建

我们看这样一个例子。创建下面的目录,并在 index.html 中以 module 的形式引入 main.js

js 复制代码
prebuild-demo
├─ index.html
├─ main.js
└─ package.json
html 复制代码
// index.html
// ...
<script type="module" src="./main.js"></script>
// ...

接着,我们npm install lodash -S 安装 lodash,并在 main.js 中写入:

js 复制代码
// main.js
import { throttle } from 'lodash';
console.log(throttle);

然后,在浏览器打开 index.html,会报错:

这是因为 ESModule 中,相对引用要采用/, ./, 或 ../开头。因此,不能够通过依赖的方式直接引入。

依赖预构建,能够重写这部分模块引入,从而解决问题

我们安装 vite,并用 vite 启动项目:

shell 复制代码
npm install vite && npx vite

打开控制台,throttle 已经能打印出来了。

再看 main.js ,模块引入变成了具体的地址:

除了依赖补全,依赖预构建还做了这两个工作:

  • CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。
  • 提高性能: Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。比如官网的例子,lodash-es。

2. 缓存

2.1 文件系统缓存

Vite 会将预构建的依赖缓存到 node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:

  • package.json中的 dependencies 列表

  • 包管理器的 lockfile,例如 package-lock.json, yarn.lock,或者 pnpm-lock.yaml

  • 可能在 vite.config.js 相关字段中配置过的

    只有在上述其中一项发生更改时,才需要重新运行预构建。

    如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。

2.2 浏览器缓存

解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable 强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:

  1. 通过浏览器调试工具的 Network 选项卡暂时禁用缓存;
  2. 重启 Vite dev server,并添加--force 命令以重新构建依赖;
  3. 重新载入页面。

常用功能与配置

1. CSS Modules

任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象。

也就是说我们直接可以模块引入:

js 复制代码
import styles from "./index.module.less";```

CSS Modules 比较常用的配置:

js 复制代码
// ...
css: {
    modules: {
      generateScopedName: "[path][name]__[local]__[hash:5]",
      localsConvention: "camelCaseOnly"
    }
  },
  // ...

其中,

  • generateScopedName: 生成的类名格式
  • localsConvention:修改生成对象的 key 的展示形式 (驼峰还是中划线)

2. CSS 预处理器

vite 提供了对 sass/less/stylus 的内置支持。 我们只需要安装相应的预处理器依赖即可直接使用。

shell 复制代码
npm install less -D

比较常用的配置:

js 复制代码
css: {
    // ...
    preprocessorOptions: {
      less: {
        additionalData: `@import '@/assets/styles/common.less';`, // 全局注入样式文件
        modifyVars: {
          'primary-color': '#409eff' // 全局样式变量
        },
        javascriptEnabled: true
      }
    },

    devSourcemap: true // 默认false,设为true开发阶段启动用sourcemap
  },

3. PostCSS

vite 提供了对 PostCSS 的内置支持。

我们可以在 vite.config.js 中配置 PostCSS ,也可以直接新建 postcss.config.js 文件配置 PostCSS 。

这里我们使用 postcss-preset-env 试一下,postcss-preset-env 包含一系列 PostCSS 的插件。比如浏览器前缀自动添加:

shell 复制代码
npm install postcss-preset-env -D

根目录新增 postcss.config.js ,并配置如下:

js 复制代码
import postcssPresetEnv from 'postcss-preset-env';

export default {
  plugins: [postcssPresetEnv()]
};

重启开发服务器,就能够看到浏览器前缀自动添加了:

4. 静态资源处理

4.1 资源引入

vite 中,引入一个静态资源会返回解析后的公共路径:

js 复制代码
import exampleImg from "/src/assets/example.png";

exampleImg 在开发时会是 /src/assets/example.png,生产环境会是 /assets/example.2d8efhg.png。类似于 webpack4 中的 file-loader.

  • 常见的图像、媒体和字体文件类型被自动检测为资源。你可以使用 assetsInclude 选项 扩展内部列表。
js 复制代码
export default defineConfig({
  assetsInclude: ['**/*.gltf'] // 会把.gltf文件当做资源文件处理
})
  • 较小的资源体积小于 assetsInlineLimit 选项值 则会被内联为 base64 data URL。
js 复制代码
 // ...
 build: {
    assetsInlineLimit: 8 * 1024, // 小于 8 KB的资源会被内联成base64格式
    // ...
  },

也可以通过 ?raw 后缀声明作为字符串引入。类似于 webpack4 中的 raw-loader

js 复制代码
import helloString from "./test.txt?raw";
console.log("exampleImg", helloString); // hello, vite

4.2 public 目录

如果你有下列这些资源:

  • 不会被源码引用(例如 robots.txt)
  • 必须保持原有文件名(没有经过 hash)
  • ...或者你压根不想引入该资源,只是想得到其 URL。 那么你可以将该资源放在指定的 public 目录中,它应位于你的项目根目录。该目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下。 目录默认是 /public,但可以通过 publicDir 选项 来配置。 请注意:
  • 引入 public 中的资源永远应该使用根绝对路径 ------ 举个例子,public/icon.png 应该在源码中被引用为 /icon.png
  • public 中的资源不应该被 JavaScript 文件引用。

5. alias 与 extensions

通过下面的代码配置别名和扩展名缩写:

js 复制代码
import { resolve } from "path";
// ...
resolve: {
    alias: {
      "@": resolve(__dirname, "./src")
    },
    extensions: [".jsx", ".js", ".tsx", ".ts", ".json"]
},

6. 本地开发服务器

js 复制代码
server: {
    open: true, // 自动打开浏览器
    host: "0.0.0.0",
    port: 9999,
    strictPort: true, // 设置为false,端口被占用会直接退出
    proxy: {
      "/webapi": {
        target: "http://10.2.2.98:8090",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/webapi/, "")
      }
    }
  }

环境变量

1. 内建环境变量

Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量:

  • import.meta.env.MODE: {string} 应用运行的模式。
  • import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由 base 配置项决定。
  • import.meta.env.PROD: {boolean} 应用是否运行在生产环境。
  • import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD 相反)。
  • import.meta.env.SSR: {boolean} 应用是否运行在 server 上。

2. .env 文件

我们可以在 .env 文件中编写自己需要的环境变量。

js 复制代码
.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略

自己编写的环境变量必须以 VITE_ 为前缀,比如:

js 复制代码
VITE_SOME_KEY=123
DB_PASSWORD=foobar // 不合法
js 复制代码
console.log(import.meta.env.VITE_SOME_KEY) // 123
console.log(import.meta.env.DB_PASSWORD) // undefined

3. 模式

默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式。

如果我们需要额外的模式,则可以使用 --mode 覆盖默认的模式:

shell 复制代码
vite build --mode staging

同时我们还需要一个 .env.staging 文件来定义环境变量:

js 复制代码
# .env.staging
NODE_ENV=production
VITE_HTTP=http://10.2.2.245:8890

生产构建优化

1. 分包策略

生产环境打包的时候,我们可能会需要分包。比如:把依赖单独打一个包,这样就可以避免依赖被重复打包。

js 复制代码
    build: {
      assetsInlineLimit: 8 * 1024, // 小于8KB的资源base64内联
      rollupOptions: {
        output: {
          manualChunks(id) {
            if (id.includes("node_modules")) {
              return "vendor";
            }
          }
        }
      }
    },

rollupOptions 里还能够配置打包生成的目录,一个常用的配置:

js 复制代码
 build: {
      rollupOptions: {
        output: {
          // ...
          assetFileNames: (assetInfo) => {
            var info = assetInfo.name.split(".");
            var extType = info[info.length - 1];
            if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
              extType = "media";
            } else if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) {
              extType = "img";
            } else if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) {
              extType = "fonts";
            }
            return `static/${extType}/[name]-[hash][extname]`;
          },
          chunkFileNames: "static/js/[name]-[hash].js",
          entryFileNames: "static/js/[name]-[hash].js"
        }
      }
    },

打包后的目录:

2. 动态导入

动态导入(import 函数)是 ES6 的新特性。使用动态导入语法能够实现分包,进而实现懒加载。通常用于路由的懒加载。下面是一个例子。

js 复制代码
// import { throttle } from 'lodash';
// console.log('object :>> throttle', throttle);

import('lodash').then(({ throttle }) => {
  console.log('object :>> throttle', throttle);
});

上面是直接导入,下面是动态导入。二者打包结果如下:

可以看出,下面的 lodash 已经自动分包了。

3. 图片压缩、gzip 压缩

通过 vite-plugin-imageminvite-plugin-compression 插件可以实现图片压缩与 gzip 压缩。用法也比较简单:

js 复制代码
import compression from 'vite-plugin-compression';
import imagemin from 'vite-plugin-imagemin';

export default defineConfig({
  // ...
  plugins: [react(), compression(), imagemin()]
  // ...
});

4. CDN 优化(外网环境)

通过 vite-plugin-cdn-import 插件能够将一些依赖使用 cdn 加载,从而降低包的大小,加快依赖加载速度。用法如下:

js 复制代码
import { Plugin as importToCDN } from 'vite-plugin-cdn-import';

export default defineConfig({
  plugins: [
    react(),
    importToCDN({
      modules: [
        {
          name: 'lodash',
          var: '_',
          path: 'https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js'
        }
      ]
    })
  ]
});

参考资料:

Vite 官方文档: cn.vitejs.dev/guide/

Vite 和 webpack、rollup 打包工具对比:blog.csdn.net/Ambibibitio...

相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼16 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax