create-react-app-typescript icon indicating copy to clipboard operation
create-react-app-typescript copied to clipboard

Support for TypeScripts 'paths' option

Open the-mike opened this issue 6 years ago • 15 comments

I'm trying to implement alias for importing modules from root of my project. According to typescripts docs it can be done by adding following configuration to 'tsconfig.json' file

    "baseUrl": "./",
    "paths": {
      "~/*": ["./src/*"]

I get no Typescript compile error, but error from webpack i.e. Failed to compile.

./src/App.tsx Module not found: Can't resolve '~/components/forms' in '/Users/mike/dev/js/react-ts/src'

My env: macOS 12.12.3 node v. 6.10 create-react-app 1.4.3 react-scripts-ts 2.8.0

It seems to be known issue and was resolved in awesome-typescript-loader using plugin https://github.com/dividab/tsconfig-paths-webpack-plugin. For now I change my typescript config to

    "paths": {
      "src/*": ["./src/*"]
    },

and added .env file with NODE_PATH=. Seems that it fixed global imports inside my project but it not very flexible.

the-mike avatar Dec 06 '17 11:12 the-mike

I fixed this problem and made pull request: https://github.com/wmonk/create-react-app-typescript/pull/223 As a temporary fix you can use diabelb-react-scripts-ts instead of react-scripts-ts.

Just run:

npm --save remove react-scripts-ts
npm --save install diabelb-react-scripts-ts

diabelb avatar Jan 13 '18 20:01 diabelb

Are you using jest with this aliases?

I've tried to use it but my tests can't find modules imported using custom paths. My app is building and running - only jest have a problem.

my tsconfig.json

{
  "compilerOptions": {
...
    "baseUrl": ".",
    "paths": {
      "@app/*": ["./src/*"]
    }
  },
...
}

my tsconfig.test.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs"
  }
}

After using import like this in one component

import { connect, Dispatch } from 'react-redux';
import StoreState from '@app/types/StoreState';
import Counter from '@app/components/counter/Counter';
import * as actions from '@app/actions/';

I've got jest errors like this

 FAIL  src/components/home/Home.test.tsx
  ● Test suite failed to run

    Cannot find module '@app/components/counter/Counter' from 'Counter.tsx'
      
      at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:179:17)
      at Object.<anonymous> (src/containers/counter/Counter.tsx:4:17)

kucharzyk avatar Jan 25 '18 17:01 kucharzyk

Hello,

Suddenly, ts-jest does not support absolute paths right know. But there is a way out. First you can check my fork with solution: https://github.com/diabelb/create-react-app-typescript-redux

If not, you need to fork create-react-app-typescript yourself or eject application. After that you have to add path info to package.json:

"jest": { "moduleNameMapper": { "^react-native$": "react-native-web", "@App/(.)": "<rootDir>/src/$1", "@Reducers/(.)": "<rootDir>/src/store/reducers/$1", "@Store/(.)": "<rootDir>/src/store/$1", "@Components/(.)": "<rootDir>/src/components/$1" } },

and edit createJestConfig.js - find out supportedKeys array and add moduleNameMapper:

const supportedKeys = [ 'moduleNameMapper', 'collectCoverageFrom', 'coverageReporters', 'coverageThreshold', 'snapshotSerializers', ];

diabelb avatar Jan 25 '18 18:01 diabelb

Suddenly, ts-jest does not support absolute paths right know.

Strictly speaking, this is not correct. The issue described above refers to module resolution, which is performed by jest when executing the tests - ts-jest does not have any influence on this. That's why the moduleNameMapper entry is required for path mapping in jest. Unluckily, the syntax used for this mapping (regex based) differs from the one that is used in the tsconfig (glob based). A suitable transformation would help, but is not that easy to achieve for general purpose. Otherwise, we would already have added it to the jest config creation process.

DorianGrey avatar Jan 30 '18 07:01 DorianGrey

@DorianGrey is there any workaround currently for fixing the jest problems with path mappings?

Also, would it be possible to just convert all the path mappings in tsconfig.test.json from glob to regex (using a lib such as glob-to-regexp for instance) with no extra config from the user side?

But it might be a good idea to allow the user to set moduleNameMapper config meanwhile.

rolandjitsu avatar Feb 12 '18 13:02 rolandjitsu

Also, would it be possible to just convert all the path mappings in tsconfig.test.json from glob to regex (using a lib such as glob-to-regexp for instance) with no extra config from the user side?

Not in a simple way. The path mappings in the tsconfig.json are relative to the baseUrl, while the ones in the jest configuration have to complete (and refer to a <rootDir), so it would be required to rewrite them. An addtional problem is raised by the matchers in the mappings - regexp vs. glob would need a manual rewrite, either before or after the conversion. While taking care that all of these steps leave a usable expression behind. However, the worst thing to somehow deal with is that the mappings in the tsconfig are 1:n, while the ones from jest are 1:1 - so, at least for supporting all potential setups, it would be required to somehow "join" the potentially multiple globs, or enforce that there is exactly one. A lot of work...

DorianGrey avatar Feb 15 '18 07:02 DorianGrey

@DorianGrey I understand. Perhaps the trivial thing to do is to allow the user to specify the moduleNameMapper config. It's not ideal, but jest and ts-jest works the same way.

It might actually be a good idea to have path mappings support without additional config in ts-jest instead.

rolandjitsu avatar Feb 15 '18 12:02 rolandjitsu

"moduleNameMapper": {
  "^@app/(.*)$": "<rootDir>/src/$1"
}

is working well, according to moduleNameMapper

in tsconfig.json i add baseUrl and path

"compilerOptions": {
  "baseUrl": "./",
  "paths": {
    "@app/*": [
      "src/*"
    ]
  }
}

@DorianGrey @rolandjitsu thank for explanations.

kuzvac avatar Mar 30 '18 19:03 kuzvac

Oddly, for me the same configuration as @kuzvac leads to Jest getting stuck at Determining test suites to run....

EDIT: Using react-scripts-ts v2.15.1

hassankhan avatar Apr 15 '18 22:04 hassankhan

@hassankhan I think we need to wait for https://github.com/wmonk/create-react-app-typescript/pull/303 to be merged before we can use that config? 😕

rolandjitsu avatar Apr 18 '18 07:04 rolandjitsu

I patched the changes into my local node_modules/ to test, I'll check against the PR

hassankhan avatar Apr 18 '18 16:04 hassankhan

@hassankhan if you have any problems with the changes in the PR let my know, so I can update it! 🙂

sebald avatar Apr 19 '18 09:04 sebald

any updates?

danieloprado avatar May 10 '18 17:05 danieloprado

https://github.com/wmonk/create-react-app-typescript/pull/303 was merged and will be included in the next CRA-ts release.

DorianGrey avatar May 11 '18 10:05 DorianGrey

I agree with @rolandjitsu that there should be some kind of default for mapping tsconfig paths for Jest paths. I think that this would reduce the number of configuration and the confusion between jest and tsconfig path mappings.

Since module resolution is basically determined at compile time, there is little sense to support n-number of paths under tsconfig's path. The only use case I see in tsconfig's path option is to resolve modules in different locations (during development, explicitly stating how module resolution would work), but I am not sure if this is carried over to a webpack build.

Of course, being able to manually override moduleNameMapper should still be supported, but a default 'mapper' makes a lot of sense.

I've dealt with this in my own private project with the following very crude code:

const tsconfig = require('./tsconfig.json')
const path = require('path')

/**
 * Transforms tsconfig paths to Jest compatible moduleNameMapper syntax
 * Only takes the first path for each key in tsconfig.compilerOptions.paths
 * Takes 'baseUrl' into consideration
 * @param {Object} tsconfig
 */
const transformTsPathsToJestPaths = tsconfig => {
    const { paths = {}, baseUrl = '' } = tsconfig.compilerOptions
    const resolvedBase = path.join('<rootDir>', baseUrl)
    return Object.keys(paths).reduce(
        (acc, key) =>
            key === '*'
                ? acc
                : {
                      ...acc,
                      ['^' + key.replace('*', '(.*)') + '$']: path.join(
                          resolvedBase,
                          paths[key][0].replace('*', '$1')
                      )
                  },
        {}
    )
}

With the following tsconfig.json

{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            "@ModuleA/*": ["ModuleA/*"]
        }
    }
}

The function outputs:

{ '^@ModuleA/(.*)$': '<rootDir>/src/ModuleA/$1' }

liangchunn avatar Jun 05 '18 12:06 liangchunn