服务器与客户端代码执行

服务器与客户端代码执行

¥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 });
}

服务器构建将在最终包中包含整个模块。然而,客户端构建将删除 actionheadersloader 及其依赖,导致:

¥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 文件。

vite-env-only

如果你想在同一个模块中混合使用服务器端代码和客户端安全代码,可以使用 vite-env-only。此 Vite 插件允许你将任何表达式明确标记为仅服务器表达式,以便在客户端将其替换为 undefined

¥If you want to mix server-only code and client-safe code in the same module, you can use vite-env-only. This Vite plugin allows you to explicitly mark any expression as server-only so that it gets replaced with 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>
  );
};
Remix v2.17 中文网 - 粤ICP备13048890号