未来特性
此页面

未来特性

以下未来特性已经稳定并准备采用。要了解更多关于未来特性的信息,请参阅 开发策略

更新到最新的 v2.x

首先更新到最新的 v2.x 次要版本,以获取最新的未来特性。

👉 更新到最新的 v2

npm install @remix-run/{dev,react,node,etc.}@2

Vite 插件

背景

Remix 不再使用自己的封闭编译器(现在称为“经典编译器”),而是使用 Vite。Vite 是一个功能强大、性能卓越且可扩展的 JavaScript 项目开发环境。 查看 Vite 文档 以获取有关性能、故障排除等更多信息。

虽然这不是一个未来特性,但新的特性和一些特性标记仅在 Vite 插件中可用,并且经典编译器将在 Remix 的下一个版本中删除。

👉 安装 Vite

npm install -D vite

更新你的代码

👉 remix.config.js 替换为 vite.config.ts,位于你的 Remix 应用的根目录下

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"],
    }),
  ],
});

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

  import {
-   LiveReload,
    Outlet,
    Scripts,
  }

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

👉 更新 tsconfig.json

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

{
  "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

配置路径别名

Vite 默认不提供任何路径别名。如果你依赖此功能,例如将 ~ 定义为 app 目录的别名,则可以安装 vite-tsconfig-paths 插件来自动解析 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 模块,以及其他 CSS 捆绑功能。Remix Vite 插件会自动将捆绑的 CSS 附加到相关的路由。

@remix-run/css-bundle包在使用 Vite 时是冗余的,因为它的 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 中引用的 CSS 导入

如果你正在 links 函数中引用 CSS,则需要更新相应的 CSS 导入以使用 Vite 的显式 ?url 导入语法。

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

-import styles from "~/styles/dashboard.css";
+import styles from "~/styles/dashboard.css?url";

export const links = () => {
  return [
    { rel: "stylesheet", href: styles }
  ];
}

迁移 Tailwind CSS 或 Vanilla Extract

如果你正在使用 Tailwind CSS 或 Vanilla Extract,请参阅 完整迁移指南

从 Remix 应用服务器迁移

👉 更新你的 devbuildstart 脚本

{
  "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()],
});

迁移自定义服务器

如果你正在迁移客户服务器或 Cloudflare Functions,请参阅 完整迁移指南

迁移 MDX 路由

如果您正在使用 MDX,则应使用官方的 MDX Rollup 插件。请参阅 完整迁移指南,了解分步演练。

v3_fetcherPersist

背景

fetcher 生命周期现在基于它何时返回空闲状态,而不是其所有者组件卸载时:查看 RFC 以获取更多信息。

👉 启用标志

remix({
  future: {
    v3_fetcherPersist: true,
  },
});

更新你的代码

它不太可能影响您的应用程序。您可能需要检查 useFetchers 的任何用法,因为它们可能比以前持续更长时间。根据您正在执行的操作,您可能比以前渲染更长时间。

v3_relativeSplatPath

背景

更改多段 splat 路径(如 dashboard/*(而不是仅 *))的相对路径匹配和链接。 查看 CHANGELOG 以获取更多信息。

👉 启用标志

remix({
  future: {
    v3_relativeSplatPath: true,
  },
});

更新你的代码

如果您有任何带有路径和 splat 的路由,例如 dashboard.$.tsxroute("dashboard/*"),并在其下方具有相对链接,例如 <Link to="relative"><Link to="../relative">,则需要更新您的代码。

👉 将路由拆分为两个

对于任何 splat 路由,将其拆分为一个布局路由和一个带有 splat 的子路由


└── routes
    ├── _index.tsx
+   ├── dashboard.tsx
    └── dashboard.$.tsx

// or
routes(defineRoutes) {
  return defineRoutes((route) => {
    route("/", "home/route.tsx", { index: true });
-    route("dashboard/*", "dashboard/route.tsx")
+    route("dashboard", "dashboard/layout.tsx", () => {
+      route("*", "dashboard/route.tsx");
    });
  });
},

👉 更新相对链接

更新该路由树中具有相对链接的任何 <Link> 元素,以包含额外的 .. 相对段以继续链接到相同的位置

// dashboard.$.tsx or dashboard/route.tsx
function Dashboard() {
  return (
    <div>
      <h2>Dashboard</h2>
      <nav>
-        <Link to="">Dashboard Home</Link>
-        <Link to="team">Team</Link>
-        <Link to="projects">Projects</Link>
+        <Link to="../">Dashboard Home</Link>
+        <Link to="../team">Team</Link>
+        <Link to="../projects">Projects</Link>
      </nav>
    </div>
  );
}

v3_throwAbortReason

背景

当服务器端请求被中止时,例如当用户在加载程序完成之前离开页面时,Remix 将抛出 request.signal.reason 而不是错误,例如 new Error("query() call aborted...")

👉 启用标志

remix({
  future: {
    v3_throwAbortReason: true,
  },
});

更新你的代码

您可能不需要调整任何代码,除非您在 handleError 内部有自定义逻辑,该逻辑匹配先前的错误消息以将其与其他错误区分开来。

v3_singleFetch

背景

使用此标志,Remix 在应用程序内进行 SPA 导航时,会采用数据请求的“单次获取”方法。在 文档 中提供了其他详细信息,但我们选择采用这种方法的主要原因是简单性。使用单次获取,数据请求现在表现得就像文档请求一样,开发人员不再需要考虑如何在两者之间以不同的方式管理标头、缓存等细微差别。对于更高级的用例,开发人员仍然可以选择细粒度的重新验证。

👉 启用标志(和类型)

import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

declare module "@remix-run/node" {
  // or cloudflare, deno, etc.
  interface Future {
    v3_singleFetch: true;
  }
}

export default defineConfig({
  plugins: [
    remix({
      future: {
        v3_singleFetch: true,
      },
    }),
    tsconfigPaths(),
  ],
});

更新你的代码

启用标志后,您应该能够按原样使用大部分代码,但以下更改应随着时间的推移进行,并且在下一个主要版本之前是必需的。

👉 删除 json()/defer() 以支持原始对象

单次获取开箱即用地支持 JSON 对象和 Promise,因此您可以从 loader/action 函数返回原始数据

-import { json } from "@remix-run/node";

export async function loader({}: LoaderFunctionArgs) {
  let tasks = await fetchTasks();
- return json(tasks);
+ return tasks;
}
-import { defer } from "@remix-run/node";

export async function loader({}: LoaderFunctionArgs) {
  let lazyStuff = fetchLazyStuff();
  let tasks = await fetchTasks();
- return defer({ tasks, lazyStuff });
+ return { tasks, lazyStuff };
}

如果您使用 json/defer 的第二个参数在响应中设置自定义状态或标头,则可以通过新的 data API 继续执行此操作

-import { json } from "@remix-run/node";
+import { data } from "@remix-run/node";

export async function loader({}: LoaderFunctionArgs) {
  let tasks = await fetchTasks();
-  return json(tasks, {
+  return data(tasks, {
    headers: {
      "Cache-Control": "public, max-age=604800"
    }
  });
}

👉 调整服务器中止延迟

如果您在 entry.server.tsx 文件中使用自定义 ABORT_DELAY,则应将其更改为使用单次获取利用的 streamTimeout API

-const ABORT_DELAY = 5000;
+// Reject/cancel all pending promises after 5 seconds
+export const streamTimeout = 5000;

// ...

function handleBrowserRequest(/* ... */) {
  return new Promise((resolve, reject) => {
    const { pipe, abort } = renderToPipeableStream(
      <RemixServer
        context={remixContext}
        url={request.url}
-        abortDelay={ABORT_DELAY}
      />,
      {
        onShellReady() {
          /* ... */
        },
        onShellError(error: unknown) {
          /* ... */
        },
        onError(error: unknown) {
          /* ... */
        },
      }
    );

-    setTimeout(abort, ABORT_DELAY);
+   // Automatically timeout the React renderer after 6 seconds, which ensures
+   // React has enough time to flush down the rejected boundary contents
+   setTimeout(abort, streamTimeout + 1000);
  });
}

v3_lazyRouteDiscovery

背景

使用此标志,Remix 不再在初始加载时将完整的路由清单发送到客户端。相反,Remix 只在清单中发送服务器渲染的路由,然后在用户在应用程序中导航时获取剩余的路由。在 文档博文 中提供了其他详细信息

👉 启用标志

remix({
  future: {
    v3_lazyRouteDiscovery: true,
  },
});

更新你的代码

您无需对应用程序代码进行任何更改即可使用此功能。

如果您希望在某些链接上禁用急切路由发现,则可能会发现新的 <Link discover> API 的一些用法。

unstable_routeConfig

基于配置的路由是 React Router v7 中的新默认值,通过应用程序目录中的 routes.ts 文件配置。Remix 中对 routes.ts 及其相关 API 的支持被设计为迁移路径,以帮助最大程度地减少将 Remix 项目迁移到 React Router v7 时所需的更改次数。虽然在 @remix-run 范围内引入了一些新包,但这些新包仅用于使 routes.ts 中的代码尽可能类似于 React Router v7 的等效代码。

启用 unstable_routeConfig 未来标志后,Remix 的内置文件系统路由将被禁用,并且您的项目将选择加入 React Router v7 的基于配置的路由。要选择返回文件系统路由,可以在 routes.ts 中显式配置,如下所述。

更新您的代码

要将 Remix 的文件系统路由和路由配置迁移到 React Router v7 中的等效设置,您可以按照以下步骤操作

👉 启用标志

remix({
  future: {
    unstable_routeConfig: true,
  },
});

👉 安装 @remix-run/route-config

此包与 React Router v7 的 @react-router/dev/routes 的 API 匹配,使 React Router v7 的迁移尽可能简单。

npm install --dev @remix-run/route-config

它提供了核心 RouteConfig 类型以及一组用于在代码中配置路由的帮助程序。

👉 添加一个没有配置任何路由的 app/routes.ts 文件

touch app/routes.ts
import type { RouteConfig } from "@remix-run/route-config";

export const routes: RouteConfig = [];

这是检查新的 routes.ts 文件是否已成功拾取的好方法。由于尚未定义任何路由,因此您的应用程序现在应该会呈现一个空白页面。

👉 安装 @remix-run/fs-routes 并将其用于 routes.ts

npm install --dev @remix-run/fs-routes

此包与 React Router v7 的 @react-router/fs-routes 的 API 匹配,使 React Router v7 的迁移尽可能简单。

如果您已将 ignoredRouteFiles 配置为 ["**/*"],则应跳过此步骤,因为您已选择退出 Remix 的文件系统路由。

import { flatRoutes } from "@remix-run/fs-routes";
import type { RouteConfig } from "@remix-run/route-config";

export const routes: RouteConfig = flatRoutes();

👉 如果您使用了 routes 配置选项,请添加 @remix-run/routes-option-adapter 并将其用于 routes.ts

Remix 提供了一种机制来在代码中定义路由并插入替代的文件系统路由约定,可通过 Vite 插件上的 routes 选项获得。

为了使迁移更容易,提供了一个适配器包,可将 Remix 的 routes 选项转换为 React Router 的 RouteConfig 数组。

要开始,首先安装适配器

npm install --dev @remix-run/routes-option-adapter

此包与 React Router v7 的 @react-router/remix-routes-option-adapter 的 API 匹配,使 React Router v7 的迁移尽可能简单。

然后,更新您的 routes.ts 文件以使用适配器,将 routes 选项的值传递给 remixRoutesOptionAdapter 函数,该函数将返回一个已配置路由的数组。

例如,如果您使用 routes 选项使用替代的文件系统路由实现,例如 remix-flat-routes

import { type RouteConfig } from "@remix-run/route-config";
import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter";
import { flatRoutes } from "remix-flat-routes";

export const routes: RouteConfig = remixRoutesOptionAdapter(
  (defineRoutes) => flatRoutes("routes", defineRoutes)
);

或者,如果您使用 routes 选项定义基于配置的路由

import { flatRoutes } from "@remix-run/fs-routes";
import { type RouteConfig } from "@remix-run/route-config";
import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter";

export const routes: RouteConfig = remixRoutesOptionAdapter(
  (defineRoutes) => {
    return defineRoutes((route) => {
      route("/", "home/route.tsx", { index: true });
      route("about", "about/route.tsx");
      route("", "concerts/layout.tsx", () => {
        route("trending", "concerts/trending.tsx");
        route(":city", "concerts/city.tsx");
      });
    });
  }
);

如果您以这种方式定义基于配置的路由,您可能需要考虑迁移到新的路由配置 API,因为它更简化,同时仍然非常类似于旧的 API。例如,上面的路由将如下所示

import {
  type RouteConfig,
  route,
  layout,
  index,
} from "@remix-run/route-config";

export const routes: RouteConfig = [
  index("home/route.tsx"),
  route("about", "about/route.tsx"),
  layout("concerts/layout.tsx", [
    route("trending", "concerts/trending.tsx"),
    route(":city", "concerts/city.tsx"),
  ]),
];

请注意,如果您需要混合和匹配不同的路由配置方法,可以将它们合并到单个路由数组中。RouteConfig 类型确保一切仍然有效。

import { flatRoutes } from "@remix-run/fs-routes";
import type { RouteConfig } from "@remix-run/route-config";
import { route } from "@remix-run/route-config";
import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter";

export const routes: RouteConfig = [
  ...(await flatRoutes({ rootDirectory: "fs-routes" })),

  ...(await remixRoutesOptionAdapter(/* ... */)),

  route("/hello", "routes/hello.tsx"),
];

unstable_optimizeDeps

在开发过程中选择加入自动 依赖项优化。此标志将保持“不稳定”状态,直到 React Router v7,因此在升级到 React Router v7 之前,您无需在 Remix v2 应用程序中采用此标志。

文档和示例根据以下许可获得许可 MIT