Search code examples
node.jstypescriptvisual-studio-codetsconfig-paths

Typescript path aliases not resolved correctly at runtime


I'm running VS Code and I am currently trying to set up some aliases on my typescript project.

My dev setup rest on nodemon and ts-node, the code gets compiled to a dist folder.

So far, I succeeded to get Typescript Hero to manage the import with aliases:

alias resolution vscode

So far, my folder structure is:

.
└─┬ src
  ├──modules
  ├────Category
  ├────Ressource
  ├──shared
  ├────debug

// tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "pretty": true,
        "sourceMap": true,
        "target": "es6",
        "outDir": "./dist",
        "baseUrl": "./src",
        "paths": {
            "@shared/*": [
                "shared/*"
            ],
            "@modules/*": [
                "modules/*"
            ]
        },
        "resolveJsonModule": true,
        "esModuleInterop": true
    },
    "include": [
        "src/**/*.ts"
    ],
    "exclude": [
        "node_modules",
        "**/*.spec.ts",
        "**/*.test.ts",
    ]
}

And this is the first alias import that fails.

//Server.ts file
import Print from '@shared/debug/Print.class';
import App from './App';

const MyApp: App = new App();

MyApp.ExpressApp.listen(MyApp.Config.ExpressPort, () => {
    Print.Log('Express server listening on port ' + MyApp.Config.ExpressPort);
});

However, I get an error: "Cannot find module '@shared/debug/Print.class'" on "cross-env NODE_ENV=development nodemon ts-node ./src/server.ts". CMD logs

And this is where I stand.

Now, I've read some Q&A on SO, and it seems that even if I managed to make the aliases works while in dev, it would fail in production, as I'm running from Typescript src folder and my deliverable are built in dist ? If so, is there any way to remediate ? Many thanks


Solution

  • The problem was situated on node path aliases resolution on runtime. Even if the typescript was executed on runtime by ts-node, the aliases couldn't be resolved by node as-is. (I think)

    But this was only the tip of the iceberg. I'd encounter it then later with my jest setup, and on JS runtime.

    I had to find a way, for every runtime I had, to interpret my aliases. There was a few npm packages but a lot of them required more declarations.

    And I didn't want to declare my aliases in every config files I'd have and only depend on my tsconfig file.

    After much testings, there was only two node modules to install tsconfig-paths for typescript runtime execution on ts-node. And @ef-carbon/tspm to convert my aliases to the build destination.

    npm i -D tsconfig-paths @ef-carbon/tspm
    

    For ts-node, the script was modified as :

    ts-node -r tsconfig-paths/register ./src/server.ts
    

    For your js compiled, you only have to run :

    ef-tspm
    

    For jest, ts-jest is needed, I already had it but it wasn't properly configured. I used the built-in helper to set up my paths. My jest config file now looks like this :

    //jest.config.js
    const { pathsToModuleNameMapper } = require('ts-jest/utils');
    const { compilerOptions } = require('./tsconfig');
    module.exports = {
        roots: ['<rootDir>/src'],
        globals: {
            'ts-jest': {
                tsConfig: 'tsconfig.json',
                diagnostics: {
                    warnOnly: true,
                },
            },
        },
        clearMocks: true,
        coverageDirectory: 'coverage',
        testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
        moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'ts', 'tsx'],
        testEnvironment: 'node',
        moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
        pathToJest: 'npm test',
        preset: 'ts-jest',
        testMatch: null,
    };
    

    Here is, how my scripts look like in my package.json

      "scripts": {
        "dev:ts": "cross-env NODE_ENV=development nodemon",
        "dev:js": "cross-env NODE_ENV=development npm run start:js",
        "staging": "cross-env NODE_ENV=staging npm run start:js",
        "production": "cross-env NODE_ENV=production npm run start:js",
    
        "test": "cross-env NODE_ENV=testing jest --runInBand",
        "test:debug": "npm run test --detectOpenHandles",
    
        "start:js": "npm run build && nodemon --config nodemon-js.json",
        "build": "npm run compile && npm run post:compile && npm run copyAssets",
        "compile": "tsc",
        "post:compile": "ef-tspm",
        "copyAssets": "copyfiles -e ./src/**/*.ts -e ./src/**/*sample* -e ./src/**/*.json -u 1 ./src/**/* ./dist/"
      },
    

    Seeing how it goes, I'll probably add a grunt/gulp solution afterward. But for now, this is good enough.