Life Codecs @ NamingCrisis.net

Ruminations. Reflections. Refractions. Code.

Oct 29, 2017 - Software dev Angular WebStorm

Non-Relative Module Imports on Angular 2+ CLI and WebStorm

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 file extends 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!