Vite(发音为 "veet")是一个新的JavaScript捆绑器。它包含了电池,几乎不需要任何配置就可以使用,并且包括大量的配置选项。哦,而且它的速度很快。令人难以置信的快。
这篇文章将介绍如何将一个现有的项目转换为Vite的过程。我们将包括别名、调整webpack的dotenv处理和服务器代理等内容。换句话说,我们要研究的是如何将一个项目从现有的捆绑器转移到Vite。如果你想开始一个新的项目,你会想跳到他们的文档。
长话短说:CLI会询问你选择的框架--React、Preact、Svelte、Vue、Vanilla,甚至lit-html,以及你是否需要TypeScript,然后给你一个完整的项目。
首先是脚手架!如果你有兴趣学习将Vite集成到一个传统项目中,我还是建议你先搭建一个空项目的脚手架,并在其中探究一番。有时,我也会粘贴一些代码块,但大部分都是直接来自Vite的默认模板。
我们的用例
我们所看到的是基于我自己迁移书单项目(repo)的webpack构建的经验。这个项目并没有什么特别之处,但它相当大,而且很老,对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特性,比如JJSX。如果是这样的话,你需要转译你的代码,以去除这些东西,产生浏览器(或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插件是兼容的
这是一个非常关键的点,我想把它放在自己的章节里说出来。如果你在写完这篇博文后,仍有一些webpack插件需要在你的Vite应用中替换,那么请尝试找到一个同等的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的开发服务器。它的速度快得令人难以置信,并且根据请求的内容,逐步构建它需要的东西。
但是,不幸的是,它失败了。至少现在是这样。

我们稍后会讨论如何设置别名,但现在,让我们改成修改我们的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 。


热模块重载(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 util被设置为相关的模块,而且我们的顶层目录也有别名。现在我们可以从任何地方的任何组件中导入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 中的任何随机值都会卡在你的源代码中,如果该键存在的话。那里有潜在的安全问题,所以请牢记这一点。
服务器代理
你部署的网络应用程序是什么样子的?如果它所做的只是为JavaScript/CSS/HTML提供服务--而实际上所有的事情都是通过位于其他地方的独立服务进行的--那么很好!你已经完成了。你已经有效地完成了。我所展示的应该是你所需要的一切。Vite的开发服务器将根据需要为你的资产提供服务,这就像以前一样,对你的所有服务进行ping。
但是,如果你的网络应用足够小,你有一些服务就在你的网络服务器上运行呢?对于我正在转换的项目,我有一个GraphQL端点在我的Web服务器上运行。为了开发,我启动了我的Express服务器,它以前知道如何为webpack生成的资产提供服务。我还启动了一个webpack观察任务来生成这些资产。
但由于Vite提供了自己的开发服务器,我们需要启动Express服务器(与Vite使用的端口不同),然后将对/graphql 的调用代理到那里。
server: {
proxy: {
"/graphql": "http://localhost:3001"
}
}
这告诉Vite,任何对/graphql 的请求都应该被发送到http://localhost:3001/graphql 。
请注意,我们没有在配置中设置代理到http://localhost:3001/graphql 。相反,我们将其设置为http://localhost:3001 ,并依靠Vite将/graphql 部分(以及任何查询参数)添加到路径中。
构建libs
作为一个快速的补充部分,让我们简单地讨论一下构建库。例如,如果你想构建的只是一个JavaScript文件,例如Redux这样的库。没有相关的HTML文件,所以你首先需要告诉Vite要制作什么。
build: {
outDir: "./public",
lib: {
entry: "./src/index.ts",
formats: ["cjs"],
fileName: "my-bundle.js"
}
}
告诉Vite把生成的bundle放在哪里,怎么称呼它,以及用什么格式来构建。请注意,我在这里使用CommonJS,而不是ES模块,因为ES模块不进行最小化处理(截至目前),因为担心会破坏树形震荡。
你可以用vite build 来运行这个构建。要开始观察并在变化时重建该库,你可以运行
vite build --watch.
总结
Vite是一个令人难以置信的工具。它不仅减轻了捆绑网络应用的痛苦和泪水,而且在这个过程中极大地提高了这样做的性能。它有一个快得惊人的开发服务器,带有热模块重载,支持所有主要的JavaScript框架。如果你从事网络开发--不管是为了好玩,还是为了工作,或者两者兼而有之--我不能不强烈推荐它。