コンテンツにスキップ

Vite

このコンテンツはまだ日本語訳がありません。

维特

Vite 是一个功能强大、性能卓越且可扩展的 JavaScript 项目开发环境。为了改进和扩展 Remix 的捆绑功能,我们现在支持 Vite 作为替代编译器。未来,Vite 将成为 Remix 的默认编译器。

经典 Remix 编译器与 Remix Vite

现有的 Remix 编译器可通过remix buildremix devCLI 命令访问并通过remix.config.js进行配置,现在被称为经典 Remix 编译器

Remix Vite 插件和 remix vite:buildremix vite:dev CLI 命令统称为Remix Vite

除非另有说明,否则今后的文档将假定使用 Remix Vite。

入门

我们有几个不同的基于 Vite 的模板可供您入门。

Terminal window
# Minimal server:
npx create-remix@latest
# Express:
npx create-remix@latest --template remix-run/remix/templates/express
# Cloudflare:
npx create-remix@latest --template remix-run/remix/templates/cloudflare
# Cloudflare Workers:
npx create-remix@latest --template remix-run/remix/templates/cloudflare-workers

这些模板包括一个 vite.config.ts 文件,用于配置 Remix Vite 插件。

配置

Remix Vite 插件通过项目根目录中的 vite.config.ts 文件进行配置。有关更多信息,请参阅我们的 Vite 配置文档

Cloudflare

要开始使用 Cloudflare,您可以使用 cloudflare 模板:

Terminal window
npx create-remix@latest --template remix-run/remix/templates/cloudflare

有两种方法可以在本地运行 Cloudflare 应用程序:

Terminal window
# Vite
remix vite:dev
# Wrangler
remix vite:build # build app before running wrangler
wrangler pages dev ./build/client

虽然 Vite 提供了更好的开发体验,但 Wrangler 通过在 Cloudflare 的 workerd 运行时 而不是 Node 中运行服务器代码,提供了更接近 Cloudflare 环境的模拟。

Cloudflare 代理

为了在 Vite 中模拟 Cloudflare 环境,Wrangler 提供了 [Node 代理到本地 workerd 绑定] wrangler-getplatformproxy。 Remix 的 Cloudflare Proxy 插件为您设置了以下代理:

vite.config.ts
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remixCloudflareDevProxy(), remix()],
});

然后,代理可以在loaderaction函数中的context.cloudflare中使用:

export const loader = ({ context }: LoaderFunctionArgs) => {
const { env, cf, ctx } = context.cloudflare;
// ... more loader code here...
};

查看 Cloudflare 的 getPlatformProxy 文档 以获取有关每个代理的更多信息。

绑定

要配置 Cloudflare 资源的绑定:

每当您更改wrangler.toml文件时,您都需要运行wrangler types来重新生成绑定。

然后,您可以通过 context.cloudflare.env 访问您的绑定。 例如,将 KV 命名空间 绑定为 MY_KV

app/routes/_index.tsx
export async function loader({
context,
}: LoaderFunctionArgs) {
const { MY_KV } = context.cloudflare.env;
const value = await MY_KV.get("my-key");
return json({ value });
}

增强加载上下文

如果您想向加载上下文添加其他属性, 您应该从共享模块中导出 getLoadContext 函数,以便 Vite、Wrangler 和 Cloudflare Pages 中的加载上下文都以相同的方式增强

load-context.ts
import { type AppLoadContext } from "@remix-run/cloudflare";
import { type PlatformProxy } from "wrangler";
// When using `wrangler.toml` to configure bindings,
// `wrangler types` will generate types for those bindings
// into the global `Env` interface.
// Need this empty interface so that typechecking passes
// even if no `wrangler.toml` exists.
interface Env {}
type Cloudflare = Omit<PlatformProxy<Env>, "dispose">;
declare module "@remix-run/cloudflare" {
interface AppLoadContext {
cloudflare: Cloudflare;
extra: string; // augmented
}
}
type GetLoadContext = (args: {
request: Request;
context: { cloudflare: Cloudflare }; // load context _before_ augmentation
}) => AppLoadContext;
// Shared implementation compatible with Vite, Wrangler, and Cloudflare Pages
export const getLoadContext: GetLoadContext = ({
context,
}) => {
return {
...context,
extra: "stuff",
};
};

您必须将 getLoadContext 传递给 Cloudflare Proxy 插件和 functions/[[path]].ts 中的请求处理程序,否则您将获得不一致的加载上下文增强,具体取决于您运行应用程序的方式。

首先,将 getLoadContext 传递给 Vite 配置中的 Cloudflare Proxy 插件,以在运行 Vite 时增强加载上下文:

vite.config.ts
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { getLoadContext } from "./load-context";
export default defineConfig({
plugins: [
remixCloudflareDevProxy({ getLoadContext }),
remix(),
],
});

接下来,将 getLoadContext 传递给 functions/[[path]].ts 文件中的请求处理程序,以在运行 Wrangler 或部署到 Cloudflare Pages 时增强加载上下文:

functions/[[path]].ts
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// @ts-ignore - the server build file is generated by `remix vite:build`
import * as build from "../build/server";
import { getLoadContext } from "../load-context";
export const onRequest = createPagesFunctionHandler({
build,
getLoadContext,
});

拆分客户端和服务器代码

Vite 处理客户端和服务器代码混合使用的方式与 Classic Remix 编译器不同。有关更多信息,请参阅我们关于 [拆分客户端和服务器代码] [拆分客户端和服务器代码] 的文档。

新的构建输出路径

Vite 管理public目录的方式与现有的 Remix 编译器有显著差异。Vite 将文件从public目录复制到客户端构建目录,而 Remix 编译器则保持public目录不变,并使用子目录(public/build)作为客户端构建目录。

为了使默认的 Remix 项目结构与 Vite 的工作方式保持一致,构建输出路径已更改。现在有一个默认为build的单个buildDirectory选项,取代了单独的assetsBuildDirectoryserverBuildDirectory选项。这意味着,默认情况下,服务器现在编译为build/server,客户端现在编译为build/client

这也意味着以下配置默认值已被更改:

  • publicPath 已被 Vite 的base选项 取代,默认为 "/" 而不是 "/build/"
  • serverBuildPath 已被 serverBuildFile 取代,默认为 "index.js"。此文件将写入您配置的 buildDirectory 中的服务器目录。

Remix 转向 Vite 的原因之一是,这样您在采用 Remix 时需要学习的内容就会减少。 这意味着,对于您想要使用的任何其他捆绑功能,您应该参考 Vite 文档Vite 插件社区,而不是 Remix 文档。

Vite 有许多现有 Remix 编译器未内置的 功能插件。 使用任何此类功能都将导致现有 Remix 编译器无法编译您的应用,因此,只有您打算从现在开始专门使用 Vite 时才使用它们。

迁移

设置 Vite

👉 安装 Vite 作为开发依赖项

Terminal window
npm install -D vite

Remix 现在只是一个 Vite 插件,所以您需要将它连接到 Vite。

👉 在 Remix 应用程序的根目录中将 remix.config.js 替换为 vite.config.ts

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remix()],
});

[支持的 Remix 配置选项] vite-config 的子集应直接传递给插件:

vite.config.ts
export default defineConfig({
plugins: [
remix({
ignoredRouteFiles: ["**/*.css"],
}),
],
});

HMR 和 HDR

Vite 为 HMR 等开发功能提供了强大的客户端运行时,

从而淘汰了 <LiveReload /> 组件。在开发中使用 Remix Vite 插件时, <Scripts /> 组件将自动包含 Vite 的客户端运行时和其他仅供开发使用的脚本。

👉 删除 <LiveReload/>,保留 <Scripts />

import {
LiveReload,
Outlet,
Scripts,
}
export default function App() {
return (
<html>
<head>
</head>
<body>
<Outlet />
<LiveReload />
<Scripts />
</body>
</html>
)
}

TypeScript 集成

Vite 处理各种不同文件类型的导入,有时方式与现有的 Remix 编译器不同,因此让我们从vite/client引用 Vite 的类型,而不是从@remix-run/dev引用过时的类型。

由于 vite/client 提供的模块类型与 @remix-run/dev 隐式包含的模块类型不兼容,因此您还需要在 TypeScript 配置中启用 skipLibCheck 标志。一旦 Vite 插件成为默认编译器,Remix 将来将不再需要此标志。

👉 更新 tsconfig.json

更新tsconfig.json中的types字段,并确保skipLibCheckmodulemoduleResolution均设置正确。

tsconfig.json
{
"compilerOptions": {
"types": ["@remix-run/node", "vite/client"],
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Bundler"
}
}

👉 更新/删除 remix.env.d.ts

删除 remix.env.d.ts 中的以下类型声明

remix.env.d.ts
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node" />

如果 remix.env.d.ts 现在为空,请将其删除

Terminal window
rm remix.env.d.ts

从 Remix 应用服务器迁移

如果您在开发中使用remix-serve(或不带-c标志的remix dev),则需要切换到新的最小开发服务器。 它内置于 Remix Vite 插件中,并将在您运行remix vite:dev时接管。

Remix Vite 插件不会安装任何 全局 Node polyfill,因此如果您依赖 remix-serve 来提供它们,则需要自行安装。最简单的方法是在 Vite 配置顶部调用 installGlobals

Vite 开发服务器的默认端口与remix-serve不同,因此如果您想维护相同的端口,则需要通过 Vite 的server.port选项进行配置。

您还需要更新到新的构建输出路径,即服务器的build/server和客户端资产的build/client

👉 更新你的 devbuildstart 脚本

package.json
{
"scripts": {
"dev": "remix vite:dev",
"build": "remix vite:build",
"start": "remix-serve ./build/server/index.js"
}
}

👉 在你的 Vite 配置中安装全局 Node polyfill

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { installGlobals } from "@remix-run/node";
import { defineConfig } from "vite";
installGlobals();
export default defineConfig({
plugins: [remix()],
});

👉 配置你的 Vite 开发服务器端口(可选)

vite.config.ts
export default defineConfig({
server: {
port: 3000,
},
plugins: [remix()],
});

迁移自定义服务器

如果您在开发中使用自定义服务器,则需要编辑自定义服务器以使用 Vite 的 connect 中间件。 这会在开发过程中将资产请求和初始渲染请求委托给 Vite,让您即使使用自定义服务器也能从 Vite 出色的 DX 中受益。

然后,您可以在开发过程中加载名为virtual:remix / server-build的虚拟模块来创建基于 Vite 的请求处理程序。

您还需要更新服务器代码以引用新的构建输出路径,即服务器构建的build/server和客户端资产的build/client

例如,如果您使用 Express,可以按如下方式操作。

👉 更新你的 server.mjs 文件

server.mjs
import { createRequestHandler } from "@remix-run/express";
import { installGlobals } from "@remix-run/node";
import express from "express";
installGlobals();
const viteDevServer =
process.env.NODE_ENV === "production"
? undefined
: await import("vite").then((vite) =>
vite.createServer({
server: { middlewareMode: true },
})
);
const app = express();
// handle asset requests
if (viteDevServer) {
app.use(viteDevServer.middlewares);
} else {
app.use(
"/assets",
express.static("build/client/assets", {
immutable: true,
maxAge: "1y",
})
);
}
app.use(express.static("build/client", { maxAge: "1h" }));
// handle SSR requests
app.all(
"*",
createRequestHandler({
build: viteDevServer
? () =>
viteDevServer.ssrLoadModule(
"virtual:remix/server-build"
)
: await import("./build/server/index.js"),
})
);
const port = 3000;
app.listen(port, () =>
console.log("http://localhost:" + port)
);

👉 更新你的 builddevstart 脚本

package.json
{
"scripts": {
"dev": "node ./server.mjs",
"build": "remix vite:build",
"start": "cross-env NODE_ENV=production node ./server.mjs"
}
}

如果您愿意,您也可以使用 TypeScript 编写自定义服务器。 然后,您可以使用 tsxtsm 等工具来运行自定义服务器:

Terminal window
tsx ./server.ts
node --loader tsm ./server.ts

请记住,如果您这样做,初始服务器启动时速度可能会明显减慢。

迁移 Cloudflare 函数

<文档警告>

Remix Vite 插件仅正式支持 Cloudflare Pages,它专为全栈应用程序而设计,与 Cloudflare Workers Sites 不同。如果您当前正在使用 Cloudflare Workers Sites,请参阅 Cloudflare Pages 迁移指南

👉 在 remix 插件之前添加 cloudflareDevProxyVitePlugin,以正确覆盖 vite dev 服务器的中间件!

vite.config.ts
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin,
} from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [cloudflareDevProxyVitePlugin(), remix()],
});

您的 Cloudflare 应用可能正在设置 [Remix Config server 字段] remix-config-server 以生成一个 catch-all Cloudflare 函数。 使用 Vite,这种间接操作不再必要。 相反,您可以直接为 Cloudflare 编写一个 catch-all 路由,就像您为 Express 或任何其他自定义服务器编写路由一样。

👉 为 Remix 创建一个万能路由

functions/[[page]].ts
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// @ts-ignore - the server build file is generated by `remix vite:build`
import * as build from "../build/server";
export const onRequest = createPagesFunctionHandler({
build,
});

👉 通过 context.cloudflare.env 而不是 context.env 访问绑定和环境变量

虽然您在开发过程中主要使用 Vite,但您也可以使用 Wrangler 来预览和部署您的应用程序。

要了解更多信息,请参阅本文档的 Cloudflare 部分。

👉 更新你的 package.json 脚本

package.json
{
"scripts": {
"dev": "remix vite:dev",
"build": "remix vite:build",
"preview": "wrangler pages dev ./build/client",
"deploy": "wrangler pages deploy ./build/client"
}
}

迁移引用以构建输出路径

使用现有 Remix 编译器的默认选项时,服务器被编译为 build,客户端被编译为 public/build。由于 Vite 通常使用其 public 目录的方式与现有 Remix 编译器不同,这些输出路径已发生更改。

👉 更新参考资料以构建输出路径

  • 服务器现在默认编译到build/server中。
  • 客户端现在默认编译到build/client中。

例如,要从 Blues Stack 更新 Dockerfile:

// Dockerfile
COPY --from=build /myapp/build /myapp/build
COPY --from=build /myapp/public /myapp/public
COPY --from=build /myapp/build/server /myapp/build/server
COPY --from=build /myapp/build/client /myapp/build/client

配置路径别名

Remix 编译器利用tsconfig.json中的paths选项来解析路径别名。这在 Remix 社区中很常见,用于将~定义为app目录的别名。

Vite 默认不提供任何路径别名。如果你依赖此功能,可以安装 vite-tsconfig-paths 插件,以自动解析 Vite 中 tsconfig.json 中的路径别名,以匹配 Remix 编译器的行为:

👉 安装 vite-tsconfig-paths

Terminal window
npm install -D vite-tsconfig-paths

👉 vite-tsconfig-paths 添加到你的 Vite 配置

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [remix(), tsconfigPaths()],
});

删除 @remix-run/css-bundle

Vite 内置了对 CSS 副作用导入、PostCSS 和 CSS 模块以及其他 CSS 捆绑功能的支持。Remix Vite 插件会自动将捆绑的 CSS 附加到相关路由。

使用 Vite 时,@remix-run/css-bundle 包是多余的,因为它的 cssBundleHref 导出将始终为 undefined

👉 卸载 @remix-run/css-bundle

Terminal window
npm uninstall @remix-run/css-bundle

👉 删除对 cssBundleHref 的引用

app/root.tsx
import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
export const links: LinksFunction = () => [
...(cssBundleHref
? [{ rel: "stylesheet", href: cssBundleHref }]
: []),
// ...
];

如果路由的链接功能仅用于连接cssBundleHref,则可以将其完全删除。

app/root.tsx
import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
export const links: LinksFunction = () => [
...(cssBundleHref
? [{ rel: "stylesheet", href: cssBundleHref }]
: []),
];

这对于其他形式的 CSS 捆绑 不是必需的,例如 CSS 模块、CSS 副作用导入、Vanilla Extract 等。

如果您 [在 links 函数中引用 CSS] regular-css,则需要更新相应的 CSS 导入以使用 [Vite 的显式 ?url 导入语法。] vite-url-imports

👉 links 中使用的 CSS 导入中添加 ?url

.css?url 导入需要 Vite v5.1 或更新版本

import styles from "~/styles/dashboard.css";
import styles from "~/styles/dashboard.css?url";
export const links = () => {
return [
{ rel: "stylesheet", href: styles }
];
}

通过 PostCSS 启用 Tailwind

如果您的项目正在使用 Tailwind CSS,您首先需要确保您有一个 PostCSS 配置文件,它将被 Vite 自动获取。 这是因为当启用 Remix 的 tailwind 选项时,Remix 编译器不需要 PostCSS 配置文件。

👉 如果缺少,请添加 PostCSS 配置,包括 tailwindcss 插件

postcss.config.mjs
export default {
plugins: {
tailwindcss: {},
},
};

如果您的项目已经有 PostCSS 配置文件,则需要添加 tailwindcss 插件(如果尚不存在)。 这是因为当 Remix 的 tailwind 配置选项 启用时,Remix 编译器会自动包含此插件。

👉 如果缺少 tailwindcss 插件,请将其添加到你的 PostCSS 配置中

postcss.config.mjs
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

👉 迁移 Tailwind CSS 导入

如果您 [在 links 函数中引用 Tailwind CSS 文件] regular-css,则需要 [迁移 Tailwind CSS 导入语句。] fix-up-css-imports-referenced-in-links

添加 Vanilla Extract 插件

如果您使用 Vanilla Extract,则需要设置 Vite 插件。

👉 安装官方 Vite 的 Vanilla Extract 插件

Terminal window
npm install -D @vanilla-extract/vite-plugin

👉 将 Vanilla Extract 插件添加到你的 Vite 配置

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remix(), vanillaExtractPlugin()],
});

添加 MDX 插件

如果你使用 MDX,由于 Vite 的插件 API 是 Rollup 插件 API 的扩展,因此你应该使用官方的 MDX Rollup 插件

👉 安装 MDX Rollup 插件

Terminal window
npm install -D @mdx-js/rollup

<文档信息>

Remix 插件需要处理 JavaScript 或 TypeScript 文件,因此必须先从其他语言(如 MDX)进行任何转译。 在这种情况下,这意味着将 MDX 插件放在 Remix 插件之前。

👉 将 MDX Rollup 插件添加到你的 Vite 配置

vite.config.ts
import mdx from "@mdx-js/rollup";
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [mdx(), remix()],
});
添加 MDX 前置内容支持

Remix 编译器允许您定义 MDX 中的 frontmatter。如果您使用此功能,则可以使用 remark-mdx-frontmatter 在 Vite 中实现此目的。

👉 安装所需的 Remark frontmatter 插件

Terminal window
npm install -D remark-frontmatter remark-mdx-frontmatter

👉 将 Remark frontmatter 插件传递给 MDX Rollup 插件

vite.config.ts
import mdx from "@mdx-js/rollup";
import { vitePlugin as remix } from "@remix-run/dev";
import remarkFrontmatter from "remark-frontmatter";
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
mdx({
remarkPlugins: [
remarkFrontmatter,
remarkMdxFrontmatter,
],
}),
remix(),
],
});

在 Remix 编译器中,frontmatter 导出名为attributes。这与 frontmatter 插件的默认导出名称frontmatter不同。虽然可以配置 frontmatter 导出名称,但我们建议更新您的应用代码以使用默认导出名称。

👉 将 MDX 文件中的 MDX attributes 导出重命名为 frontmatter

app/posts/first-post.mdx
---
title: Hello, World!
---
# {attributes.title}
# {frontmatter.title}

👉 将 MDX attributes 导出重命名为 frontmatter 以供消费者使用

app/routes/posts/first-post.tsx
import Component, {
attributes,
frontmatter,
} from "./posts/first-post.mdx";
定义 MDX 文件的类型

👉 *.mdx 文件的类型添加到 env.d.ts

env.d.ts
/// <reference types="@remix-run/node" />
/// <reference types="vite/client" />
declare module "*.mdx" {
let MDXComponent: (props: any) => JSX.Element;
export const frontmatter: any;
export default MDXComponent;
}
将 MDX 前置内容映射到路由导出

Remix 编译器允许您在 frontmatter 中定义 headersmetahandle 路由导出。这个 Remix 特有的功能显然不受 remark-mdx-frontmatter 插件支持。如果您使用此功能,则应手动将 frontmatter 映射到路由导出:

👉 将前言映射到 MDX 路由的路由出口

---
meta:
- title: My First Post
- name: description
content: Isn't this awesome?
headers:
Cache-Control: no-cache
---
export const meta = frontmatter.meta;
export const headers = frontmatter.headers;
# Hello World

请注意,由于您明确映射了 MDX 路由导出,因此您现在可以自由使用您喜欢的任何前置结构。

---
title: My First Post
description: Isn't this awesome?
---
export const meta = () => {
return [
{ title: frontmatter.title },
{
name: "description",
content: frontmatter.description,
},
];
};
# Hello World
更新 MDX 文件名用法

Remix 编译器还提供了从所有 MDX 文件中导出filename的功能。这主要是为了能够链接到 MDX 路由集合。如果您使用此功能,您可以在 Vite 中通过 glob imports 实现此功能,它为您提供了一个将文件名映射到模块的便捷数据结构。这使得维护 MDX 文件列表变得更加容易,因为您不再需要手动导入每个文件。

例如,要导入posts目录中的所有 MDX 文件:

const posts = import.meta.glob("./posts/*.mdx");

这相当于手写:

const posts = {
"./posts/a.mdx": () => import("./posts/a.mdx"),
"./posts/b.mdx": () => import("./posts/b.mdx"),
"./posts/c.mdx": () => import("./posts/c.mdx"),
// etc.
};

如果愿意,您还可以立即导入所有 MDX 文件:

const posts = import.meta.glob("./posts/*.mdx", {
eager: true,
});

调试

您可以使用 [NODE_OPTIONS 环境变量] node-options 来启动调试会话:

Terminal window
NODE_OPTIONS="--inspect-brk" npm run dev

然后,您可以从浏览器中附加调试器。 例如,在 Chrome 中,您可以打开chrome://inspect或单击开发工具中的 NodeJS 图标来附加调试器。

vite-插件-检查

vite-plugin-inspect 向您展示每个 Vite 插件如何转换您的代码以及每个插件需要多长时间。

表现

Remix 包含一个用于性能分析的 --profile 标志。

Terminal window
remix vite:build --profile

当使用--profile运行时,将生成一个.cpuprofile文件,可以共享或上传到speedscope.app进行分析。

您还可以在开发服务器运行时按p + enter来在开发中进行分析,以启动新的分析会话或停止当前会话。 如果您需要分析开发服务器启动,您还可以使用--profile标志在启动时初始化分析会话:

Terminal window
remix vite:dev --profile

请记住,您可以随时查看 Vite 性能文档 获取更多提示!

捆绑分析

要可视化和分析你的包,你可以使用 rollup-plugin-visualizer 插件:

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
remix(),
// `emitFile` is necessary since Remix builds more than one bundle!
visualizer({ emitFile: true }),
],
});

然后,当你运行 remix vite:build 时,它会在每个包中生成一个 stats.html 文件:

build
├── client
│ ├── assets/
│ ├── favicon.ico
│ └── stats.html 👈
└── server
├── index.js
└── stats.html 👈

在浏览器中打开stats.html来分析您的捆绑包。

故障排除

查看 [调试][调试] 和 [性能][性能] 部分,了解常规故障排除技巧。 此外,查看 github 上 remix vite 插件的已知问题,看看是否有其他人遇到类似问题。

高温热释光

如果您期待热更新但却得到整个页面重新加载, 请查看我们的关于热模块替换的讨论,以了解有关 React Fast Refresh 的局限性以及常见问题的解决方法的更多信息。

ESM/CJS

Vite 同时支持 ESM 和 CJS 依赖项,但有时您仍可能会遇到 ESM / CJS 互操作问题。 通常,这是因为依赖项未正确配置为支持 ESM。 我们不会责怪他们,正确支持 ESM 和 CJS 真的很棘手

有关修复示例错误的演练,请查看🎥 如何修复 Remix 中的 CJS/ESM 错误

要诊断您的某个依赖项是否配置错误,请检查 publint类型是否错误。 此外,您可以使用 vite-plugin-cjs-interop 插件 解决外部 CJS 依赖项的默认导出问题。

最后,您还可以明确配置要将哪些依赖项捆绑到使用 Vite 的 ssr.noExternal 选项 捆绑的服务器中,以使用 Remix Vite 插件模拟 Remix 编译器的 serverDependenciesToBundle

开发过程中浏览器中的服务器代码错误

如果您在开发过程中在浏览器控制台中看到指向服务器代码的错误,则可能需要明确隔离仅限服务器的代码。 例如,如果您看到类似以下内容:

Terminal window
Uncaught ReferenceError: process is not defined

然后,您需要追踪哪个模块正在引入除服务器专用全局变量(如process)之外的依赖项,并在 单独的 .server 模块或使用 vite-env-only 中隔离代码。 由于 Vite 使用 Rollup 在生产环境中对您的代码进行树形调整,因此这些错误只会在开发过程中发生。

与其他基于 Vite 的工具一起使用插件(例如 Vitest、Storybook)

Remix Vite 插件仅适用于应用程序的开发服务器和生产版本。 虽然还有其他基于 Vite 的工具(例如 Vitest 和 Storybook)使用 Vite 配置文件,但 Remix Vite 插件并非设计用于这些工具。 我们目前建议在与其他基于 Vite 的工具一起使用时排除该插件。

对于 Vitest:

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig, loadEnv } from "vite";
export default defineConfig({
plugins: [!process.env.VITEST && remix()],
test: {
environment: "happy-dom",
// Additionally, this is to load ".env.test" during vitest
env: loadEnv("test", process.cwd(), ""),
},
});

对于故事书:

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
const isStorybook = process.argv[1]?.includes("storybook");
export default defineConfig({
plugins: [!isStorybook && remix()],
});

或者,您可以为每个工具使用单独的 Vite 配置文件。 例如,要使用专门针对 Remix 的 Vite 配置:

Terminal window
remix vite:dev --config vite.config.remix.ts

当不提供 Remix Vite 插件时,你的设置可能还需要提供 Vite Plugin React。例如,当使用 Vitest 时:

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import react from "@vitejs/plugin-react";
import { defineConfig, loadEnv } from "vite";
export default defineConfig({
plugins: [!process.env.VITEST ? remix() : react()],
test: {
environment: "happy-dom",
// Additionally, this is to load ".env.test" during vitest
env: loadEnv("test", process.cwd(), ""),
},
});

文档重新安装时样式在开发中消失

当使用 React 渲染整个文档(如 Remix 所做的那样)时,您可能会在将元素动态注入head元素时遇到问题。如果重新安装文档,现有的head元素将被删除并替换为一个全新的元素,从而删除 Vite 在开发过程中注入的所有style元素。

这是一个已知的 React 问题,已在其 canary 发布渠道 中修复。如果您了解所涉及的风险,您可以将您的应用固定到特定的 React 版本,然后使用 package overrides 确保这是整个项目中使用的唯一 React 版本。例如:

package.json
{
"dependencies": {
"react": "18.3.0-canary-...",
"react-dom": "18.3.0-canary-..."
},
"overrides": {
"react": "18.3.0-canary-...",
"react-dom": "18.3.0-canary-..."
}
}

作为参考,这是 Next.js 代表您在内部处理 React 版本控制的方式,因此这种方法的使用比您想象的更为广泛,即使它不是 Remix 默认提供的功能。

值得强调的是,Vite 注入的样式问题仅发生在开发阶段。生产版本不会出现此问题,因为会生成静态 CSS 文件。

在 Remix 中,当 [根路由的默认组件导出] route-component 与其 [ErrorBoundary] error-boundary 和/或 [HydrateFallback] hydrate-fallback 导出之间交替渲染时,可能会出现此问题,因为这会导致安装新的文档级组件。

由于 Hydration 错误,React 会从头开始重新渲染整个页面,因此也可能发生这种情况。Hydration 错误可能是由您的应用代码引起的,也可能是由操纵文档的浏览器扩展引起的。

这与 Vite 息息相关,因为在开发过程中,Vite 会将 CSS 导入转换为 JS 文件,并将其样式注入到文档中,这会产生副作用。Vite 这样做是为了支持静态 CSS 文件的延迟加载和 HMR。

例如,假设您的应用具有以下 CSS 文件:

* { 边距:0 }

在开发过程中,此 CSS 文件在导入时将作为副作用转换为以下 JavaScript 代码:

import {createHotContext as __vite__createHotContext} from "/@vite/client";
import.meta.hot = __vite__createHotContext("/app/styles.css");
import {updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle} from "/@vite/client";
const __vite__id = "/path/to/app/styles.css";
const __vite__css = "*{margin:0}"
__vite__updateStyle(__vite__id, __vite__css);
import.meta.hot.accept();
import.meta.hot.prune(()=>__vite__removeStyle(__vite__id));

这种转换不适用于生产代码,这就是为什么这个样式问题只影响开发。

Wrangler 开发中的错误

使用 Cloudflare Pages 时,您可能会遇到来自 wrangler pages dev 的以下错误:

ERROR: Your worker called response.clone(), but did not read the body of both clones.
This is wasteful, as it forces the system to buffer the entire response body
in memory, rather than streaming it through. This may cause your worker to be
unexpectedly terminated for going over the memory limit. If you only meant to
copy the response headers and metadata (e.g. in order to be able to modify
them), use `new Response(response.body, response)` instead.

这是 [Wrangler 的已知问题] cloudflare-request-clone-errors

致谢

Vite 是一个了不起的项目,我们非常感谢 Vite 团队所做的工作。 特别感谢 Vite 团队的 Matias Capeletto、Arnaud Barré 和 Bjorn Lu 的指导。

Remix 社区很快就探索了 Vite 支持,我们感谢他们的贡献:

最后,我们受到了其他框架实现 Vite 支持的启发: