Problem
I’ve just started exploring Angular 2+ and the
Angular CLI. I have custom libraries in my
application, and wish to avoid using stupid relative paths like
../../../common/mylib.ts
in imports. This post documents what is needed to get
both WebStorm (or IDEA) and Angular CLI resolving these paths cleanly.
The project structure is as follows:
// Various files not shown for brevity
ui (-> ng new ui)
├── node_modules/
├── package.json
├── src
│ ├── app/*
│ ├── index.html
│ ├── main.ts
│ ├── tsconfig.app.json
│ └── typings.d.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
Example modules/files of interest:
///
/// ui/src/app/common/illegalargumenterror.model.ts
///
export class IllegalArgumentError extends Error {
constructor(msg: string) {
super(msg);
}
}
///
/// ui/src/app/risk-reward-calculator/risk-reward.model.ts
///
import { IllegalArgumentError } from "@app/common/illegalargumenterror.model";
// Much nicer than
// import { IllegalArgumentError } from "../common/illegalargumenterror.model";
// especially for deeper hierarchies where this noise builds up.
export class RiskRewardItem {
...
constructor(rewardFactor: Big, entryPrice: Big, stopLossPrice: Big) {
if (entryPrice.lte(0)) {
throw new IllegalArgumentError(`Non-positive entry...`);
}
}
}
Solution
We are primarily interested in the app
module right now, handled by the
following files:
ui/src/tsconfig.app.json
- And the common configuration,
ui/tsconfig.json
; note that the app fileextends
this common file.
To get custom module resolution working on Angular CLI (including ng serve
),
we change ui/src/tsconfig.app.json
, adding compilerOptions.paths
(which relies on compilerOptions.baseUrl
— should already be there):
/// ui/src/tsconfig.app.json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": [],
// BEGIN
// Note that the paths are relative to 'baseUrl'
"paths": {
"@app/*": ["app/*"]
}
// END
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
WebStorm (as at Release 2017.2), however is only aware of ui/tsconfig.json
(see this), and just
modifying the above leaves the IDE TypeScript Language Service complaining with
an error in the editor, so we also update ui/tsconfig.json
.
/// ui/tsconfig.json - note compilerOptions.baseUrl and compilerOptions.paths
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
],
// BEGIN
// Note that the paths are relative to 'baseUrl'
"baseUrl": "./",
"paths": {
"@app/*": ["src/app/*"]
}
// END
}
}
You’ll need additional entries for other modules/tests if you require, but the idea is the same.
Limitations
Initially, I found that I could not navigate to the definition of modules imported in this custom manner. Doing a WebStorm Invalidate Caches and Restart seems to have sorted this out.
Unfortunately, refactors (e.g. renames), don’t seem to be propagating cleanly. I’ve tried using both just the TypeScript Language Service, and using IDEA’s compiler.
I’ll update the post if I figure out how to get WebStorm’s key features working with non-relative/custom-resolution imports.
Alternatively, contact me if you find out!