Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remix optional segments #4706

Merged

Conversation

lordofthecactus
Copy link
Contributor

Closes: remix-run/react-router#9550
Related: remix-run/react-router#9650

As discussed this would transform remix routes/($lang)/about
into /:lang?/about which would then be matched by react-router (remix-run/react-router#9650)

/lang/about
/about

Another example /(one)/($two)/(three).($four) file routing would get transformed into `/one?/:two?/three?/:four? which then would get matched by react-router:

/
/one
/one/:two
/one/:two/three
/one/:two/three/:four

Context

Parenthesis was chosen to denote optionality since windows file systems can't have ?

Feedback needed

left some tests failing, for some edge cases. I would like feedback on how would be best to handle them or how to be more defensive about this.

  • Docs
  • Tests

Testing Strategy:
This test covers this code: https://github.com/remix-run/remix/compare/dev...lordofthecactus:remix:feat/remix-optional-segments?expand=1#diff-04792fa211911bb758e39db1e8bc1dfffb55657840b8f9f17fbc2330d7b346e1R41-R79

@changeset-bot
Copy link

changeset-bot bot commented Nov 29, 2022

🦋 Changeset detected

Latest commit: 85801f1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
@remix-run/dev Major
create-remix Major
remix Major
@remix-run/architect Major
@remix-run/cloudflare Major
@remix-run/cloudflare-pages Major
@remix-run/cloudflare-workers Major
@remix-run/deno Major
@remix-run/eslint-config Major
@remix-run/express Major
@remix-run/netlify Major
@remix-run/node Major
@remix-run/react Major
@remix-run/serve Major
@remix-run/server-runtime Major
@remix-run/vercel Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@remix-cla-bot
Copy link
Contributor

remix-cla-bot bot commented Nov 29, 2022

Hi @lordofthecactus,

Welcome, and thank you for contributing to Remix!

Before we consider your pull request, we ask that you sign our Contributor License Agreement (CLA). We require this only once.

You may review the CLA and sign it by adding your name to contributors.yml.

Once the CLA is signed, the CLA Signed label will be added to the pull request.

If you have already signed the CLA and received this response in error, or if you have any questions, please contact us at hello@remix.run.

Thanks!

- The Remix team

@remix-cla-bot
Copy link
Contributor

remix-cla-bot bot commented Nov 29, 2022

Thank you for signing the Contributor License Agreement. Let's get this merged! 🥳

@ryanflorence
Copy link
Member

ryanflorence commented Nov 29, 2022

Awesome. @brophdawg11 is 1.8.0-pre on React Router 6.4? If so let's merge this and add it to 1.9. Otherwise we'd need to wait until we're on 6.4.

EDIT: Just checked, we're not on 6.4 in dev yet. Any reason for this? Technically it should be fine?

@brophdawg11
Copy link
Contributor

@ryanflorence Yep! Documenting what we discussed here. We do want to do an isolated react-router-dom 6.3.0 -> 6.4.* upgrade while still using createBrowserHistory/Router. We do not have that in 1.8.0 since that is isolated to using @remix-run/router in @remix-run/server-runtime. So I think the timeline will be:

  1. Remix 1.8.0 - Uses @remix-run/router for server-side data fetching
  2. Remix 1.8.1 - Upgrades to react-router-dom@6.4.* while still using createBrowerHistory/Router
  3. React Router 6.5 - Introduces optional route segments
  4. Remix 1.9.0 - Upgrades to react-router-dom@6.5.* + introduces conventional route support for optional route segments
    • This may also be the switch for Remix to start using createBrowserRouter/RouterProvider but will depend on timing

So I think we want to merge this after we cut the release branch for 1.8.1, which is probably early next week


// Optional segment routes
["(routes)/$", "routes?/*"],
["(routes)/($)", "routes?/*?"], // TODO: Fails, do we want to allow this?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to support optional splats as they're already optional. path="child/*" will match both /child and /child/any/other/path: See https://codesandbox.io/s/optional-splat-segments-xpn3kq

Maybe we throw an error on ($) syntax?

["(routes)/($)", "routes?/*?"], // TODO: Fails, do we want to allow this?
["(routes)/(sub)/$", "routes?/sub?/*"],
["(routes).(sub)/$", "routes?/sub?/*"],
["(routes.sub)/$", "routes?/sub?/*"], // TODO: Fails, do we want to allow this?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I don't think we need to add the complexity of being able to interpolate dots inside an optional segment - the line above is how I'd expect optional segments + dot-notation to work together. In this case I'd expect the generated React Router path to be <Route path="routes.sub?/*">

Copy link
Member

@ryanflorence ryanflorence Nov 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

["(routes.sub)/$", "routes?/sub?/*"] // do we want to allow this?

The word "routes" there is confusing to me, so I'm changing it to "products.all" for my comment 😅

["(products.all)/$", "products?/all?/*"] // do we want to allow this?

No, we don't. The React Router version won't have a way of "grouping" two segments as optional together either:

// this is only for "sub"
<Route path="products/all?" />

// this is for both
<Route path="products?/all?" />

So in Remix it should be the same, only individual segments can be optional:

(products).(all).tsx -> products?/all?

["(nested)/($slug)", "nested?/:slug?"],
["(flat).($slug)", "flat?/:slug?"],
["flat.(sub)", "flat/sub?"],
["(nested)/(index)", "nested?"], // Fails with `"flat?/index?"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to do an "optional" index route in react router since ? is only for use with path. Optional segments are for when you want to use the same element for multiple <Route> paths, but that wouldn't make sense for index routes:

// You wouldn't need to do this since `/nested` already renders <Nested /> and 
// therefore doesn't need the index route to add it
<Route path="nested" element={<Nested />}>
  <Route index element={<Nested />} />
  <Route path="child" element={<Child />} />
</Parent/>

// So you would just do and the `Outlet` inside of `<Nested/>` would be a no-op 
// when you were at `/nested`
<Route path="nested" element={<Nested />}>
  <Route path="child" element={<Child />} />
</Parent/>

I haven't ever tried this but I assume if you want an actual /index URL you would escape it like routes/nested/[index].tsx? Lets' test that - and if that's the case, would we need to support /routes/nested/([index]).tsx to output <Route path="nested/index?">?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, [] are the escape characters to escape any of our file conventions. Should make sure it works with (), also.

// match literal paren characters in the URL
routes/[(foo)].tsx => <Route path="(foo)" />

// match literal `index` in an optional segment
routes/funds/([index]).tsx => <Route path="index?" />

["(nested)/__layout/($slug)", "nested?/:slug?"],
["($slug[.]json)", ":slug.json?"],
["(sub)/([sitemap.xml])", "sub?/sitemap.xml?"],
["(sub)/[(sitemap.xml)]", "sub?/sitemap.xml?"], // TODO should this have been sub?/(sitemap.xml)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think this looks like a bug since the [] escaping isn't working right

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working now!

["(sub)/[(sitemap.xml)]", "sub?/sitemap.xml?"], // TODO should this have been sub?/(sitemap.xml)
["(posts)/($slug)/([image.jpg])", "posts?/:slug?/image.jpg?"],
[
"($[$dollabills]).([.]lol)[/](what)/([$]).($)", // TODO outputs ":$dollabills?/.lol?/what?/$?/:?"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is an escaped [/] allowed? That seems odd to me. And then we don't need to support ($) so I think this test could become something like:

[
  "($[$dollabills]).([.]lol)/(what)/([$]).($up)", 
  ":$dollabills?/.lol?/what?/$?/:up?"
]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added!

],
["(sub).([[])", "sub?/[?"],
["(sub).(])", "sub?/)?"],
["(sub).([([])])", "sub/[]"], // Fails with "sub?/([)]?"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤯 uhhh - I guess the intention here is sub?/([])??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems from before there is not a clear handling of multiple brackets within brackets. Either you close on the first match or get the longest within multiple separators? feels like this should be a different PR and discussion.

@sergiodxa
Copy link
Member

Parenthesis was chosen to denote optionality since windows file systems can't have ?

😭

@ryanflorence
Copy link
Member

@sergiodxa don't forget, you can make your own convention in remix.config.routes and use ? if you don't have anybody using windows on your team :)

@lordofthecactus
Copy link
Contributor Author

@ryanflorence @sergiodxa interestingly question mark is still possible on route creation 🤔

Co-authored-by: Pedro Cattori <pcattori@gmail.com>
@pcattori
Copy link
Contributor

pcattori commented Dec 8, 2022

@brophdawg11 so if we merge this before Remix uses RR 6.5, then optional routes won't work, correct?

So what's the rational of merging this prior to that? I get the it won't break things since we could just not tell anyone about optional routes until Remix is on RR 6.5, but then we'd have to remember to write release notes for it later. Why not just merge this after Remix uses RR 6.5?

@brophdawg11
Copy link
Contributor

Yeah that would break. I'm not advocating for merging prior to that - step 4 in #4706 (comment) encompasses merging this PR and updating @remix-run/react to use RR 6.5.0 once it's released. Now that optional routes are merged in RR we can probably cut a 6.5.0-pre.0 next week over there. Then we should sync up with internally on the corresponding remix releases - we could cut a 1.8.3 on 6.4 first or we could go straight to a 1.9.0 with on 6.5.

@ryanflorence
Copy link
Member

ryanflorence commented Dec 9, 2022

We can actually merge this before the router dep is updated, only the documentation needs to be delayed. IF somebody uses the syntax they'll get route paths that don't work, that's all. Which you can already do in remix config route("lol?/oops?:$%#@%").

I'd say merge it so that it's done. I'll add an issue to document it in the release where it works.

@pcattori
Copy link
Contributor

pcattori commented Dec 9, 2022

We can actually merge this before the router dep is updated, only the documentation needs to be delayed. IF somebody uses the syntax they'll get route paths that don't work, that's all. Which you can already do in remix config route("lol?/oops?:$%#@%").

I'd say merge it so that it's done. I'll add an issue to document it in the release where it works.

If we do merge it now, might be best to edit the changeset to make it clear that this feature isn't ready for use yet

@pcattori pcattori merged commit d7efede into remix-run:dev Dec 9, 2022
@pcattori
Copy link
Contributor

pcattori commented Dec 9, 2022

I'm going to update the changeset to clear that^ up in a separate PR.

@kiliman
Copy link
Collaborator

kiliman commented Dec 13, 2022

BTW: Are optional segments a router thing? Or do I need to add support for that in the flat routes convention?

@brophdawg11
Copy link
Contributor

They are a router thing but we need any route files compilation methods to compile a file-name-syntax (/my/(optional)/path in Remix) down to the router path syntax (/my/optional?/path).

@kiliman
Copy link
Collaborator

kiliman commented Dec 13, 2022

I see. Thanks. So as long as I generate the correct route path, I don't have to worry about expanding the optional routes to multiple routes, that's done by the router, correct?

@kiliman
Copy link
Collaborator

kiliman commented Dec 13, 2022

I've added optional routes support to flat routes:

<Routes>
  <Route file="root.tsx">
    <Route file="routes-flat/_public.tsx">
      <Route path="optional?" file="routes-flat/_public.(optional).tsx" />
      <Route path="about" file="routes-flat/_public.about.tsx" />
      <Route path="contact.jpg" file="routes-flat/_public.contact[.jpg].tsx" />
    </Route>
    <Route index file="routes-flat/index.tsx" />
    <Route path="test/*" file="routes-flat/test.$.tsx" />
    <Route path="users" file="routes-flat/users.tsx">
      <Route path="/:userId" file="routes-flat/users.$userId.tsx" />
      <Route path="/:userId/edit" file="routes-flat/users.$userId_.edit.tsx" />
    </Route>
  </Route>
</Routes>

pcattori added a commit that referenced this pull request Dec 16, 2022
* fix(remix-dev): convert `config.appDirectory` to relative unix path (#4709)

* fix(remix-dev): convert appDirectory to unix style for fast-glob

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: relative path

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: add test

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* fix test

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* fix: typo

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: update test

Signed-off-by: Logan McAnsh <logan@mcan.sh>

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: add changeset for #4709 (#4718)

* ci: add typechecking for deno (#4715)

* ci: add typechecking for deno

* ci: install deno for integration tests

* chore: format

* chore: format

* fix: Firefox LiveReload (#4725)

Firefox infinitely reloads the page as long as `<LiveReload>` is rendering.

Closes #4692

* fix(remix-dev): allow defining multiple routes for the same route module file (#3970)

* Allow multiple routes for same route module

* Update packages/remix-dev/config/routes.ts

Co-authored-by: Andrew Leedham <AndrewLeedham@outlook.com>

* Update routes.ts

- Better name for automated ID variable;
- Small adjust in `id` option comment;

* - Removing redundant IF

* Update routes.ts

Revert complex custom ID in routes

* Non unique custom routes ID error and test

* Update assets.ts

Trying to solve a conflict

* Revert "Update assets.ts"

This reverts commit 2064c57.

* Error on collisions with non-custom routeIds

* Create big-spoons-grab.md

Co-authored-by: Andrew Leedham <AndrewLeedham@outlook.com>
Co-authored-by: Matt Brophy <matt@brophy.org>

* feat: Allow pass-through script props in `ScrollRestoration` (#2879)

* ci(nightly): add deno for typechecking deno files (#4738)

* ci: fix race condition writing globals.d.ts shim (#4717)

Co-authored-by: Chance Strickland <hi@chance.dev>

* chore: bump @playwright/test to latest (#4749)

Signed-off-by: Logan McAnsh <logan@mcan.sh>

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* Fix 4199: TypedResponse allows incompatible types (#4734)

* Fixes #4199: Do not allow assignment of incompatible TypedResponses

* Add myself to contributors.yml

* Create light-sheep-give.md

* slight changeset tweak

* additional changeset tweaks

Co-authored-by: Pedro Cattori <pcattori@gmail.com>

* chore: format

* test: add transition integration tests (#4739)

test: useTransition to wait for states

This approach could probably be applied across other flakey tests and could also be documented as a good approach of if there is user-feedback for a specific action when running integration tests.

* feat: testing helpers (#4539)

Co-authored-by: James Restall <james.restall@gmail.com>
Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: format

* chore(remix-testing): update dependencies (#4756)

* fix(remix-testing): fix deps (#4757)

* Fix deps for new remix-testing package

* fix lint

* Remove ENABLE_REMIX_ROUTER flags (#4732)

* chore: add `@remix-run/testing` to changesets (#4781)

* chore: add `@remix-run/testing` to changesets

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: update `ADDING_A_PACKAGE.md`

Signed-off-by: Logan McAnsh <logan@mcan.sh>

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* refactor(remix-react): upgrade Remix to `react-router-dom@6.4` (non-data-router) and drop `history` (#4731)

* Bump remix to react-router-dom@6.4.4 (#4668)

Co-authored-by: Mehdi Achour <machour@gmail.com>

* Bump remix to RR 6.4.4 and drop history dependency (#4702)

Co-authored-by: Mehdi Achour <machour@gmail.com>

* ci(nightly): move git operations after build (#4797)

* ci(nightly): move git operations after build

when adding deno typechecking (#4715), nightly now refers to the version we just calculated which hasnt been published when trying to build and typecheck

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* ci: update tmp branch name

Signed-off-by: Logan McAnsh <logan@mcan.sh>

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* perf(remix-dev): Optimize `parentRouteId` lookup in `defineConventionalRoutes` (#4538)

* Use object for parentRouteId lookup

* Sign CLA

* Move parentRouteId logic to a separate function

* Update test fixture path

* chore: format

* chore(dev): add changeset for PR #4538 (#4800)

* chore: format

* refactor(remix-react): use `react-router-dom` import instead of `react-router` (#3325)

* chore: manually bump remix-testing version due to not being on main just yet

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* fixup! chore: manually bump remix-testing version due to not being on main just yet

* fixup! chore: manually bump remix-testing version due to not being on main just yet

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: unify error usage (#4696)

* chore: format

* Add fetcher state/type tests (#4803)

* chore(deps): bump esbuild to latest (#4754)

* chore: add invariant instead of using `!`

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore(deps): bump esbuild to latest

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* Create fresh-shrimps-join.md

Signed-off-by: Logan McAnsh <logan@mcan.sh>
Co-authored-by: Pedro Cattori <pcattori@gmail.com>

* test(integration): close server synchronously  (#4785)

* chore: normalize afterAll

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* test: close server synchronously

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: appFixture.close isnt async anymore

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* Update integration/helpers/create-fixture.ts

Co-authored-by: Pedro Cattori <pcattori@gmail.com>

Signed-off-by: Logan McAnsh <logan@mcan.sh>
Co-authored-by: Pedro Cattori <pcattori@gmail.com>

* feat: remix optional segments (#4706)

* feat: transform optional routes from remix to react router

* Add to contributors

* Add changeset

* fix(optional-segments): fix escaping of parenthesis

* small function fix

* Update packages/remix-dev/__tests__/routesConvention-test.ts

Co-authored-by: Pedro Cattori <pcattori@gmail.com>

Co-authored-by: Pedro Cattori <pcattori@gmail.com>

* chore: format

* chore: edit the optional segments changeset (#4815)

to make it clear that Remix won't support optional segments until integrated with React Router 6.5

* chore: format

* docs: rearrange docs, give everything its own page (#4821)

* chore: format

* fix: wrong parentheses in the optional segments changeset (#4825)

* fix: wrong parentheses in the optional segments changeset

* add: akamfoad to the contributers

* ci(nightly): add workflow_call for testing purposes

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* Revert "ci(nightly): add workflow_call for testing purposes"

This reverts commit f3fa77e.

* chore(scripts): Use relaxed peer dependencies to avoid triggering major version bumps (#4736)

- Add script to bump peer deps with changesets version

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Add integration tests for request structures (#4829)

* fix(remix-dev): resolve asset entry full path to support mono-repo import of styles (#4855)

* chore: have eslint report unused eslint comments (#4863)

* chore: have eslint report unused eslint comments

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: remove additional comment

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* perf(remix-architect,remix-netlify): improve performance of `isBinaryType` (#4761)

Co-authored-by: Logan McAnsh <logan@mcan.sh>
Co-authored-by: Pannatier Guillaume <Guillaume.Pannatier@hopitalvs.ch>

* chore: format

* chore(remix-testing): remove internal installGlobals (#4755)

* chore: remove internal installGlobals

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* chore: add README

Signed-off-by: Logan McAnsh <logan@mcan.sh>

* Create quick-cats-fix.md

* Update .changeset/quick-cats-fix.md

Co-authored-by: Michaël De Boey <info@michaeldeboey.be>

* chore(deps): remove jsdom and happydom from devDependencies

Signed-off-by: Logan McAnsh <logan@mcan.sh>

Signed-off-by: Logan McAnsh <logan@mcan.sh>
Co-authored-by: Michaël De Boey <info@michaeldeboey.be>

* fix(dev): build js modules for ts->js conversion

The TS->JS migration was removed from the CLI codemod options, but still
used for TS->JS conversion when creating a new Remix project from the
CLI. The TS modules responsible for the TS->JS conversion were
incorrectly removed from the Rollup build, resulting in the
corresponding built JS modules being absent. That caused the CLI to
error when trying to perform TS->JS conversion. This changes
reintroduces the wiring to build the modules responsible for the TS->JS
conversion.

Fixes #4854

Signed-off-by: Logan McAnsh <logan@mcan.sh>
Co-authored-by: Logan McAnsh <logan@mcan.sh>
Co-authored-by: Matt Kane <m@mk.gg>
Co-authored-by: Remix Run Bot <hello@remix.run>
Co-authored-by: Chance Strickland <hi@chance.dev>
Co-authored-by: Ryan Florence <rpflorence@gmail.com>
Co-authored-by: Lucas Ferreira <panchorf@gmail.com>
Co-authored-by: Andrew Leedham <AndrewLeedham@outlook.com>
Co-authored-by: Matt Brophy <matt@brophy.org>
Co-authored-by: dabdine <1955040+dabdine@users.noreply.github.com>
Co-authored-by: Jacob Ebey <jacob.ebey@live.com>
Co-authored-by: James Restall <james.restall@gmail.com>
Co-authored-by: Michaël De Boey <info@michaeldeboey.be>
Co-authored-by: Mehdi Achour <machour@gmail.com>
Co-authored-by: Dylan Markow <dylan@dylanmarkow.com>
Co-authored-by: Daniel Rios <ieldanr@gmail.com>
Co-authored-by: Akam Foad <41629832+akamfoad@users.noreply.github.com>
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
Co-authored-by: Guillaume Pannatier <guillaume.pannatier@gmail.com>
Co-authored-by: Pannatier Guillaume <Guillaume.Pannatier@hopitalvs.ch>
@pcattori pcattori mentioned this pull request Dec 22, 2022
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants