React Router v7 已發布。 查看文件
未來標誌
本頁內容

未來標誌和棄用

本指南將引導您完成在您的 Remix 應用程式中採用未來標誌的過程。透過遵循此策略,您將能夠以最少的變更升級到 Remix 的下一個主要版本。若要深入瞭解未來標誌,請參閱開發策略

我們強烈建議您在每個步驟之後進行提交並發布,而不是一次完成所有事情。大多數標誌可以按任何順序採用,但以下註明的例外情況除外。

更新至最新的 v2.x 版本

首先更新至最新的 v2.x 次要版本,以取得最新的未來標誌。在您升級時,您可能會看到一些棄用警告,我們將在下面介紹。

👉 更新至最新的 v2

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

移除 installGlobals

背景

先前 Remix 需要安裝 fetch polyfill。這是透過呼叫 installGlobals() 來完成的。

下一個主要版本需要至少 Node 20 才能利用內建的 fetch 支援。

注意:如果您在您的 remix 專案中使用 miniflare/cloudflare worker,請確保您的相容性標誌也設定為 2023-03-01 或更高版本。

👉 更新至 Node 20+

建議您升級至最新的偶數版本 Node LTS。

👉 移除 installGlobals

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

-installGlobals();

export default defineConfig({
  plugins: [remix()],
});

採用 Vite 外掛

背景

Remix 不再使用其自己的封閉編譯器(現在稱為「Classic Compiler」),而是使用 Vite。Vite 是 JavaScript 專案強大、高效且可擴充的開發環境。檢視 Vite 文件以取得效能、疑難排解等方面的更多資訊。

雖然這不是一個未來標誌,但新功能和某些功能標誌僅在 Vite 外掛中可用,而 Classic Compiler 將在下一個版本的 Remix 中移除。

👉 安裝 Vite

npm install -D vite

更新您的程式碼

👉 將您 Remix 應用程式根目錄中的 remix.config.js 替換為 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"],
    }),
  ],
});

👉 新增 unstable_optimizeDeps(可選)

許多使用者發現自動最佳化相依性可協助他們更輕鬆地採用 Vite 外掛。因此,我們在 Vite 外掛中新增了 unstable_optimizeDeps 標誌。

此標誌將保持在「不穩定」狀態,直到 React Router v7,因此在升級至 React Router v7 之前,您不需要在 Remix v2 應用程式中採用此標誌。

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

👉 移除 <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 外掛程式,以自動從 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 模組,以及其他 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 開發伺服器連接埠(可選)

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_lazyRouteDiscovery

背景

啟用此標記後,Remix 不再在初始載入時將完整的路由清單傳送到客戶端。相反地,Remix 只會在清單中傳送伺服器渲染的路由,然後在使用者瀏覽應用程式時擷取其餘的路由。其他詳細資訊請參閱文件部落格文章

👉 啟用標誌

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

更新您的程式碼

您應該不需要為了讓此功能運作而對應用程式程式碼進行任何變更。

如果您想要在某些連結上停用預先路由探索,您可能會發現新的 <Link discover> API 有些用途。

v3_singleFetch

此標記需要 Vite 外掛程式

背景

啟用此標記後,Remix 會在客戶端導覽期間使用單次擷取來處理資料請求。這透過將資料請求視為文件請求相同的方式,簡化了資料載入,消除了處理標頭和快取的差異。對於進階的使用案例,您仍然可以選擇進行細微的重新驗證。如需更多資訊,請參閱「單次擷取」文件

👉 啟用標記(和類型)

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_routeConfig

此標記需要 Vite 外掛程式

基於設定的路由是 React Router v7 中的新預設設定,透過應用程式目錄中的 routes.ts 檔案進行設定。Remix 中對 routes.ts 及其相關 API 的支援,旨在作為遷移路徑,以盡可能減少將 Remix 專案遷移到 React Router v7 時所需的變更數量。雖然在 @remix-run 範圍內引入了一些新的套件,但這些新的套件僅為了讓 routes.ts 中的程式碼,與 React Router v7 的等效程式碼盡可能相似。

啟用 v3_routeConfig 未來標記後,Remix 的內建檔案系統路由將會停用,而您的專案將選擇使用 React Router v7 的基於設定的路由。如果您偏好繼續使用 Remix 的基於檔案的路由,我們將在下方的 routes.ts 中說明如何啟用它。

更新您的程式碼

若要將 Remix 的檔案系統路由和路由設定遷移到 React Router v7 中的等效設定,您可以按照下列步驟操作

👉 啟用標誌

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

👉 安裝 @remix-run/route-config

此套件符合 React Router v7 的 @react-router/dev/routes 的 API,盡可能簡化 React Router v7 的遷移。

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

這提供了核心的 RouteConfig 類型,以及一組用於在程式碼中設定路由的輔助工具。

👉 新增一個不含任何已設定路由的 app/routes.ts 檔案

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

export default [] satisfies RouteConfig;

這是一個很好的方法,可以檢查您的新 routes.ts 檔案是否已成功載入。由於尚未定義任何路由,因此您的應用程式現在應呈現空白頁面。

👉 安裝 @remix-run/fs-routes 並在 routes.ts 中使用它

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

此套件符合 React Router v7 的 @react-router/fs-routes 的 API,盡可能簡化 React Router v7 的遷移。

如果您將 ignoredRouteFiles 設定為 ["**/*"],則應略過此步驟,因為您已選擇退出 Remix 的檔案系統路由。

import { flatRoutes } from "@remix-run/fs-routes";

export default flatRoutes();

👉 如果您使用了 routes 設定選項,請新增 @remix-run/routes-option-adapter 並在 routes.ts 中使用它

Remix 提供了一種在程式碼中定義路由,並插入替代檔案系統路由慣例的機制,可透過 Vite 外掛程式上的 routes 選項取得。

為了簡化遷移,提供了一個轉換器套件,可將 Remix 的 routes 選項轉換為 React Router 的 RouteConfig 陣列。

若要開始,請先安裝轉換器

npm install -D @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 default remixRoutesOptionAdapter((defineRoutes) =>
  flatRoutes("routes", defineRoutes)
) satisfies RouteConfig;

或者,如果您使用 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 default 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");
    });
  });
}) satisfies RouteConfig;

如果您以這種方式定義基於設定的路由,您可能會想要考慮遷移到新的路由設定 API,因為它更精簡,同時仍然與舊 API 非常相似。例如,上述路由看起來會像這樣

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

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

請注意,如果您需要混合和比對不同的路由設定方法,它們可以合併成單個路由陣列。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 default [
  ...(await flatRoutes({ rootDirectory: "fs-routes" })),

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

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

已棄用

@remix-run/eslint-config

@remix-run/eslint-config 套件已棄用,且不會包含在 React Router v7 中。我們建議您轉向簡化的 ESLint 設定,例如 Remix 範本中包含的設定。

json

此公用程式已棄用,並將在 React Router v7 中移除,以支援 單次擷取原始物件傳回。

  • 如果您不依賴 json 來序列化您的資料(例如字串化 Date 物件),您可以安全地移除它。
  • 如果您透過 json 傳回 headersstatus,您可以使用新的 data 公用程式作為直接替代來設定這些值。
  • 如果您想要將資料序列化為 JSON,可以使用原生的 Response.json() 方法。

如需更多資訊,請參閱 單次擷取 文件。

defer

此公用程式已棄用,並將在 React Router v7 中移除,以支援 單次擷取原始物件傳回。

  • 如果您透過 defer 傳回 headersstatus,您可以使用新的 data 公用程式作為直接替代來設定這些值。

如需更多資訊,請參閱 單次擷取 文件。

SerializeFrom

此類型已棄用,並將在 React Router v7 中移除,因為 單次擷取不再將資料序列化為 JSON。

如果您依賴 SerializeFrom 來解開 loader/action 資料,您可以使用像這樣的自訂類型

type SerializeFrom<T> = ReturnType<typeof useLoaderData<T>>;

在大多數情況下,您應該能夠直接移除 SerializeFrom,並使用從 useLoaderData/useActionData 傳回的類型,或 loader/action 函式中資料的類型。

多部分表單資料和檔案上傳公用程式

以下公用程式已棄用,並將在 React Router v7 中移除

  • unstable_parseMultipartFormData
  • unstable_composeUploadHandlers
  • unstable_createFileUploadHandler
  • unstable_createMemoryUploadHandler

我們建議使用 @mjackson/form-data-parser@mjackson/file-storage 來處理多部分表單資料和檔案上傳。

您也可以查看 React Router「檔案上傳」文件「使用 Remix 進行檔案上傳」部落格文章,以取得使用這些程式庫的指南。

文件和範例授權於 MIT