Convert TypeScript path aliases (tsconfig.json paths like ~/*) to native Node.js subpath imports (package.json imports with
Convert a project from TypeScript path aliases (tsconfig.json) to native Node.js subpath imports (package.json with # prefix mapping to project root).
Read tsconfig.json to identify existing path aliases in compilerOptions.paths
Update package.json with an imports field:
#/* to map to project root: "#/*": "./*"#/app/..., #/prisma/..., etc.Update tsconfig.json paths to match:
"~/*": ["./app/*"] to "#/*": ["./*"]baseUrl if presentpathsConvert all imports in source files:
~/)
#/app/)from 'app/foo' without ~/)
~/ replacementfrom 'app/ or from "app/ and convert to #/app/../ imports to use #/ (e.g., from '../_app.foo/route' → #/app/routes/_app.foo/route)../ paths inside import() calls that run in the browserimport('../mocks/browser') as-is in client entry filesFix imports that should use ./ instead of #/ (REQUIRED - do not skip):
The rule: Use ./ for same-directory imports and for root-level files importing subdirectories. Use #/ only for cross-directory imports.
a) Root-level app files (files directly in app/ like root.tsx, entry.client.tsx):
./ for app subdirectories, not #/app/grep -l "from '#/app/" app/*.{ts,tsx} 2>/dev/null#/app/core/utils → ./core/utilsb) Same-directory imports (files importing from their own directory):
#/find app -name '*.ts' -o -name '*.tsx' | while read file; do
dir=$(dirname "$file" | sed 's|^\./||')
# Convert dir path to import pattern (e.g., app/core -> #/app/core/)
pattern="from ['\"]#/$dir/"
if grep -qE "$pattern" "$file" 2>/dev/null; then
echo "$file imports from own directory using #/"
grep -E "$pattern" "$file"
fi
done
#/app/<same-dir>/foo → ./fooRemove unnecessary packages (if present):
npm uninstall vite-tsconfig-paths tsconfig-pathstsconfigPaths() from vite.config.ts if presentVerify the conversion:
Format the code:
npm run format to format the codeBefore (tsconfig.json):
"paths": { "~/*": ["./app/*"] }
After (package.json):
"imports": { "#/*": "./*" }
After (tsconfig.json):
"paths": { "#/*": ["./*"] }
Before (source):
import { db } from '~/core/db.server';
After (source, cross-directory):
// In app/routes/notes.tsx importing from app/core/
import { db } from '#/app/core/db.server'; // Use #/ not ../
After (source, same directory):
// In app/auth/auth.server.ts importing from app/auth/types.ts
import type { User } from './types'; // Use ./ for same dir
imports: Runtime/build resolution (Node.js, Vite)paths: TypeScript type checking and IDE support (TS bundler moduleResolution doesn't fully support wildcard subpath imports)