Remix 平面路由

All Contributors

此套件讓您可以使用 flat-routes 慣例定義路由。這是基於 Ryan Florence 的 gist

💡 React Router v7 支援

React Router v7 使用新的路由配置。為了簡化從 Remix 遷移的過程,團隊發布了一個適配器套件,可將現有的 Remix 基於檔案的路由轉換為新的配置格式。

若要使用現有的基於檔案的路由,請安裝適配器並更新 routes.ts 以封裝您的適配器。

npm install -D @react-router/remix-config-routes-adapter
npm install -D remix-flat-routes
// app/routes.ts
import { remixConfigRoutes } from "@react-router/remix-config-routes-adapter";
import { flatRoutes } from "remix-flat-routes";

export const routes = remixConfigRoutes((defineRoutes) => {
  return flatRoutes("routes", defineRoutes, {/* options */});
});

✨🎉 v0.5.0 中的新功能

Remix v2 平面路由慣例

remix-flat-routes 是平面路由規範的最初實作。我根據使用者回饋添加了一些增強功能。當 Remix v2 將平面路由慣例添加為預設值時,他們使用了原始規範。

如果您想要諸如混合路由、擴展路由檔名、自訂參數前綴等增強功能,您將需要繼續使用此套件。

remix-flat-routes 將始終保持與預設 Remix 慣例的相容性。此套件僅是核心慣例的超集/擴展。

注意:像 Kent C. Dodds 的 Epic Stack 等熱門專案都使用了 remix-flat-routes

混合路由

您現在可以使用巢狀資料夾作為路由名稱,同時仍然保留平面路由的並置功能。

如果您有一個大型應用程式,路由巢狀多層並不罕見。使用預設平面路由時,資料夾名稱是整個路由路徑:some.really.long.route.edit/index.tsx

通常您可能會有幾個父版面配置,例如 _publicadmin。您不必在每個路由中重複名稱,而是可以建立頂層資料夾,然後將路由巢狀置於其下。這樣,您仍然可以利用平面資料夾的並置優勢。

之前

❯ tree app/routes-folders
app/routes-folders
├── _index
│   └── page.tsx
├── _public
│   └── _layout.tsx
├── _public.about
│   └── index.tsx
├── _public.contact[.jpg]
│   └── index.tsx
├── test.$
│   ├── _route.server.tsx
│   └── _route.tsx
├── users
│   ├── _layout.tsx
│   └── users.css
├── users.$userId
│   ├── _route.tsx
│   └── avatar.png
├── users.$userId_.edit
│   └── _route.tsx
└── users._index
    └── index.tsx

之後

❯ tree app/routes-hybrid
app/routes-hybrid
├── _index
│   └── index.tsx
├── _public
│   ├── _layout.tsx
│   ├── about
│   │   └── _route.tsx
│   └── contact[.jpg]
│       └── _route.tsx
├── test.$
│   └── _route.tsx
└── users
    ├── $userId
    │   ├── _route.tsx
    │   └── avatar.png
    ├── $userId_.edit
    │   └── _route.tsx
    ├── _index
    │   └── index.tsx
    ├── _layout.tsx
    └── users.css

具有 flat-files 慣例的巢狀資料夾 (✨ v0.5.1 中的新功能)

若要建立一個資料夾但將其視為平面檔案,只需在資料夾名稱後附加 +

_auth+/forgot-password.tsx => _auth.forgot-password.tsx

注意:您可以在資料夾內包含 _layout.tsx 檔案。您不需要有 _public.tsx 或 users.tsx 檔案。

您仍然可以使用平面資料夾進行並置。因此,這是兩種格式的最佳組合。

❯ tree app/routes-hybrid-files/
app/routes-hybrid-files/
├── _auth+
│   ├── forgot-password.tsx
│   └── login.tsx
├── _public+
│   ├── _layout.tsx
│   ├── about.tsx
│   ├── contact[.jpg].tsx
│   └── index.tsx
├── project+
│   ├── _layout.tsx
│   ├── parent.child
│   │   └── index.tsx
│   └── parent.child.grandchild
│       ├── index.tsx
│       └── styles.css
└── users+
    ├── $userId.tsx
    ├── $userId_.edit.tsx
    ├── _layout.tsx
    └── index.tsx
<Routes>
  <Route file="root.tsx">
    <Route
      path="forgot-password"
      file="routes-hybrid-files/_auth+/forgot-password.tsx"
    />
    <Route path="login" file="routes-hybrid-files/_auth+/login.tsx" />
    <Route file="routes-hybrid-files/_public+/_layout.tsx">
      <Route path="about" file="routes-hybrid-files/_public+/about.tsx" />
      <Route
        path="contact.jpg"
        file="routes-hybrid-files/_public+/contact[.jpg].tsx"
      />
      <Route index file="routes-hybrid-files/_public+/index.tsx" />
    </Route>
    <Route path="project" file="routes-hybrid-files/project+/_layout.tsx">
      <Route
        path="parent/child"
        file="routes-hybrid-files/project+/parent.child/index.tsx"
      >
        <Route
          path="grandchild"
          file="routes-hybrid-files/project+/parent.child.grandchild/index.tsx"
        />
      </Route>
    </Route>
    <Route path="users" file="routes-hybrid-files/users+/_layout.tsx">
      <Route path=":userId" file="routes-hybrid-files/users+/$userId.tsx" />
      <Route
        path=":userId/edit"
        file="routes-hybrid-files/users+/$userId_.edit.tsx"
      />
      <Route index file="routes-hybrid-files/users+/index.tsx" />
    </Route>
  </Route>
</Routes>

擴展路由檔名

除了標準的 index | route | page | layout 名稱之外,任何具有 _ 前綴的檔案都將被視為路由檔案。這將更容易找到特定的路由,而不是瀏覽一堆 route.tsx 檔案。這受到了 SolidStart 的「重新命名索引」功能的啟發。

所以,不用

_public.about/route.tsx
_public.contact/route.tsx
_public.privacy/route.tsx

您可以將它們命名為

_public.about/_about.tsx
_public.contact/_contact.tsx
_public.privacy/_privacy.tsx

多個路由資料夾

您現在可以傳入除了預設 routes 資料夾之外的其他路由資料夾。這些路由將合併到單個命名空間中,因此您可以讓一個資料夾中的路由使用另一個資料夾中的共用路由。

自訂參數前綴

您可以覆寫 $ 的預設參數前綴。某些 shell 會將 $ 前綴用於變數,這可能會因為 shell 擴展而導致問題。請使用任何有效的檔名字元,例如:^

users.^userId.tsx  => users/:userId
test.^.tsx         => test/*

自訂基本路徑

您可以覆寫 / 的預設基本路徑。這會將您的基本路徑附加到根路徑。

可選路由區段

React Router 將為可選路由區段引入新功能。若要在平面路由中使用可選區段,只需將路由名稱包裝在 () 中即可。

parent.(optional).tsx   => parent/optional?

自訂應用程式目錄

您可以覆寫 app 的預設應用程式目錄。

🛠 安裝

npm install -D remix-flat-routes

⚙️ 設定

更新您的 *remix.config.js* 檔案並使用自訂路由配置選項。

const { flatRoutes } = require('remix-flat-routes')

/**
 * @type {import("@remix-run/dev").AppConfig}
 */
module.exports = {
  // ignore all files in routes folder to prevent
  // default remix convention from picking up routes
  ignoredRouteFiles: ['**/*'],
  routes: async defineRoutes => {
    return flatRoutes('routes', defineRoutes)
  },
}

API

function flatRoutes(
  routeDir: string | string[],
  defineRoutes: DefineRoutesFunction,
  options: FlatRoutesOptions,
)

type FlatRoutesOptions = {
  appDir?: string // optional app directory (defaults to app)
  basePath?: string // optional base path (default is '/')
  paramPrefixChar?: string // optional param prefix (default is '$')
  ignoredRouteFiles?: string[] // optional files to ingore as routes (same as Remix config option)
  visitFiles?: VisitFilesFunction // optional visitor (useful for tests to provide files without file system)
}

注意:routeDir 應相對於 app 資料夾。如果您想要使用 routes 資料夾,您將需要更新 ignoredRouteFiles 屬性以忽略所有檔案:**/*

🔨 平面路由慣例

範例(平面檔案)

routes/
  _auth.forgot-password.tsx
  _auth.login.tsx
  _auth.reset-password.tsx
  _auth.signup.tsx
  _auth.tsx
  _landing.about.tsx
  _landing.index.tsx
  _landing.tsx
  app.calendar.$day.tsx
  app.calendar.index.tsx
  app.calendar.tsx
  app.projects.$id.tsx
  app.projects.tsx
  app.tsx
  app_.projects.$id.roadmap.tsx
  app_.projects.$id.roadmap[.pdf].tsx

作為 React Router 路由

<Routes>
  <Route element={<Auth />}>
    <Route path="forgot-password" element={<Forgot />} />
    <Route path="login" element={<Login />} />
    <Route path="reset-password" element={<Reset />} />
    <Route path="signup" element={<Signup />} />
  </Route>
  <Route element={<Landing />}>
    <Route path="about" element={<About />} />
    <Route index element={<Index />} />
  </Route>
  <Route path="app" element={<App />}>
    <Route path="calendar" element={<Calendar />}>
      <Route path=":day" element={<Day />} />
      <Route index element={<CalendarIndex />} />
    </Route>
    <Route path="projects" element={<Projects />}>
      <Route path=":id" element={<Project />} />
    </Route>
  </Route>
  <Route path="app/projects/:id/roadmap" element={<Roadmap />} />
  <Route path="app/projects/:id/roadmap.pdf" />
</Routes>

個別說明

檔名 網址 巢狀於...
_auth.forgot-password.tsx /forgot-password _auth.tsx
_auth.login.tsx /login _auth.tsx
_auth.reset-password.tsx /reset-password _auth.tsx
_auth.signup.tsx /signup _auth.tsx
_auth.tsx 不適用 root.tsx
_landing.about.tsx /about _landing.tsx
_landing.index.tsx / _landing.tsx
_landing.tsx 不適用 root.tsx
app.calendar.$day.tsx /app/calendar/:day app.calendar.tsx
app.calendar.index.tsx /app/calendar app.calendar.tsx
app.projects.$id.tsx /app/projects/:id app.projects.tsx
app.projects.tsx /app/projects app.tsx
app.tsx /app root.tsx
app_.projects.$id.roadmap.tsx /app/projects/:id/roadmap root.tsx
app_.projects.$id.roadmap[.pdf].tsx /app/projects/:id/roadmap.pdf 不適用(資源路由)

巢狀版面配置

預設比對

預設情況下,flat-routes 會將目前的路由巢狀置於具有最長比對前綴的父版面配置中。

假設版面配置路由為 app.calendar.tsx,以下路由將巢狀於 app.calendar.tsx 下,因為 app.calendar 是最長的比對前綴。

  • app.calendar.index.tsx
  • app.calendar.$day.tsx

覆寫比對

有時您想要使用路由階層中較高的父版面配置。使用預設 Remix 慣例時,您會使用點 (.) 表示法,而不是使用巢狀資料夾。使用 flat-routes 時,由於路由檔案始終使用點,因此存在一種不同的慣例來指定巢狀於哪個版面配置下。

假設您有一個 app.tsx 版面配置,並且有一個您不想與版面配置共用的路由,而是想要與 root.tsx 比對。若要覆寫預設父比對,請在您要巢狀於其下的路由的直接子項區段附加底線 (_)。

app_.projects.$id.roadmap.tsx 將巢狀於 root 下,因為沒有比對的路由

  • app_.projects.$id.tsx
  • app_.projects.tsx
  • app_.tsx
  • root.tsx

慣例

檔名 慣例 行為
privacy.jsx 檔名 一般路由
pages.tos.jsx 不含版面配置的點 一般路由,. -> /
about.jsx 具有子項的檔名 父版面配置路由
about.contact.jsx 版面配置的子路由
about.index.jsx 索引檔名 版面配置的索引路由
about._index.jsx index.tsx 的別名 版面配置的索引路由*
about_.company.jsx 尾隨底線 URL 區段,無版面配置
app_.projects.$id.roadmap.tsx 尾隨底線 變更預設父版面配置
_auth.jsx 開頭底線 版面配置巢狀,無 URL 區段
_auth.login.jsx 開頭底線 無路徑版面配置路由的子項
users.$userId.jsx 開頭 $ URL 參數
docs.$.jsx 裸 $ 星號路由
dashboard.route.jsx 路由字尾 選用,完全忽略
investors/[index].jsx 括號 逸出傳統字元

注意:索引路由的底線前綴是選用的,但有助於將檔案排序到目錄清單的頂部。

理由

  • 更容易查看您的應用程式定義的路由 - 只需開啟「routes/」它們就在那裡。由於檔案系統通常先對資料夾進行排序,因此當您有數十個路由時,很難看出哪些資料夾具有版面配置,哪些沒有。現在,所有相關路由都一起排序。

  • 減少重構/重新設計摩擦 - 雖然程式碼編輯器在您移動檔案時非常擅長修復匯入,並且 Remix 具有 "~" 匯入別名,但通常更容易重構沒有一堆巢狀資料夾的程式碼庫。Remix 將不再強制執行此操作。

    此外,在重新設計使用者介面時,調整檔案名稱比建立/刪除資料夾和移動路由以變更其巢狀方式更簡單。

  • 協助應用程式遷移到 Remix - 現有的應用程式通常沒有像今天的慣例那樣的巢狀路由資料夾結構。遷移到 Remix 很費力,因為您必須處理所有匯入。

並置

雖然範例專門是檔案,但它們實際上只是「匯入路徑」。因此,您可以為路由建立一個資料夾,並且會匯入 index 檔案,讓路由的所有模組可以並排存在。這是平面資料夾慣例,而不是上面詳述的平面檔案慣例。

範例(平面資料夾)

routes/
  _auth.forgot-password.tsx
  _auth.login.tsx
  _auth.tsx
  _landing.about.tsx
  _landing.index.tsx
  _landing.tsx
  app.projects.tsx
  app.projects.$id.tsx
  app.tsx
  app_.projects.$id.roadmap.tsx

每個路由會變成一個資料夾,資料夾名稱為路由名稱減去檔案副檔名。路由檔案接著會被命名為 index.tsx

所以 app.projects.tsx 會變成 app.projects/index.tsx

routes/
  _auth/
    index.tsx x <- route file (same as _auth.tsx)
  _auth.forgot-password/
    index.tsx  <- route file (same as _auth.forgot-password.tsx)
  _auth.login/
    index.tsx   <- route files (same as _auth.login.tsx)
  _landing.about/
    index.tsx   <- route file (same as _landing.about.tsx)
    employee-profile-card.tsx
    get-employee-data.server.tsx
    team-photo.jpg
  _landing.index/
    index.tsx   <- route file (same as _landing.index.tsx)
    scroll-experience.tsx
  _landing/
    index.tsx   <- route file (same as _landing.tsx)
    header.tsx
    footer.tsx
  app/
    index.tsx   <- route file (same as app.tsx)
    primary-nav.tsx
    footer.tsx
  app_.projects.$id.roadmap/
    index.tsx   <- route file (same as app_.projects.$id.roadmap.tsx)
    chart.tsx
    update-timeline.server.tsx
  app.projects/
    index.tsx <- layout file (sames as app.projects.tsx)
    project-card.tsx
    get-projects.server.tsx
    project-buttons.tsx
  app.projects.$id/
    index.tsx  <- route file (sames as app.projects.$id.tsx)

別名 (Aliases)

由於路由檔案現在被命名為 index.tsx,而你可以在同一個路由資料夾中並排放置額外的檔案,index.tsx 檔案可能會在檔案列表中迷失。你也可以使用以下別名來表示 index.tsx。底線前綴會將檔案排序到目錄列表的頂部。

  • _index.tsx
  • _layout.tsx
  • _route.tsx

注意:_layout.tsx_route.tsx 檔案只是更明確地表明它們的角色。它們的作用與 index.tsx 相同。

與扁平檔案一樣,索引路由(不要與索引路由*檔案*混淆)也可以使用底線前綴。路由 _landing.index 可以儲存為 _landing.index/index.tsx_landing._index/_index.tsx

這有點主觀,但我認為這最終是大多數開發人員會喜歡的。每個路由都變成自己獨立的「迷你應用程式」,所有依賴項都在一起。使用 ignoredRouteFiles 選項時,完全不清楚哪些檔案是路由,哪些不是。

🚚 遷移現有路由

你現在可以將現有路由遷移到新的 flat-routes 約定。只需執行

npx migrate-flat-routes <sourceDir> <targetDir> [options]

Example:
  npx migrate-flat-routes ./app/routes ./app/flatroutes --convention=flat-folders

NOTE:
  sourceDir and targetDir are relative to project root

Options:
  --convention=<convention>
    The convention to use when migrating.
      flat-files - Migrates to flat files
      flat-folders - Migrates to flat directories with route.tsx files
      hybrid - Keep folder structure with '+' suffix and _layout files
  --force
    Overwrite target directory if it exists

😍 貢獻者

感謝這些優秀的人們 (表情符號鍵)


Kiliman

💻 📖

Ryan Florence

📖

Brandon Pittman

📖 💻

Mehdi Achour

📖

Fidel González

📖

Andrew Haines

💻

Wonu Lee

💻

Markus Wolf

💻

Sarat Chandra Balla

💻

這個專案遵循 all-contributors 規範。歡迎任何形式的貢獻!