Repository: remix-run/remix
Pull request #5160 (2023-01-20 16:52:54)

fix(remix-dev/flat-routes): use `index` without leading underscore for colocation
before:
```
  app_.projects.$id.roadmap/
    _index.tsx
    chart.tsx
    update-timeline.server.tsx
```

after:
```
  app_.projects.$id.roadmap/
    index.tsx
    chart.tsx
    update-timeline.server.tsx
```

Signed-off-by: Logan McAnsh <logan@mcan.sh>

<!--

👋 Hey, thanks for your interest in contributing to Remix!

If this is a simple docs change, go ahead and delete all this.

**Please ask first before starting work on any significant new features.**

It's never a fun experience to have your pull request declined after investing a
lot of time and effort into a new feature. To avoid this from happening, we
request that contributors create a
[Feature Request discussion](https://github.com/remix-run/remix/discussions/new?category=ideas)
to first discuss any significant new features.

https://github.com/remix-run/remix/blob/main/CONTRIBUTING.md

Please fill in or delete each item below:

-->

Closes: #

- [ ] Docs
- [ ] Tests

Testing Strategy:

<!--
Please explain how you tested this. For example:

> This test covers this code: <link to test>

Or

> I opened up my windows machine and ran this script:
>
> ```
> npx create-remix@0.0.0-experimental-7e420ee3 --template remix my-test
> cd my-test
> npm run dev
> ```
-->


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 28d23c6e7c4614c9cd15e5cb062240cf8ff6b22c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/remix-run/remix/new/logan/flat-routes-colocation-no-underscore?filename=.changeset/fresh-needles-join.md&value=---%0A%22remix%22%3A%20patch%0A%22%40remix-run%2Fdev%22%3A%20patch%0A---%0A%0Afix(remix-dev%2Fflat-routes)%3A%20use%20%60index%60%20without%20leading%20underscore%20for%20colocation%0A)



Patch:
diff --git a/.changeset/fresh-needles-join.md b/.changeset/fresh-needles-join.md
new file mode 100644
index 000000000..fa8b3fce0
--- /dev/null
+++ b/.changeset/fresh-needles-join.md
@@ -0,0 +1,24 @@
+---
+"remix": patch
+"@remix-run/dev": patch
+---
+
+use `index` without leading underscore for colocation
+
+when using flat routes with folder colocation, use `index` without leading underscore for route
+
+before:
+```
+  app_.projects.$id.roadmap/
+    _index.tsx
+    chart.tsx
+    update-timeline.server.tsx
+```
+
+after:
+```
+  app_.projects.$id.roadmap/
+    index.tsx
+    chart.tsx
+    update-timeline.server.tsx
+```
diff --git a/contributors.yml b/contributors.yml
index 58269fbfa..1263d129b 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -137,6 +137,7 @@
 - fgiuliani
 - fishel-feng
 - francisudeji
+- frandiox
 - freeman
 - frontsideair
 - fx109138
diff --git a/packages/remix-dev/compiler/watch.ts b/packages/remix-dev/compiler/watch.ts
index 5643309f3..3700e4e28 100644
--- a/packages/remix-dev/compiler/watch.ts
+++ b/packages/remix-dev/compiler/watch.ts
@@ -21,6 +21,7 @@ function isEntryPoint(config: RemixConfig, file: string): boolean {
 }
 
 export type WatchOptions = Partial<CompileOptions> & {
+  reloadConfig?(root: string): Promise<RemixConfig>;
   onRebuildStart?(): void;
   onRebuildFinish?(durationMs: number, assetsManifest?: AssetsManifest): void;
   onFileCreated?(file: string): void;
@@ -36,6 +37,7 @@ export async function watch(
     liveReloadPort,
     target = "node14",
     sourcemap = true,
+    reloadConfig = readConfig,
     onWarning = warnOnce,
     onCompileFailure = logCompileFailure,
     onRebuildStart,
@@ -68,7 +70,7 @@ export async function watch(
     dispose(compiler);
 
     try {
-      config = await readConfig(config.rootDirectory);
+      config = await reloadConfig(config.rootDirectory);
     } catch (error: unknown) {
       onCompileFailure(error as Error);
       return;
@@ -113,7 +115,7 @@ export async function watch(
       onFileCreated?.(file);
 
       try {
-        config = await readConfig(config.rootDirectory);
+        config = await reloadConfig(config.rootDirectory);
       } catch (error: unknown) {
         onCompileFailure(error as Error);
         return;
diff --git a/packages/remix-dev/config/flat-routes.ts b/packages/remix-dev/config/flat-routes.ts
index 65d59e394..16af37382 100644
--- a/packages/remix-dev/config/flat-routes.ts
+++ b/packages/remix-dev/config/flat-routes.ts
@@ -103,8 +103,11 @@ export function flatRoutesUniversal(
   return routes;
 }
 
-function isIndexRoute(routeId: string) {
-  return routeId.endsWith("_index");
+export function isIndexRoute(routeId: string) {
+  let isFlatFile = !routeId.includes(path.posix.sep);
+  return isFlatFile
+    ? routeId.endsWith("_index")
+    : /\/index$/.test(routeId);
 }
 
 type State =
@@ -262,7 +265,7 @@ function getRouteInfo(
   let routeIdWithoutRoutes = routeId.slice(routeDirectory.length + 1);
   let index = isIndexRoute(routeIdWithoutRoutes);
   let routeSegments = getRouteSegments(routeIdWithoutRoutes);
-  let routePath = createRoutePath(routeSegments);
+  let routePath = createRoutePath(routeSegments, index);
 
   return {
     id: routeIdWithoutRoutes,
@@ -274,9 +277,13 @@ function getRouteInfo(
   };
 }
 
-export function createRoutePath(routeSegments: string[]) {
+export function createRoutePath(routeSegments: string[], isIndex: boolean) {
   let result = "";
 
+  if (isIndex) {
+    routeSegments = routeSegments.slice(0, -1);
+  }
+
   for (let segment of routeSegments) {
     // skip pathless layout segments
     if (segment.startsWith("_")) {


Test patch:
diff --git a/packages/remix-dev/__tests__/flat-routes-test.ts b/packages/remix-dev/__tests__/flat-routes-test.ts
index 8735e7525..5446afec4 100644
--- a/packages/remix-dev/__tests__/flat-routes-test.ts
+++ b/packages/remix-dev/__tests__/flat-routes-test.ts
@@ -4,6 +4,7 @@ import {
   createRoutePath,
   flatRoutesUniversal,
   getRouteSegments,
+  isIndexRoute,
 } from "../config/flat-routes";
 import type { ConfigRoute } from "../config/routes";
 
@@ -25,10 +26,10 @@ describe("flatRoutes", () => {
       ["nested/$slug", "/nested/:slug"],
       ["flat.$slug", "/flat/:slug"],
       ["flat.sub", "/flat/sub"],
-      ["nested/_index", "/nested"],
+      ["nested/index", "/nested"],
       ["flat._index", "/flat"],
       ["_index", undefined],
-      ["_layout/_index", undefined],
+      ["_layout/index", undefined],
       ["_layout/test", "/test"],
       ["_layout.test", "/test"],
       ["_layout/$slug", "/:slug"],
@@ -88,7 +89,8 @@ describe("flatRoutes", () => {
     for (let [input, expected] of tests) {
       it(`"${input}" -> "${expected}"`, () => {
         let routeSegments = getRouteSegments(input);
-        expect(createRoutePath(routeSegments)).toBe(expected);
+        let isIndex = isIndexRoute(input);
+        expect(createRoutePath(routeSegments, isIndex)).toBe(expected);
       });
     }
 
@@ -332,9 +334,9 @@ describe("flatRoutes", () => {
         },
       ],
       [
-        "routes/app_.skipall_/_index.tsx",
+        "routes/app_.skipall_/index.tsx",
         {
-          id: "routes/app_.skipall_/_index",
+          id: "routes/app_.skipall_/index",
           index: true,
           parentId: "root",
           path: "app/skipall",
@@ -580,9 +582,9 @@ describe("flatRoutes", () => {
         },
       ],
       [
-        "routes/brand/_index.tsx",
+        "routes/brand/index.tsx",
         {
-          id: "routes/brand/_index",
+          id: "routes/brand/index",
           parentId: "root",
           path: "brand",
           index: true,