Remix Flat Routes

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 flat routes 约定

remix-flat-routes是 flat-routes 规范的初始实现。我根据用户反馈添加了一些增强功能。当 Remix v2 将 flat-routes 约定作为默认约定添加时,他们只使用了原始规范。

如果您想要诸如混合路由、扩展路由文件名、自定义参数前缀等增强功能,则需要继续使用此包。

remix-flat-routes将始终保持与默认 Remix 约定的兼容性。此包仅仅是核心约定的超集/扩展。

注意:流行的项目,例如 Kent C. Dodds 的 Epic Stack 使用 remix-flat-routes

混合路由

您现在可以为路由名称使用嵌套文件夹,但仍保留 flat routes 的协同定位功能。

如果您有一个大型应用程序,则嵌套多层路由并不罕见。使用默认的 flat routes,文件夹名称是整个路由路径:some.really.long.route.edit/index.tsx

通常您可能有多个父布局,例如_publicadmin。无需在每个路由中重复名称,您可以创建顶级文件夹,然后在其下嵌套路由。这样您仍然可以利用 flat 文件夹的协同定位优势。

之前

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

您仍然可以使用 flat 文件夹进行协同定位。因此,这是两种格式的最佳选择。

❯ 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 将引入一个用于可选路由段的新功能。要在 flat routes 中使用可选段,只需将路由名称括在()中。

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属性以忽略**所有**文件:**/*

🔨 Flat Routes 约定

示例(flat-files)

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>

单独解释

文件名 URL 嵌套在…中
_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 n/a root.tsx
_landing.about.tsx /about _landing.tsx
_landing.index.tsx / _landing.tsx
_landing.tsx n/a 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 n/a (资源路由)

嵌套布局

默认匹配

默认情况下,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 route 后缀 可选,完全忽略
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)

别名

由于路由文件现在命名为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规范。欢迎任何形式的贡献!