¥Server vs. Client Code Execution
Remix 既可以在服务器上运行你的应用,也可以在浏览器中运行。但是,它不会在两个地方运行你的所有代码。
¥Remix runs your app on the server as well as in the browser. However, it doesn't run all of your code in both places.
在构建步骤中,编译器会同时创建服务器构建和客户端构建。服务器构建将所有内容打包成一个模块(使用 服务器包 时则打包成多个模块),但客户端构建会将你的应用拆分成多个包,以优化浏览器的加载速度。它还会从 bundles 中删除服务器代码。
¥During the build step, the compiler creates both a server build and a client build. The server build bundles everything up into a single module (or multiple modules when using server bundles), but the client build splits your app up into multiple bundles to optimize loading in the browser. It also removes server code from the bundles.
以下路由导出及其中使用的依赖已从客户端构建中移除:
¥The following route exports and the dependencies used within them are removed from the client build:
假设上一节中的这个路由模块:
¥Consider this route module from the last section:
import type {
ActionFunctionArgs,
HeadersFunction,
LoaderFunctionArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";
import { getUser, updateUser } from "../user";
export const headers: HeadersFunction = () => ({
"Cache-Control": "max-age=300, s-maxage=3600",
});
export async function loader({
request,
}: LoaderFunctionArgs) {
const user = await getUser(request);
return json({
displayName: user.displayName,
email: user.email,
});
}
export default function Component() {
const user = useLoaderData<typeof loader>();
return (
<Form action="/account">
<h1>Settings for {user.displayName}</h1>
<input
name="displayName"
defaultValue={user.displayName}
/>
<input name="email" defaultValue={user.email} />
<button type="submit">Save</button>
</Form>
);
}
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const user = await getUser(request);
await updateUser(user.id, {
email: formData.get("email"),
displayName: formData.get("displayName"),
});
return json({ ok: true });
}
服务器构建将在最终包中包含整个模块。然而,客户端构建将删除 action
、headers
和 loader
及其依赖,导致:
¥The server build will contain the entire module in the final bundle. However, the client build will remove the action
, headers
and loader
, along with the dependencies, resulting in this:
import { useLoaderData } from "@remix-run/react";
export default function Component() {
const user = useLoaderData();
return (
<Form action="/account">
<h1>Settings for {user.displayName}</h1>
<input
name="displayName"
defaultValue={user.displayName}
/>
<input name="email" defaultValue={user.email} />
<button type="submit">Save</button>
</Form>
);
}
¥Splitting Up Client and Server Code
开箱即用,Vite 不支持在同一模块中混合使用纯服务端代码和客户端安全代码。Remix 能够为路由设置例外,因为我们知道哪些导出是仅用于服务器的,并且可以将其从客户端移除。
¥Out of the box, Vite doesn't support mixing server-only code with client-safe code in the same module. Remix is able to make an exception for routes because we know which exports are server-only and can remove them from the client.
有几种方法可以在 Remix 中隔离仅服务端的代码。最简单的方法是使用 .server
和 .client
模块。
¥There are a few ways to isolate server-only code in Remix.
The simplest approach is to use .server
and .client
modules.
.server
模块¥.server
modules
虽然并非绝对必要,但 .server
模块 模块是将整个模块明确标记为仅服务端的好方法。如果 .server
文件或 .server
目录中的任何代码意外地出现在客户端模块图中,构建将失败。
¥While not strictly necessary, .server
modules are a good way to explicitly mark entire modules as server-only.
The build will fail if any code in a .server
file or .server
directory accidentally ends up in the client module graph.
app
├── .server 👈 marks all files in this directory as server-only
│ ├── auth.ts
│ └── db.ts
├── cms.server.ts 👈 marks this file as server-only
├── root.tsx
└── routes
└── _index.tsx
.server
模块必须位于你的 Remix 应用目录中。
¥.server
modules must be within your Remix app directory.
.server
目录仅在使用 Remix Vite 时受支持。Classic Remix 编译器 仅支持 .server
文件。
.client
模块¥.client
modules
你可能依赖的客户端库甚至可能不安全地打包到服务器上 - 它可能只是通过导入就试图访问 window
。
¥You may depend on client libraries that are unsafe to even bundle on the server — maybe it tries to access window
by simply being imported.
你可以通过在文件名后附加 *.client.ts
或将它们嵌套在 .client
目录中,从服务器构建中删除这些模块的内容。
¥You can remove the contents of these modules from the server build by appending *.client.ts
to the file name or nesting them within a .client
directory.
.client
目录仅在使用 Remix Vite 时受支持。Classic Remix 编译器 仅支持 .client
文件。
如果你想在同一个模块中混合使用服务器端代码和客户端安全代码,可以使用 undefined
。
¥If you want to mix server-only code and client-safe code in the same module, you
can use undefined
in the client.
例如,将插件添加到 Vite 配置 后,你可以使用 serverOnly$
封装任何仅限服务器的导出:
¥For example, once you've added the plugin to your Vite config, you can wrap any server-only exports with serverOnly$
:
import { serverOnly$ } from "vite-env-only";
import { db } from "~/.server/db";
export const getPosts = serverOnly$(async () => {
return db.posts.findMany();
});
export const PostPreview = ({ title, description }) => {
return (
<article>
<h2>{title}</h2>
<p>{description}</p>
</article>
);
};
本示例将被编译为客户端的以下代码:
¥This example would be compiled into the following code for the client:
export const getPosts = undefined;
export const PostPreview = ({ title, description }) => {
return (
<article>
<h2>{title}</h2>
<p>{description}</p>
</article>
);
};