Ir al contenido principal

Using paths in tsconfig.json

How frustrating can be managing a big Typescript project in which all the module paths are relative to the importing file, as the next example. Every time we move a file, and therefore its relative path changes regarding its imported modules, we must sacrifice time and patience fixing a train of dots and slashes that too many times are fixed by trial and error.
import {Request} from "express";
import {foo} from "../../../../types";
import {bar, baz} from "../../../../../myModules/bax";

Hopefully Typescript's tsconfig.json file allows specifying path aliases by setting the paths option. This option, together with the use of the npm package module-alias allows converting the previous mess into a piece of fine art:
import {Request} from "express";
import {foo} from "@types";
import {bar, baz} from "@myModules/bax";

As said the process implies not just configuring tsconfig.json but also using the package module-alias, as described below.

Tunning tsconfig.json paths

In the example above we are importing, always relative to our project root directory:
  • some types and functions from express, a module usually located in the directory node_modules
  • the foo type, defined in let's say the types/index.d.ts file
  • the functions bar and baz, defined in the file myModules/bax.ts
We can avoid that messy way of defining file locations by properly configuring tsconfig.json paths option in this way:
{
    "compilerOptions": {
    	"paths": {
            "*": ["node_modules/*"],
            "@types": ["types/index.d.ts"],
            "@myModules/*": ["src/myModules/*"]
        }
    }
}

With that configuration we can rewrite the first example in a much more manageable way:
import {Request} from "express";
import {foo} from "@types";
import {bar, baz} from "@myModules/bax";

Let's analyze how paths has been defined to achieve the above result:

// Path { "*": ["node_modules/*"] }
import {Request} from "express";
The first import is using the first path definition, that basically is stating that every module path ("*") must be prefixed with the string "node_modules/" ("node_modules/*").
This path definition is always created by default when generating a tsconfig.json file with the command tsc --init and allows us importing code from the installed modules without the need of always setting "node_modules/" in their path.

// Path { "@types": ["types/index.d.ts"] }
import {foo} from "@types"
The {foo} import module path is set to @types and therefore it will be computed as it was defined as "types/index.d.ts", ie, it behaves 100% like an alias.
Note that this path is not including an asterisk, so @types cannot be sufixed with additional strings. If you try to import a type from the path "@types/messyTypes" you will get an error, that could be solved by defining this path as the next one.

// Path { "@myModules/*": ["src/myModules/*"] }
import {bar, baz} from "@myModules/bax"
Finally, the functions {bar, baz} are being imported from "@myModules/bax", therefore matching the last path definition, that states that every partial occurrence of "@myModules/" will be replaced by "src/myModules/", hence being computed as "src/myModules/baz".
Note that since this path definition is using asterisks we can suffix additional paths to @myModules/, as in this case, where it is being sufixed with the file bax(.ts). This behaviour is different to that shown in the previous example, in which the asterisk was not used.

Using module-alias

Two of the examples above are special in some way: node_modules is a special directory for both Typescript and Javascript, and Typescript doesn't transpile the types. That means that you will not have any problem with paths "*" and "@types" when transpiling to Javascript.

However things are not the same with the rest of paths, as in this case "@myModules/*": When you transpile your code Typescript doesn't blame, but as soon as you try to run it you get an error like this:
Error: Cannot find module '@myModules/bax'

The reason is that, opposite to what any rationale person expected, Typescript doesn't resolve the paths into their real values during transpilation. You can check it going to your (usually) dist directory and checking the transpiled code to find that your paths are as you defined them, unmodified.

However you can solve that problem by using the npm package module-alias and then following the next steps.

First install the module-alias package by running this command:
npm install --save-dev module-alias

Second, define the module-alias aliases in your package.json file in this way:
"_moduleAliases": {
    "@myModules": "dist/myModules"
}
Note that you are using not src, as in tsconfig.json, but dist since the aliases refer to the code location once transpiled, usually stored in the dist directory.

Finally, register the path aliases in your application by adding this import in the first line of your startup file (usually index.ts):
import "module-alias/register";

Now the module paths will remain unchanged in the transpiled code, but you will get no error when running it.

Comentarios

Entradas populares de este blog

Linting C# in Visual Studio Code

Though very usual in programming environments as Javascript/Typescript, linting , or analyzing code for enforcing a set of coding style rules, is not usually present in the .NET based environments. Rule enforcing is really useful when working on team shared codebases in order to keep them coherent, what in last term reduces both development times and coding errors. A linting example Maybe a practical example would be helpful for explaining what  linting  is to the newcomers (feel free to go on if you aren't). Let's imagine you are a new member in a C# development team that has well established set of coding style rules. Instead (or apart) of putting them in a document, they've adopted a tool that checks these rules during the code building process. Your first code is such ambitious as this: namespace HelloWorld {      using System;      public class Program      {           public static void Main(string[] args)           {                Console

ESlint: Ignore unused underscore variables

Some naming conventions promote the use of the underscore character (" _ ") for those variables that must be declared but are not being used. One common case is that in which a function signature contains some variables that will not be used, as for instance the Express error handlers: app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); In the above example only the arguments err and res are being used, though all four must be defined in the handler signature. Thus, following the naming convention of using underscores for those unused variables, we could recode it as: app.use(function(err, _, res, __) { console.error(err.stack); res.status(500).send('Something broke!'); }); Though it makes the function more readable, it comes with a problem if using ESlint: it will blame by declaring unused variables. error '_' is defined but never used error '__' is define

Using Bitbucket app passwords with git on MacOS (OSX)

Learn how Bitbucket passwords are stored by git on MacOS.