react-router
react-router copied to clipboard
Bug: path aliases don't get correctly resovlved in `routes.ts`
TL;DR
All imports in routes.ts and all its direct or transitive imports have to be absolute or relative using ./ and ../ notation, path aliases like ~ and @ aren't resolved correctly.
The issue
Vite and TypeScript allows defining aliases to relatively access files without using ./ or ../ notation, this is pretty significant for complex apps at scale as imports point to different, sometimes deeply nested files.
One of the usecases that inspired React Router's config-based route pattern and routes.ts was this, for example if I want to make my apps modular or feature-based, I'd use high level folders to separate features, each of these feature might export their own routes, these routes need to be imported, and for the sake of readability and consistency, we use ~ or @ symbols as relative path aliases.
Also, sometimes, we need to import variables into the routes.ts file, for example when you want to create a unique id for a route and you don't want to duplicate code and future-proof your app, this too, would be imported using an aliased path.
These use-cases aren't currently handled correctly when the routes.ts is evaluated and executed.
Please checkout the #14090 and see the bug IRL
Reproduction
- Clone
remix-run/react-router - Checkout
akamfoad:routes-path-alias-issuebranch - Run
pnpm i cd playground/framework-spa- Run
pnpm run dev - You'll immediately get an error that
routes.tsis invalid
System Info
N/A
Used Package Manager
pnpm
Expected Behavior
For the app to work seamlessly regardless of usage of import aliases.
Actual Behavior
Error: Route config in "routes.ts" is invalid.
Where are these aliases defined?
I'l defer to @pcattori and @markdalgleish here if this is a bug or expected, but my hunch is that the processing of routes.ts happens as part of the processing of the reactRouter() plugin initialization and is thus before Vite has bootstrapped (and before it has processed the tsconfigPaths() plugin) so it can't understand the tsconfig aliases.
I'm pretty sure that if you use package.json path aliases (rather than tsconfig.json path aliases), then TS and Vite will both use those correctly.
@GabenGar Usually they're defined in tsconfig.json at compiler.Options.paths and inside the vite.config.ts the vite-tsconfig-paths library is used which will configure the same aliases for vite, so the alias works on the type-level as well as bundler-level.
Hmmm @pcattori @brophdawg11
Package path aliases could work, but the DX is not good honestly, t'd make the config scattered across many files, the beauty of this configuration is that we have a single source of truth ( tsconfig ) and it works seamlessly everywhere.
Since React Router is just a Vite plugin, it'd be helpful if the full vite experience is supported. Aliases are an important feature for our codebase, tbh we had this issue ever since RR has become just a vite plugin, it was fine at first but it slowly became a pain, especially that there is not clear message what is exactly the issue, since the path is correctly inferred in the editor but it fails when you start the server.
Even if its not possible to add support for this feature, maybe let's keep this open or add it to the roadmap and work on it in the future.
@akamfoad
Usually they're defined in
tsconfig.jsonat compiler.Options.paths and inside thevite.config.tsthevite-tsconfig-paths
Off to a great start with usually for something precise like module specifier resolution. What about unusual setups?
tsconfig.json by the way is not a real json file, it's more of a jsonc, so the standard JSON parser will choke on it. This config file can also extend from other config file which can extend from other config file... so the entire typescript config resolution procedure has to be ran just to get the paths.
vite.config.ts is not a javascript file, which means it has to be compiled by typescript first, exactly what the current vite setup does. Where does the config for this process come from? Do note it should also accept vite.config.mts, vite.config.cts, vite.config.mjs, vite.config.cjs and vite.config.js files and vite.config.js/vite.config.ts must also compile according to the value of "type" in package.json.
Even better, since it's a javascript file (in the end at least), it can have an unlimited amount of side effects to run before even getting to actual config. But also it depends on a specific plugin present and be added to the final config.
There isn't even a standard way to define aliases, the current ones:
~- a bash-only alias for home directory (doesn't make sense outside of unix-like terminal environments)@- a prefix reserved for scoped packages on npm
only persisted because some guy put them into a starter template and it propagated.
but the DX is not good honestly, t'd make the config scattered across many files
How is looking up package.json, a file in spec-compliant format and finite size, more "scattered" than parsing at least 2 files with vague rules and potentially infinite resolution time? Note that looking up package.json is required for parsing those files in the first place.
RR has become just a vite plugin
Citation needed. As per these blogposts: React Router and React Server Components: The Path Forward and Wake up, Remix!, RR will either become bundler-free or Django.
@GabenGar That kind of discussion isn't really productive to the conversation here. This is simply a matter of routes being allowed to use TS to resolve modules, which may be an impossibility with the current plugin architecture. Let's keep it to just that.
Related to this issue: Vite macros are not expanded either.
My use case is a modular app with a single configuration file that registers which modules are used. This lets me build multiple applications from the same codebase.
However, some of these modules need to import files that use the serverOnly$ macro from the vite-env-only package. If I then import routes from the same configuration file, the route config fails with unreplaced macro errors.
I can work around this by moving the route configuration into a separate file, but it would be nicer to keep everything in a single file that defines the behavior for the whole app.
Any workaround for this issue? I'm not able to use aliases in my route utils (functions used everywhere in my app) because of this behavior
I understand that it would be useful in certain cases if some plugins could be loaded, but in that case, where do you think it would be best to define the plugins to use when loading routes.ts?
I think it would be better to load only the plugins that are needed, rather than using the vite configuration file in the project as is, but that has the disadvantage of being defined in different places.