@prometx/trpc-navigation-plugin
Version:
TypeScript Language Service Plugin that fixes broken 'go to definition' for tRPC when using declaration emit
264 lines (215 loc) ⢠9.33 kB
Markdown
# tRPC Navigation Plugin
A TypeScript Language Service Plugin that fixes broken "go to definition" navigation for tRPC procedures when using TypeScript's declaration emit.
š **[View Demo Repository](https://github.com/ebg1223/trpc-plugin-demo)** - See the plugin in action with a T3 Turbo monorepo
## Problem
When using tRPC with TypeScript's declaration emit (`declaration: true`), there's a TypeScript bug that completely breaks "go to definition" functionality. When you try to Cmd+Click on a tRPC procedure call like `api.users.getUser.useQuery()`, TypeScript only takes you to the root router definition - it can't navigate any deeper into nested routers or the actual procedure implementations.
This is caused by how TypeScript handles the complex type inference in tRPC's router chains when generating `.d.ts` files.
## Solution
This plugin bypasses TypeScript's broken type-based navigation by using a configured router location. When you Cmd+Click on a tRPC procedure, the plugin intercepts the navigation request and takes you directly to the procedure implementation in your source code.
## Features
- **Fixes Broken Navigation**: Restores "go to definition" functionality that TypeScript's declaration emit breaks
- **Direct Source Navigation**: Takes you to the actual implementation code, not type definitions
- **Simple Configuration**: Just specify your router location - no complex setup
- **Intelligent Client Detection**: Automatically detects any tRPC client variable (api, trpc, client, etc.)
- **useUtils Support**: Full navigation support for `useUtils()` variables
- **Cross-Package Support**: Works seamlessly in monorepos
- **Fast Performance**: Direct navigation without complex type resolution
## When You Need This Plugin
You need this plugin if:
- Your project uses tRPC
- You have `declaration: true` in your tsconfig.json (for generating .d.ts files)
- "Go to definition" on tRPC procedures doesn't work or takes you to the wrong place
You DON'T need this plugin if:
- You're not using TypeScript's declaration emit
- Your "go to definition" already works correctly
## Installation
> ā ļø **IMPORTANT: VS Code & Editor Setup**
>
> **You MUST use the workspace TypeScript version, not the built-in version!**
>
> In VS Code, Cursor, or other VS Code-based editors:
> 1. Open any TypeScript file in your project
> 2. Click the TypeScript version number or `{}` in the bottom status bar
> 3. Select "Select TypeScript Version..."
> 4. Choose "Use Workspace Version" (NOT "Use VS Code's Version")
>
> Without this step, the plugin will not be detected or loaded!
### Setup Steps
1. Install the plugin:
```bash
npm install --save-dev trpc-navigation-plugin
# or
yarn add -D trpc-navigation-plugin
# or
bun add -D trpc-navigation-plugin
```
2. Configure in your `tsconfig.json` with your router location:
```json
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/server/api/root.ts",
"variableName": "appRouter"
}
}
]
}
}
```
3. **Switch to workspace TypeScript** (see important note above)
4. Restart the TypeScript server: `Cmd+Shift+P` ā "TypeScript: Restart TS Server"
## Configuration
The plugin requires you to specify where your tRPC router is defined:
```json
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/server/api/root.ts", // Path to your router file
"variableName": "appRouter" // Name of your router variable
}
}
]
}
}
```
### Configuration Options
- `router.filePath` (required): Path to the file containing your main tRPC router
- Can be relative (resolved from project root) or absolute
- Example: `"./src/server/api/root.ts"`
- `router.variableName` (required): Name of your router variable in that file
- Example: `"appRouter"`, `"router"`, `"mainRouter"`
- `patterns` (optional): Customize pattern detection
- `procedureTypes`: Procedure types to detect (default: `['query', 'mutation', 'subscription']`)
- `routerFunctions`: Router function names (default: `['router', 'createTRPCRouter', 'createRouter', 't.router']`)
- `clientInitializers`: Client initialization patterns (default: `['createTRPC', 'initTRPC', 'createTRPCClient']`)
- `utilsMethod`: Name of the utils method (default: `'useUtils'`)
- `fileExtensions` (optional): File extensions to process
- Default: `['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs']`
- Add custom extensions if needed
### Monorepo Setup
In monorepos, add the plugin to each package that uses tRPC:
```json
// packages/api/tsconfig.json - where router is defined
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/router/index.ts",
"variableName": "appRouter"
}
}
]
}
}
// packages/web/tsconfig.json - uses tRPC through @my/api package
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "../api/src/router/index.ts",
"variableName": "appRouter"
}
}
]
}
}
```
### Optional: Enable Verbose Logging
For debugging, you can enable verbose logging:
```json
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/server/api/root.ts",
"variableName": "appRouter"
},
"verbose": true // Enable detailed logging
}
]
}
}
```
### Advanced Configuration
Customize detection patterns for non-standard tRPC setups:
```json
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/server/api/root.ts",
"variableName": "appRouter"
},
"patterns": {
"procedureTypes": ["query", "mutation", "subscription", "action"],
"routerFunctions": ["router", "createTRPCRouter", "myCustomRouter"],
"clientInitializers": ["createTRPC", "initTRPC", "setupTRPC"],
"utilsMethod": "useUtils" // or "useContext" for older tRPC versions
},
"fileExtensions": [".ts", ".tsx", ".mts", ".cts"]
}
]
}
}
```
## Automatic tRPC Client Detection
The plugin intelligently detects any tRPC client variable, regardless of naming:
```typescript
// All of these work automatically:
export const api = createTRPCReact<AppRouter>();
export const trpc = createTRPCNext<AppRouter>();
export const client = createTRPCProxyClient<AppRouter>();
export const myCustomName = initTRPC.create();
// In your components:
api.users.getUser.useQuery(); // ā Works
trpc.users.getUser.useQuery(); // ā Works
client.users.getUser.query(); // ā Works
myCustomName.users.getUser.query(); // ā Works
// useUtils variables also work:
const utils = api.useUtils();
const apiCtx = trpc.useUtils();
utils.users.getUser.fetch(); // ā Works
apiCtx.users.getUser.invalidate(); // ā Works
```
## How It Works
1. **Router Configuration**: You specify where your tRPC router is defined
2. **Client Detection**: The plugin detects when you click on a tRPC client call
3. **Direct Navigation**: Uses the configured router location to navigate directly to procedures
4. **Path Resolution**: Follows the navigation path through nested routers to find the target
5. **Contextual Navigation**: Click on different parts for different results:
- `api.billing.claims` - clicking "billing" goes to billing router
- `api.billing.claims` - clicking "claims" goes to claims procedure/router
## Troubleshooting
If navigation isn't working:
1. **Verify TypeScript Version**: Make sure you're using the workspace TypeScript version! (See installation section)
2. **Check Configuration**: Ensure your router config points to the correct file and variable name
3. **Verify Router Export**: Make sure your router is exported or declared in the specified file
4. **Check TS Server Logs**: Look for `[TRPC-Nav]` entries in the TypeScript Server logs
5. **Enable Verbose Logging**: Add `"verbose": true` to see detailed debug information
6. **Restart TS Server**: After configuration changes: `Cmd+Shift+P` ā "TypeScript: Restart TS Server"
Common issues:
- **Plugin not loading**: You're using VS Code's built-in TypeScript instead of workspace version
- Wrong file path: Paths are resolved from the project root (where tsconfig.json is)
- Wrong variable name: The variable name must match exactly (case-sensitive)
- Router not found: Make sure the router is a top-level export or variable declaration
## Technical Details
- Uses TypeScript's Language Service API for navigation interception
- Directly navigates to configured router location without complex type resolution
- Works around TypeScript's navigation bug without modifying your build process
- Compatible with tRPC v10+ and v11 that use the standard router pattern
- Supports monorepo setups with relative or absolute paths
- Does not interfere with TypeScript's type checking or declaration emit