将 Vite 添加到您的现有 Web 应用程序

Avatar of Adam Rackis
Adam Rackis

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

Vite (发音为“veet”)是一种新式的 JavaScript 捆绑器。 它包含了电池,几乎不需要配置即可使用,并且包含大量配置选项。 哦,而且它很快。 非常快。

这篇文章将逐步介绍将现有项目转换为 Vite 的过程。 我们将涵盖别名、模拟 webpack 的 dotenv 处理和服务器代理等内容。 换句话说,我们正在研究如何将项目从其现有的捆绑器迁移到 Vite。 如果您想从头开始一个新项目,您需要 跳转到他们的文档

长话短说:CLI 会询问您选择的框架——React、Preact、Svelte、Vue、Vanilla 甚至 lit-html——以及您是否想要 TypeScript,然后为您提供一个功能齐全的项目。

首先搭建! 如果您有兴趣了解如何将 Vite 集成到遗留项目中,我仍然建议您搭建一个空项目并四处看看。 有时我会粘贴一些代码块,但其中大部分直接来自默认的 Vite 模板。

我们的用例

我们正在研究的是基于我自己将 booklist 项目 (repo) 的 webpack 构建迁移到 Vite 的经验。 这个项目没有什么特别之处,但它相当大而且很旧,并且严重依赖 webpack。 因此,从某种意义上说,这是一个很好的机会,可以在我们迁移到 Vite 的过程中看到 Vite 一些更有用的配置选项的实际应用。

我们不需要的东西

选择 Vite 的最令人信服的原因之一是,它在开箱即用时已经做得很好了,它整合了其他框架的许多职责,因此依赖项更少,配置和约定的基础更完善。

因此,与其从一开始就说明我们需要什么,不如我们来看一下所有常见的 webpack 东西,我们不需要,因为 Vite 免费提供它们。

静态资源加载

我们通常需要在 webpack 中添加类似这样的内容

{
  test: /\.(png|jpg|gif|svg|eot|woff|woff2|ttf)$/,
  use: [
    {
      loader: "file-loader"
    }
  ]
}

这将获取对字体文件、图像、SVG 文件等的任何引用,并将它们复制到您的 dist 文件夹中,以便可以从您的新捆绑包中引用它们。 Vite 标准配备了它。

样式

我在这里有意地说“样式”而不是“css”,因为对于 webpack,您可能会有类似这样的东西

{
  test: /\.s?css$/,
  use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
},

// later

new MiniCssExtractPlugin({ filename: "[name]-[contenthash].css" }),

…这允许应用程序导入 CSS SCSS 文件。 您会厌倦听到我说这句话,但 Vite 开箱即用地支持它。 只需确保将 Sass 本身安装到您的项目中,Vite 会处理其余的工作。

转译 / TypeScript

您的代码可能正在使用 TypeScript 以及或非标准 JavaScript 特性,例如 JSX。 如果是这种情况,您需要将代码转译以删除这些东西并生成浏览器(或 JavaScript 解析器)可以理解的纯 JavaScript。 在 webpack 中,这看起来像这样

{
  test: /\.(t|j)sx?$/,
  exclude: /node_modules/,
  loader: "babel-loader"
},

…以及相应的 Babel 配置,以指定适当的插件,对我来说,它看起来像这样

{
  "presets": ["@babel/preset-typescript"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-proposal-optional-chaining",
    "@babel/plugin-proposal-nullish-coalescing-operator"
  ]
}

虽然我可能在多年前就停止使用那两个插件了,但这并不重要,因为我相信您已经猜到了,Vite 为我们做了所有这些工作。 它获取您的代码,删除任何 TypeScript 和 JSX,并生成现代浏览器支持的代码。

如果您想支持旧浏览器(我并不是说您应该这样做),那么 有一个插件可以做到这一点

node_modules

令人惊讶的是,webpack 需要您告诉它解析来自 node_modules 的导入,我们使用以下方法来实现这一点

resolve: {
  modules: [path.resolve("./node_modules")]
}

正如预期的那样,Vite 已经做到了这一点。

生产模式

我们在 webpack 中经常做的一件事是通过手动传递一个 mode 属性来区分生产环境和开发环境,像这样

mode: isProd ? "production" : "development",

…我们通常通过类似这样的东西来推断它

const isProd = process.env.NODE_ENV == "production";

当然,我们通过构建过程设置该环境变量。

Vite 对此的处理方式略有不同,它为我们提供了不同的命令来运行开发构建和生产构建,我们很快就会介绍到。

文件扩展名

为了避免说得太多,我会快速指出 Vite 也不需要您指定您使用的每个文件扩展名。

resolve: {
  extensions: [".ts", ".tsx", ".js"],
}

只需设置好适当的 Vite 项目,您就可以开始了。

Rollup 插件兼容!

这是一个非常重要的点,我想在单独的部分中说明。 如果在完成这篇博文后,您仍然需要在 Vite 应用程序中替换一些 webpack 插件,那么尝试找到一个等效的 Rollup 插件并使用。 您没听错:Rollup 插件已经(或者通常至少)与 Vite 兼容。 当然,某些 Rollup 插件会做一些事情与 Vite 的工作方式不兼容,但总的来说,它们应该可以正常工作。

有关更多信息,请查看文档

您的第一个 Vite 项目

请记住,我们正在将现有的遗留 webpack 项目迁移到 Vite。 如果您正在构建新项目,最好从头开始一个新的 Vite 项目。 也就是说,我展示给您的初始代码基本上是直接从 Vite 从头开始搭建的项目中复制过来的,因此花点时间搭建一个新项目对于您来说可能也是个好主意,以便比较流程。

HTML 入口点

没错,你没看错。Vite 不是像 Webpack 那样将 HTML 集成放在插件后面,而是将 HTML 放在首位。它需要一个 HTML 文件,其中包含一个指向 JavaScript 入口点的脚本标签,并从此处生成所有内容。

以下是我们开始使用的 HTML 文件(Vite 期望该文件名为 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>The GOAT of web apps</title>
  </head>
  <body>
    <div id="home"></div>
    <script type="module" src="/reactStartup.tsx"></script>
  </body>
</html>

请注意,<script> 标签指向 /reactStartup.tsx。根据需要将其调整为自己的入口点。

让我们安装一些东西,比如 React 插件

npm i vite @vitejs/plugin-react @types/node

我们还将在项目目录中,与 index.html 文件相邻的位置创建以下 vite.config.ts 文件。

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

export default defineConfig({
  plugins: [react()]
});

最后,让我们添加一些新的 npm 脚本

"dev": "vite",
"build": "vite build",
"preview": "vite preview",

现在,让我们使用 npm run dev 启动 Vite 的开发服务器。它非常快,并且根据请求的内容增量构建所需的内容。

但是,不幸的是,它失败了。至少现在是这样。

Screenshot of a terminal screen with a dark background and light text. There is an error in read that says there was an error when starting the development server.

我们将在稍后介绍如何设置别名,但现在,让我们修改 reactStartup 文件(或您的入口文件名称)如下所示

import React from "react";
import { render } from "react-dom";

render(
  <div>
    <h1>Hi there</h1>
  </div>,
  document.getElementById("home")
);

现在,我们可以运行 npm run dev 命令,然后浏览到 localhost:3000

Screenshot of a terminal window with a black background and light text. Green text says the development server is running at localhost.
Screenshot of a blank white page that says hi there in black in a default serif font.

热模块替换 (HMR)

现在开发服务器正在运行,尝试修改源代码。输出应该会通过 Vite 的 HMR 几乎立即更新。这是 Vite 最好的功能之一。当更改似乎立即反映出来而不是必须等待,甚至需要我们自己触发时,它使开发体验变得更加愉快。

本文的其余部分将介绍我必须做的一切才能让我的应用程序使用 Vite 构建和运行。希望其中一些对您也有用!

别名

基于 Webpack 的项目通常会有一些类似于以下的配置

resolve: {
  alias: {
    jscolor: "util/jscolor.js"
  },
  modules: [path.resolve("./"), path.resolve("./node_modules")]
}

这将在提供的路径上为 jscolor 设置一个别名,并告诉 Webpack 在解析导入时同时查找根文件夹 (./) 和 node_modules 中的内容。这允许我们在组件树的任何位置进行如下导入

import { thing } from "util/helpers/foo"

…假设在最顶层有一个 util 文件夹。

Vite 不允许您像这样提供整个文件夹进行解析,但它允许您指定别名,这些别名遵循与 @rollup/plugin-alias 相同的规则

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

import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      jscolor: path.resolve("./util/jscolor.js"),
      app: path.resolve("./app"),
      css: path.resolve("./css"),
      util: path.resolve("./util")
    }
  },
  plugins: [react()]
});

我们添加了一个 resolve.alias 部分,其中包括我们需要的所有别名的条目。我们的 jscolor 实用程序设置为相关的模块,并且我们为我们的顶层目录设置了别名。现在,我们可以从任何组件的任何位置导入 app/css*/*util/

请注意,这些别名仅适用于导入的根目录,例如 util/foo。如果您在树的更深处还有其他 util 文件夹,并使用以下方法引用它

import { thing } from "./helpers/util";

…那么上面的别名将不会影响它。这种区别没有得到很好的记录,但您可以在 Rollup 别名插件中看到它。Vite 的别名匹配相同的行为。

环境变量

Vite 当然 支持环境变量。它在开发环境中从 .env 文件中读取配置值,或者从 process.env 中读取,并将它们注入您的代码中。不幸的是,它们的工作方式与您可能习惯的方式略有不同。首先,它不替换 process.env.FOO,而是替换 import.meta.env.FOO。不仅如此,默认情况下它只替换以 VITE_ 为前缀的变量。因此,import.meta.env.VITE_FOO 将被实际替换,但我的原始 FOO 不会被替换。可以配置此前缀,但不能将其设置为空字符串。

对于遗留项目,您可以使用 grep 替换所有环境变量以使用 import.meta.env,然后添加 VITE_ 前缀,更新您的 .env 文件,并更新您使用的任何 CI/CD 系统中的环境变量。或者,您可以配置在开发模式下从 .env 文件中替换 process.env.ANYTHING 的更经典行为,或者在生产模式下使用真实的 process.env 值。

以下是操作方法。Vite 的 define 功能基本上是我们需要的。它在开发环境中注册全局变量,并在生产环境中进行原始文本替换。我们需要设置一切,以便在开发模式下手动读取 .env 文件,并在生产模式下读取 process.env 对象,然后添加相应的 define 条目。

让我们将所有这些都构建到一个 Vite 插件中。首先,运行 npm i dotenv

现在让我们看一下插件的代码

import dotenv from "dotenv";

const isProd = process.env.NODE_ENV === "production";
const envVarSource = isProd ? process.env : dotenv.config().parsed;

export const dotEnvReplacement = () => {
  const replacements = Object.entries(envVarSource).reduce((obj, [key, val]) => {
    obj[`process.env.${key}`] = `"${val}"`;
    return obj;
  }, {});

  return {
    name: "dotenv-replacement",
    config(obj) {
      obj.define = obj.define || {};
      Object.assign(obj.define, replacements);
    }
  };
};

Vite 为我们设置了 process.env.NODE_ENV,所以我们只需检查它来查看我们处于哪种模式。

现在,我们获取实际的环境变量。如果我们处于生产模式,我们将获取 process.env 本身。如果我们处于开发模式,我们将要求 dotenv 获取我们的 .env 文件,解析它,然后返回一个包含所有值的数组。

我们的插件是一个返回 Vite 插件对象的函数。我们将环境值注入一个新的对象中,该对象在前面有 process.env.,然后返回实际的插件对象。可以使用许多挂钩。但是在这里,我们只需要 config 挂钩,它允许我们修改当前的配置对象。如果不存在,我们将添加一个 define 条目,然后添加所有值。

但在继续之前,我想指出我们正在解决的 Vite 环境变量限制是有原因的。上面的代码是捆绑程序的常见配置方式,但这仍然意味着如果该键存在,process.env 中的任何随机值都会被粘贴到您的源代码中。这可能存在潜在的安全问题,请牢记这一点。

服务器代理

您的已部署 Web 应用程序是什么样的?如果它只是提供 JavaScript/CSS/HTML(实际上所有内容都是通过其他地方的独立服务完成的),那就太好了!您实际上已经完成了。我向您展示的内容应该足够了。Vite 的开发服务器将根据需要提供您的资产,就像以前一样 ping 所有您的服务。

但是,如果您的 Web 应用程序很小,以至于您在 Web 服务器上运行了一些服务呢?对于我正在转换的项目,我在 Web 服务器上运行了一个 GraphQL 端点。在开发过程中,我启动了我的 Express 服务器,它以前知道如何提供 Webpack 生成的资产。我还启动了一个 Webpack 监视任务来生成这些资产。

但是,使用 Vite 自带的开发服务器,我们需要启动该 Express 服务器(在与 Vite 使用的端口不同的端口上),然后将对 /graphql 的调用代理到那里

server: {
  proxy: {
    "/graphql": "https://#:3001"
  }
} 

这告诉 Vite,对 /graphql 的任何请求都应该发送到 https://#:3001/graphql

请注意,我们没有在配置中将代理设置为 https://#:3001/graphql。相反,我们将其设置为 https://#:3001,并依靠 Vite 添加 /graphql 部分(以及任何查询参数)到路径中。

构建库

作为一个小小的附加部分,让我们简要讨论一下构建库。例如,如果您要构建的只是一个 JavaScript 文件,例如 Redux 之类的库。它没有关联的 HTML 文件,因此您需要先告诉 Vite 要生成什么

build: {
  outDir: "./public",
  lib: {
    entry: "./src/index.ts",
    formats: ["cjs"],
    fileName: "my-bundle.js"
  }
}

告诉 Vite 将生成的包放在哪里,要叫什么名字,以及要构建什么格式。请注意,这里我使用的是 CommonJS 而不是 ES 模块,因为 ES 模块不会压缩(截至撰写本文时),因为担心这可能会破坏树摇动。

您可以使用 vite build 运行此构建。要启动监视并让库在更改时重建,您可以运行

vite build --watch.

总结

Vite 是一款令人兴奋的工具。它不仅消除了捆绑 Web 应用程序的痛苦,而且还极大地提高了构建过程的性能。它自带一个超快的开发服务器,该服务器支持热模块替换,并支持所有主要 JavaScript 框架。如果您进行 Web 开发(无论是出于兴趣还是工作需要,或者两者兼而有之!),我强烈推荐它。