realias
v0.1.3
Published
Rewrite relative imports to the most specific tsconfig path alias and re-alias existing ones when a better match is added. Lightweight bash CLI, no Node.js runtime needed.
Maintainers
Readme
realias
Rewrite relative imports in TypeScript/JavaScript projects to the most specific
path alias from your tsconfig.json — and re-alias existing aliased imports
when a better match is added. Lightweight CLI: pure bash + perl, no Node.js
runtime required.
Why
You add @components/* to tsconfig.json#compilerOptions.paths, but your
codebase is still full of ../../../components/Button. Or you add a more
specific @v2/* alias and want existing @components/v2/... imports to use
it. realias walks the project, resolves each import to an absolute path, and
rewrites it to the shortest/most-specific alias available.
Install
# global — installs the `realias` command on your $PATH
npm install -g realias
# project — runnable via npx / npm scripts
npm install -D realiasRequirements:
bash,perl,find,awk,sed. All preinstalled on macOS and every mainstream Linux distro. No Node.js runtime needed at execution time — Node is only used by npm for installation.
Quick start
From any directory inside your project:
realiasThat's it. realias walks upward to find the nearest tsconfig*.json that
declares compilerOptions.paths, reads the aliases, and rewrites imports
under that tsconfig's directory.
How it works
For each .ts / .tsx / .js / .jsx file:
- Read the import block at the top of the file.
- For each
import … from '<path>':- If
<path>starts with../→ resolve to an absolute path. - If
<path>starts with./→ skipped by default (opt in with-a). - If
<path>already uses an alias → expand it to absolute so a more specific alias can be considered. - Otherwise (bare module like
react,lodash) → leave alone.
- If
- Look up the absolute path in the alias table, picking the most specific
match (e.g.
@v2/foowins over@components/v2/foo). - If the result differs from the current path, rewrite the line.
- Stop scanning the file at the first non-empty, non-
importline. The rest of the file is byte-copied through, never parsed. - Files with no changes are never written.
CLI
realias [options]
Options:
-c, --tsconfig FILE Explicit tsconfig file (skips auto-discovery).
-r, --root DIR Directory to scan (default: dirname of tsconfig).
-e, --exts "a b c" Space-separated extensions
(default: "ts tsx js jsx").
-s, --skip "a b c" Space-separated directory names to prune
(default: "node_modules .git").
-a, --all-relative Also rewrite imports that start with `./`.
By default only `../`-style imports are touched.
-f, --full-scan Scan the whole file for imports instead of
stopping at the first non-import line. Useful
for files with imports mixed below other code
(lazy imports, post-directive imports, etc.).
-p, --pattern REGEX Extra bash-ERE regex whose single capture group
is the path to rewrite. Catches things the
import matcher misses (jest.mock, require,
dynamic import). Repeatable. Implies a
full-file scan for pattern matches.
-v, --verbose Print every file scanned and every alias loaded.
-h, --help Show this help.Every flag can also be supplied via environment variable
(TSCONFIG, ROOT_DIR, FILE_EXTS, SKIP_DIRS, INCLUDE_SIBLINGS,
FULL_SCAN, VERBOSE). Flags win when both are set.
Examples
# Default run from project root
realias
# Use a specific tsconfig
realias -c tsconfig.build.json
# Limit scan to one folder
realias -r src/components
# Rewrite ./sibling imports too
realias -a
# Scan every line, not just the top import block
realias -f
# Rewrite paths in jest.mock(...) and require(...) too
realias -p "jest\.mock\(['\"]([^'\"]+)['\"]" \
-p "require\(['\"]([^'\"]+)['\"]"
# Only TypeScript files; skip extra dirs
realias -e "ts tsx" -s "node_modules .git dist coverage"
# Verbose progress
realias -vAdd to package.json scripts
{
"scripts": {
"imports:fix": "realias",
"imports:fix:all": "realias -a"
}
}What gets rewritten
Given tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"],
"@v2/*": ["src/components/v2/*"]
}
}
}| Before | After |
| ----------------------------------------------- | ----------------- |
| import X from '../../components/Button' | '@components/Button' |
| import X from '../../components/v2/Modal' | '@v2/Modal' |
| import X from '@components/v2/Modal' | '@v2/Modal' |
| import X from './sibling' (default) | unchanged |
| import X from './sibling' (-a) | '@components/Button/sibling' (if applicable) |
| import X from 'react' | unchanged |
Custom patterns (-p)
The default matcher only handles import … from '<path>'. Anything else —
require('<path>'), jest.mock('<path>'), import('<path>'),
vi.mock('<path>'), loadable(() => import('<path>')) — needs an
explicit pattern.
Each -p value is a bash extended-regex (ERE) with exactly one capture
group around the path. The capture is fed through the same resolver as
ordinary imports (../, ./ with -a, existing aliases, stale-sigil
salvage), then substituted back into the original match.
| Use case | Pattern |
| -------------------- | -------------------------------------- |
| require('x') | require\(['\"]([^'\"]+)['\"] |
| jest.mock('x') | jest\.mock\(['\"]([^'\"]+)['\"] |
| vi.mock('x') | vi\.mock\(['\"]([^'\"]+)['\"] |
| Dynamic import('x')| import\(['\"]([^'\"]+)['\"] |
Notes:
-pautomatically scans every line of every file — you don't also need-f.- One match per pattern per line. If you have two
require()calls on the same line, the first wins on this pass; rerun to catch the second. - Bash ERE only — no
\d,\b, lookarounds, etc. - Paths inside the captured group must not contain glob characters (
*,?,[,]), which is the case for every real import path.
Caveats
- By default only the top import block is rewritten. Once a non-import
line is hit, the rest of the file is copied through untouched. Pass
-fto scan every line forimportstatements, or-pto rewrite paths in arbitrary call shapes. - Comment stripping in
tsconfig.jsonis JSONC-aware (handles//,/* */, and trailing commas) but assumes no}characters inside thepathsblock.
License
MIT
