import aliases
rule
use ~/* for one package's authored source root: "~/*": ["./src/*"].
relative imports are for immediately neighboring files; package imports cross
workspace boundaries.
why
an import's first character discloses its ownership boundary. one alias per package keeps that classification true everywhere, so moving a file or packaging code for independent use cannot hide a dependency behind an overlapping alias or a relative escape. the risk avoided: two paths to the same module that update independently and then diverge.
workflow
- configure the alias in the package
tsconfig.json. - configure the bundler or test resolver to match it.
- use
~/for package-internal imports and@scope/packagefor public sibling-package APIs.
why one alias is enough
the alias answers exactly one question: "where is this package's authored source root?" it is not a second package manager or a way to hide a workspace boundary.
| import form | meaning | prohibited use |
|---|---|---|
./ or ../ | immediately neighboring implementation | climbing across an architectural area to avoid a real module boundary. |
~/ | authored source owned by this package | reaching into a sibling package. |
@scope/package | public API of a sibling package | importing its src, private output, or build internals. |
failure mode of overlapping aliases or relative escapes: with both @app/utils
and ~/utils pointing to nearby folders, a search for utils returns both. an
agent moving formatDate updates one path, creating a duplicate; a later
breaking change updates only one path, so one caller serves the old broken
version while another serves the fixed one. a codemod or IDE refactor picks one
path arbitrarily, silently creating divergence. a relative escape like
../../../services hides a package-boundary crossing — a future agent packaging
the code for independent use misses the hidden dependency.
verification
resolve the alias from the compiler, the runtime bundler, and the test runner; all three must point at the same source root. after a workspace move, search for deep cross-package paths — they are a hidden alias that bypasses ownership.
do not
- point
~outside the package. - import another workspace's
srcdirectory. - add multiple aliases for the same source root.
good and bad
bad
import { readInvoice } from "@app/services/invoice";import { InvoiceSchema } from "@shared/schemas/invoice";import { parseCursor } from "../../../../shared/cursor";
the aliases overlap and the relative import escapes an architectural boundary, so import syntax no longer reveals ownership: the module is reachable through two independent paths that update separately — a fix or rename lands on one while the other keeps serving the old bytes.
good
import { readInvoice } from "~/repositories/billingRepository";import { InvoiceSchema } from "~/schemas/billing/invoice";import { parseCursor } from "@scope/base/cursor";
~/ stays inside the package; the scoped import crosses a published workspace
boundary. the first character of every import classifies its ownership, so
reviewers and agents audit dependency direction without resolving anything —
file moves stay safe (the alias travels with the package), workspace extractions
stay mechanical, and hidden cross-boundary dependencies cannot appear by
accident.