Vite 是一个功能强大、性能卓越且可扩展的 JavaScript 项目开发环境。为了改进和扩展 Remix 的打包功能,我们现在支持 Vite 作为替代编译器。未来,Vite 将成为 Remix 的默认编译器。
现有的 Remix 编译器(通过 remix build
和 remix dev
CLI 命令访问,并通过 remix.config.js
配置)现在被称为“经典 Remix 编译器”。
Remix Vite 插件以及 remix vite:build
和 remix vite:dev
CLI 命令统称为“Remix Vite”。
未来,除非另有说明,文档将假设使用 Remix Vite。
我们提供了一些基于 Vite 的模板来帮助你入门。
# 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
模板
npx create-remix@latest --template remix-run/remix/templates/cloudflare
有两种方法可以在本地运行 Cloudflare 应用
# Vite
remix vite:dev
# Wrangler
remix vite:build # build app before running wrangler
wrangler pages dev ./build/client
虽然 Vite 提供了更好的开发体验,但 Wrangler 通过在 Cloudflare 的 workerd
运行时 而不是 Node 中运行服务器代码,提供了更接近 Cloudflare 环境的模拟。
为了在 Vite 中模拟 Cloudflare 环境,Wrangler 提供了 指向本地 workerd
绑定的 Node 代理。Remix 的 Cloudflare 代理插件为你设置了这些代理
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remixCloudflareDevProxy(), remix()],
});
然后,这些代理可以在你的 loader
或 action
函数中的 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
访问你的绑定。例如,使用绑定为 MY_KV
的 KV 命名空间
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 中的加载上下文都以相同的方式扩展**
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
传递给 functions/[[path]].ts
中的 Cloudflare 代理插件和请求处理程序**两者**,否则,根据你运行应用程序的方式,你将获得不一致的加载上下文扩展。
首先,将 getLoadContext
传递给 Vite 配置中的 Cloudflare 代理插件,以便在运行 Vite 时扩展加载上下文
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 时扩展加载上下文
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 处理客户端和服务器代码的混合使用方式与经典 Remix 编译器不同。有关更多信息,请参阅我们关于 拆分客户端和服务器代码 的文档。
Vite 管理 public
目录的方式与现有的 Remix 编译器相比存在显著差异。Vite 将 public
目录中的文件复制到客户端构建目录中,而 Remix 编译器则保留了 public
目录,并使用子目录 (public/build
) 作为客户端构建目录。
为了使默认的 Remix 项目结构与 Vite 的工作方式保持一致,构建输出路径已更改。现在有一个名为 buildDirectory
的选项,默认为 "build"
,取代了单独的 assetsBuildDirectory
和 serverBuildDirectory
选项。这意味着,默认情况下,服务器现在编译到 build/server
中,客户端现在编译到 build/client
中。
这也意味着以下配置默认值已更改
"/"
而不是 "/build/"
。serverBuildFile
替换,默认为 "index.js"
。此文件将写入你配置的 buildDirectory
中的服务器目录。Remix 迁移到 Vite 的原因之一是为了简化 Remix 的学习曲线。这意味着,如果您想使用任何额外的打包功能,应该参考 Vite 文档 和 Vite 插件社区,而不是 Remix 文档。
Vite 有许多 功能 和 插件,这些功能和插件没有内置到现有的 Remix 编译器中。使用任何此类功能将导致现有的 Remix 编译器无法编译您的应用程序,因此,只有在您打算从现在开始完全使用 Vite 时才使用它们。
👉 安装 Vite 作为开发依赖项
npm install -D vite
Remix 现在只是一个 Vite 插件,因此您需要将其连接到 Vite。
👉 将 remix.config.js
替换为 Remix 应用根目录下的 vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remix()],
});
一部分 受支持的 Remix 配置选项 应该直接传递给插件
export default defineConfig({
plugins: [
remix({
ignoredRouteFiles: ["**/*.css"],
}),
],
});
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>
)
}
Vite 处理各种不同文件类型的导入,有时与现有的 Remix 编译器的方式不同,因此,让我们从 vite/client
中引用 Vite 的类型,而不是从 @remix-run/dev
中引用已过时的类型。
由于 vite/client
提供的模块类型与 @remix-run/dev
隐式包含的模块类型不兼容,因此您还需要在 TypeScript 配置中启用 skipLibCheck
标志。一旦 Vite 插件成为默认编译器,Remix 将在未来不再需要此标志。
👉 更新 tsconfig.json
更新 tsconfig.json
中的 types
字段,并确保 skipLibCheck
、module
和 moduleResolution
都正确设置。
{
"compilerOptions": {
"types": ["@remix-run/node", "vite/client"],
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
👉 更新/删除 remix.env.d.ts
删除 remix.env.d.ts
中以下类型声明
- /// <reference types="@remix-run/dev" />
- /// <reference types="@remix-run/node" />
如果 remix.env.d.ts
现在为空,则删除它
rm remix.env.d.ts
如果您在开发中使用 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
。
👉 更新您的 dev
、build
和 start
脚本
{
"scripts": {
"dev": "remix vite:dev",
"build": "remix vite:build",
"start": "remix-serve ./build/server/index.js"
}
}
👉 在您的 Vite 配置中安装全局 Node polyfill
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 开发服务器端口(可选)
export default defineConfig({
server: {
port: 3000,
},
plugins: [remix()],
});
如果您在开发中使用自定义服务器,则需要编辑您的自定义服务器以使用 Vite 的 connect
中间件。这将在开发期间将资源请求和初始渲染请求委托给 Vite,使您即使使用自定义服务器也能从 Vite 优秀的开发体验中获益。
然后,您可以在开发期间加载名为 "virtual:remix/server-build"
的虚拟模块以创建基于 Vite 的请求处理程序。
您还需要更新服务器代码以引用新的构建输出路径,服务器构建为 build/server
,客户端资源为 build/client
。
例如,如果您使用的是 Express,则可以按以下方式操作。
👉 更新您的 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("https://127.0.0.1:" + port)
);
👉 更新您的 build
、dev
和 start
脚本
{
"scripts": {
"dev": "node ./server.mjs",
"build": "remix vite:build",
"start": "cross-env NODE_ENV=production node ./server.mjs"
}
}
如果您愿意,也可以使用 TypeScript 编写您的自定义服务器。然后,您可以使用 tsx
或 tsm
等工具来运行您的自定义服务器
tsx ./server.ts
node --loader tsm ./server.ts
请记住,如果您这样做,服务器初始启动可能会有一些明显的延迟。
Remix Vite 插件仅官方支持 Cloudflare Pages,它专为全栈应用程序设计,与 Cloudflare Workers Sites 不同。如果您目前使用 Cloudflare Workers Sites,请参考 Cloudflare Pages 迁移指南。
👉 将 cloudflareDevProxyVitePlugin
插件放在 remix
插件之前,以正确覆盖 Vite 开发服务器的中件件!
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin,
} from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [cloudflareDevProxyVitePlugin(), remix()],
});
您的 Cloudflare 应用可能会设置 Remix 配置的 server
字段 以生成一个通配符 Cloudflare 函数。使用 Vite 后,这种间接方式不再需要。相反,您可以直接为 Cloudflare 编写一个通配符路由,就像您为 Express 或任何其他自定义服务器所做的那样。
👉 为 Remix 创建一个通配符路由
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
脚本
{
"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 与现有 Remix 编译器相比,在处理 public
目录的方式上存在差异,因此这些输出路径已更改。
👉 更新对构建输出路径的引用
build/server
中。build/client
中。例如,要更新 Blues Stack 中的 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
npm install -D vite-tsconfig-paths
👉 将 vite-tsconfig-paths
添加到您的 Vite 配置中
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 Modules 等 CSS 打包功能。Remix Vite 插件会自动将打包后的 CSS 附加到相关的路由上。
当使用 Vite 时,@remix-run/css-bundle
cssBundleHref
导出将始终为 undefined
。
👉 卸载 @remix-run/css-bundle
npm uninstall @remix-run/css-bundle
👉 删除对 cssBundleHref
的引用
- 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 }]
- : []),
// ...
];
如果路由的 links
函数仅用于连接 cssBundleHref
,则可以完全删除它。
- 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 }]
- : []),
- ];
links
中引用的 CSS 导入如果您正在 links
函数中引用 CSS,则需要更新相应的 CSS 导入以使用 Vite 的显式 ?url
导入语法。
👉 在 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 }
];
}
如果您的项目正在使用 Tailwind CSS,则首先需要确保您有一个 PostCSS 配置文件,Vite 会自动获取该文件。这是因为当 Remix 的 tailwind
选项启用时,Remix 编译器不需要 PostCSS 配置文件。
👉 如果缺少 PostCSS 配置文件,请添加它,包括 tailwindcss
插件
export default {
plugins: {
tailwindcss: {},
},
};
如果您的项目已经有一个 PostCSS 配置文件,则需要添加 tailwindcss
插件(如果尚未存在)。这是因为 Remix 编译器在 Remix 的 tailwind
配置选项 启用时会自动包含此插件。
👉 如果缺少 tailwindcss
插件,请将其添加到您的 PostCSS 配置文件中
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
👉 迁移 Tailwind CSS 导入
如果您正在 links
函数中引用您的 Tailwind CSS 文件,则需要 迁移您的 Tailwind CSS 导入语句。
如果您正在使用 Vanilla Extract,则需要设置 Vite 插件。
👉 安装官方的 用于 Vite 的 Vanilla Extract 插件
npm install -D @vanilla-extract/vite-plugin
👉 将 Vanilla Extract 插件添加到您的 Vite 配置中
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,由于 Vite 的插件 API 是 Rollup 插件 API 的扩展,因此您应该使用官方的 MDX Rollup 插件
👉 安装 MDX Rollup 插件
npm install -D @mdx-js/rollup
Remix 插件期望处理 JavaScript 或 TypeScript 文件,因此来自其他语言(如 MDX)的任何转译必须首先完成。在这种情况下,这意味着将 MDX 插件放在 Remix 插件之前。
👉 将 MDX Rollup 插件添加到您的 Vite 配置中
import mdx from "@mdx-js/rollup";
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [mdx(), remix()],
});
Remix 编译器允许您在 MDX 中定义 前置 matter。如果您正在使用此功能,则可以使用 remark-mdx-frontmatter 在 Vite 中实现此功能。
👉 安装所需的 Remark 前置 matter 插件
npm install -D remark-frontmatter remark-mdx-frontmatter
👉 将 Remark 前置 matter 插件传递给 MDX Rollup 插件
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 中 attributes
的导出名称重命名为 frontmatter
---
title: Hello, World!
---
- # {attributes.title}
+ # {frontmatter.title}
👉 将 MDX 中 attributes
的导出名称重命名为 frontmatter
以供使用者使用
import Component, {
- attributes,
+ frontmatter,
} from "./posts/first-post.mdx";
👉 为 *.mdx
文件添加类型到 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;
}
Remix 编译器允许您在 frontmatter 中定义 headers
、meta
和 handle
路由导出。这个 Remix 特定的功能显然不受 remark-mdx-frontmatter
插件的支持。如果您正在使用此功能,则应手动将 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 路由导出,因此您现在可以自由使用任何您喜欢的 frontmatter 结构。
---
title: My First Post
description: Isn't this awesome?
---
export const meta = () => {
return [
{ title: frontmatter.title },
{
name: "description",
content: frontmatter.description,
},
];
};
# Hello World
Remix 编译器还从所有 MDX 文件中提供了 filename
导出。这主要旨在支持链接到 MDX 路由的集合。如果您正在使用此功能,则可以通过 全局导入 在 Vite 中实现此功能,全局导入为您提供了一个方便的数据结构,该结构将文件名映射到模块。这使得维护 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="--inspect-brk" npm run dev
然后,您可以从浏览器附加调试器。例如,在 Chrome 中,您可以打开 chrome://inspect
或单击开发者工具中的 NodeJS 图标以附加调试器。
vite-plugin-inspect
显示每个 Vite 插件如何转换您的代码以及每个插件花费多长时间。
Remix 包含一个用于性能分析的 --profile
标志。
remix vite:build --profile
使用 --profile
运行时,将生成一个 .cpuprofile
文件,该文件可以共享或上传到 speedscope.app 进行分析。
您也可以在开发过程中按 p + enter
启动新的分析会话或停止当前会话来进行分析。如果您需要分析开发服务器启动,您也可以使用 --profile
标志在启动时初始化分析会话
remix vite:dev --profile
请记住,您始终可以查看 Vite 性能文档 以获取更多提示!
要可视化和分析您的包,您可以使用 rollup-plugin-visualizer 插件
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 的限制以及常见问题的解决方法。
Vite 支持 ESM 和 CJS 依赖项,但有时您仍然可能会遇到 ESM / CJS 互操作性问题。通常,这是因为依赖项未正确配置以支持 ESM。我们不责怪他们,因为 正确支持 ESM 和 CJS 非常棘手。
有关修复示例错误的分步指南,请查看 🎥 如何修复 Remix 中的 CJS/ESM 错误。
要诊断您的依赖项之一是否配置错误,请检查 publint 或 类型是否错误。此外,您可以使用 vite-plugin-cjs-interop 插件 解决外部 CJS 依赖项的 default
导出问题。
最后,您还可以明确配置要捆绑到服务器中的哪些依赖项,方法是使用 Vite 的 ssr.noExternal
选项 来模拟 Remix 编译器的 serverDependenciesToBundle
和 Remix Vite 插件。
如果您在开发过程中在浏览器控制台中看到指向服务器代码的错误,则可能需要 明确隔离仅限服务器的代码。例如,如果您看到类似以下内容
Uncaught ReferenceError: process is not defined
那么您需要查找哪个模块正在引入期望服务器端全局变量(如 process
)的依赖项,并在 单独的 .server
模块或使用 vite-env-only
中隔离代码。由于 Vite 在生产环境中使用 Rollup 来摇树您的代码,因此这些错误仅在开发环境中出现。
Remix Vite 插件仅用于应用程序的开发服务器和生产构建。虽然还有其他基于 Vite 的工具(例如 Vitest 和 Storybook)使用 Vite 配置文件,但 Remix Vite 插件并非为这些工具而设计。我们目前建议在与其他基于 Vite 的工具一起使用时排除该插件。
对于 Vitest
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(), ""),
},
});
对于 Storybook
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 配置
remix vite:dev --config vite.config.remix.ts
在不提供 Remix Vite 插件的情况下,您的设置可能还需要提供 Vite Plugin React。例如,在使用 Vitest 时
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 问题,已在其 金丝雀发布渠道 中修复。如果您了解所涉及的风险,您可以将您的应用程序固定到特定的 React 版本,然后使用 包覆盖 确保这是整个项目中使用的唯一 React 版本。例如
{
"dependencies": {
"react": "18.3.0-canary-...",
"react-dom": "18.3.0-canary-..."
},
"overrides": {
"react": "18.3.0-canary-...",
"react-dom": "18.3.0-canary-..."
}
}
值得强调的是,Vite 注入的样式出现的问题仅在开发环境中发生。生产构建不会出现此问题,因为会生成静态 CSS 文件。
在 Remix 中,当渲染在 根路由的默认组件导出 与其 ErrorBoundary 和/或 HydrateFallback 导出之间交替时,可能会出现此问题,因为这会导致挂载新的文档级组件。
它也可能由于水合错误而发生,因为它会导致 React 从头开始重新渲染整个页面。水合错误可能是由您的应用程序代码引起的,但也可能是由操纵文档的浏览器扩展引起的。
这与 Vite 相关,因为在开发过程中,Vite 将 CSS 导入转换为 JS 文件,并将它们的样式作为副作用注入文档。Vite 这样做是为了支持静态 CSS 文件的延迟加载和 HMR。
例如,假设您的应用程序具有以下 CSS 文件
* { margin: 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));
此转换不应用于生产代码,这就是为什么此样式问题仅影响开发的原因。
当使用 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 的已知问题。
Vite 是一个很棒的项目,我们感谢 Vite 团队的辛勤工作。特别感谢 Vite 团队的 Matias Capeletto、Arnaud Barré 和 Bjorn Lu 的指导。
Remix 社区迅速探索了 Vite 支持,我们感谢他们的贡献
最后,我们从其他框架如何实现 Vite 支持中获得灵感