kulshekhar / ts-jest

A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.
https://kulshekhar.github.io/ts-jest
MIT License
6.93k stars 453 forks source link

Support ESM #1709

Closed ahnpnl closed 3 years ago

ahnpnl commented 4 years ago

🚀 Feature Proposal

Jest is working on supporting ESM https://github.com/facebook/jest/issues/9430 . We should consider lift up the hardcoded target: commonjs at some points.

Related discussion about detecting whether a file is esm or commonjs can be found here https://github.com/facebook/jest/issues/9860

Related issue: #1174

Motivation

Let ts-jest users as well as jest users take part in the ESM support development and testing.

hedefalk commented 4 years ago

@ahnpnl Sorry, I didn't properly look at this ticket when starting to work on it yesterday:

https://github.com/hedefalk/ts-jest/commit/f488ab64929a16deccd58d02e668cf5b254790e0

As mentioned in the other thread, I think we have a problem that the extension that jest switches on is not the extension we output from ts-jest, but will be the input file's extension (.ts). At least it seemed that way when I debugged it, I'll double check…

ahnpnl commented 4 years ago

yep, all I know is transformer only delivers compiled js but nothing is mentioned about file extension. That might need to investigate.

ahnpnl commented 4 years ago

It is still unclear how transformer should adopt to esm support. Need more guidance from jest team

marcj commented 3 years ago

ESM works now in ts-node very well since v9.1.0. Would be great to have ts-jest support ESM too. It's the last tool in my toolbox to support esm :)

marcj commented 3 years ago

I just tested the ts-jest master with jest v27.0.0-next.2. After

everything works great with my esnext tests + esnext dependencies. ✌️

ahnpnl commented 3 years ago

ESM support for isolatedModules: true was out in 27.0.0-next.2.

isolatedModules: false will come later.

ahnpnl commented 3 years ago

finished in 27.0.0-next.3

koonfoon commented 3 years ago

Hi, I recently working on a project that use typescript, jest and ts-jest. When I run jest. It give me this error:

__tests__/someTypescriptCode.test.ts:1:25 - error TS2691: An import path cannot end with a '.ts' extension. Consider importing '../src/someTypescriptCode' instead.

    1 import information from '../src/someTypescriptCode.ts';

I had explicitly install ts-jest@27.0.0-next.3 and jest@27.0.0-next.2. Execute jest with node --experimental-vm-modules node_modules/jest/bin/jest.js This is how my jest.config.js file look like

//  jest.config.js

export default {
    "roots": [
      "<rootDir>"
    ],
    "testMatch": [
      "**/__tests__/**/*.+(ts|tsx|js)",
      "**/?(*.)+(spec|test).+(ts|tsx|js)"
    ],
    "transform": {
      "^.+\\.(ts|tsx)$": "ts-jest"
    },
    "testEnvironment": 'node',
    "extensionsToTreatAsEsm": [".ts"]
  }

My tsconfig.json:

{
  "compilerOptions": {
     "target": "ES2018",
     "module": "ESNEXT", 
     "outDir": "./build",
     "strict": true,
     "moduleResolution": "node",
     "esModuleInterop": true,
     "skipLibCheck": true,
     "forceConsistentCasingInFileNames": true
  },
  "include": ["./src"],
  "exclude": ["node_modules", "**/*.test.ts"]
}

Please help. Thank you.

ahnpnl commented 3 years ago

Hi, to use ESM you need to follow doc https://kulshekhar.github.io/ts-jest/docs/next/guides/esm-support

Besides, the error comes from TypeScript itself. I'm not sure why. It’s better that your import path shouldn’t contain “.ts” extension.

koonfoon commented 3 years ago

Hi, to use ESM you need to follow doc https://kulshekhar.github.io/ts-jest/docs/next/guides/esm-support

Besides, the error comes from TypeScript itself. I'm not sure why. It’s better that your import path shouldn’t contain “.ts” extension.

It did work if I remove ".ts". But my actual headache is something like this: I have

// codeA.ts;

export default (arg: string): boolean => {
   // some implementation
}
// codeB.ts

import codeA from './codeA.js';   // ".js" not typo, it work fine after "tsc" transpile, but jest always complaint

export const someFunction = (): void => {
  // some more code
}
// codeB.test.ts

import * as Bcode from './codeB';

Error occur when jest execution hit import codeA from './codeA.js' inside codeB.ts

ahnpnl commented 3 years ago

Which error do you get ?

Maybe you can share your repo here

koonfoon commented 3 years ago

Which error do you get ?

Maybe you can share your repo here

👍, but let me remove all unrelated codes first. Thank you our willingness to help 😊

koonfoon commented 3 years ago

Sorry for the delay respond, I was trying to re-create the same error, here is my repo https://github.com/koonfoon/ts-jest-code-test. This is not my actual project, but is very similar to what I am trying to test with jest. Thanks

gadicc commented 3 years ago

Can I ask what is the recommended way to import local .ts files? I understood the convention is to:

// Import as ".js" even though the source file is ".ts"
import file from "./file.js" 

Unless I missed something, that will fail with:

    Cannot find module './file.js' from 'src/callingFile.spec.ts'

      at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:311:11)

I got it working by stripping off the .js with moduleMapper in my jest.config.ts:

  moduleNameMapper: {
    "(.*)\\.js$": "$1",
  },

but I guess this will fail on the same ambiguous edge cases the forcing .js is meant to solve.

Happy to create a more minimal repo for this but I think my main question is to just understand the recommended way to import other local .ts files.

@koonfoon this will presumably solve your problem but I'm not sure if it will solve it in the right way :sweat_smile:

marcj commented 3 years ago

@gadicc you simply import the TS files as usual, omitting the file extension.

gadicc commented 3 years ago

Ok thanks, got it! And thanks for the miraculously fast reply 😅 Have a great weekend 🎉

On Fri, 30 Apr 2021, 18:21 Marc J. Schmidt, @.***> wrote:

@gadicc https://github.com/gadicc you simply import the TS files as usual, omitting the file extension.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/kulshekhar/ts-jest/issues/1709#issuecomment-830170644, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAC5IGRZEW7CRFGVUCKH3VTTLLDJBANCNFSM4NSLIQFQ .

koonfoon commented 3 years ago

Hi @gadicc thank you for your suggestion, will try it out.

tstelzer commented 3 years ago

you simply import the TS files as usual, omitting the file extension.

This doesn't make sense to me.

In an ESM project, imports will end with .js. Even when I omit the file extension in my test files, the code under test will import other modules, using .js files extensions. In other words:

// file: a.spec.ts
import * as a from './a'; // works

// file: a.ts
import * as b from './b.js'; // "Cannot find module './b.js' from './a.ts'

For what it's worth, @gadicc workaround seems to work for me.

koonfoon commented 3 years ago

Hi @tstelzer I did try @gadicc suggested method, it worked fine when I only import local codes/modules that are written by me. But when I import others npm packages/dependencies. It will start giving me errors again. I suspect moduleNameMapper blindly resolved import's from others npm packages/dependencies too. I will try to re-create the error again and post it here. It had been a month since the last time I try to work on this issue.

gadicc commented 3 years ago

@koonfoon, probably the following regexp will fix that for you but I haven't tested:

  moduleNameMapper: {
    "^(\\./.*)\\.js$": "$1",
  },

It will only match imports starting with ./ (and ending with .js) and so ignore imports from packages.

tstelzer commented 3 years ago

I reproduced the original issue in https://github.com/tstelzer/ts-jest-playground

Running npm test produces:

    Cannot find module './a.js' from 'src/a.spec.ts'

    > 1 | import * as a from './a.js';
        | ^
      2 |
      3 | describe('a', () => {
      4 |     test('foo', () => {

      at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:311:11)
      at Object.<anonymous> (src/a.spec.ts:1:1)

(edit) By the way, as to my intentions:

  1. I want to refute the statement "you simply import the TS files as usual, omitting the file extension.", because that is not how ESM modules are supposed to work (to the best of my knowledge).
  2. Get a maintainers' perspective on if this is an issue of ts-jest or jest.
ahnpnl commented 3 years ago

The issue you are seeing about “Cannot find module”, it is linked to Jest default resolver. This is not something for ts-jest because ts-jest is a Jest transformer, which only compiles ts to js. You should bring this error to https://github.com/facebook/jest/issues/9430

gadicc commented 3 years ago

Thanks, @ahnpnl, for clarifying. To save others some time I'm going to pull out a few quotes from that issue:

From SimenB (Jest author/collaborator) in this comment:

yeah, remove the extension from your import. Jest doesn't operate on the "virtual" .js file, but the ts file directly. You'll also need https://jestjs.io/docs/next/configuration#extensionstotreatasesm-arraystring.

Feel free to open up a new issue about resolving the js file, we'll probably need it (somehow) for proper interop with other tools (using the .js extension is what ts-node says to do even though the file does not exist)

From cspotcode (Jest contributor) in this comment:

A quick note, ts-node also supports extensionless imports as long as you've enabled the correct node flag --experimental-specifier-resolution=node This extensionless resolution behavior is implemented by node, so if you don't enable it, node will throw errors, and we can't stop that. Even if ts-node papered over this wrinkle, pre-compiled code would fail outside of ts-node, so this would be counter-productive.

When users do not want to enable this flag, we recommend adding the .js extension because otherwise TS's language service will raise an error in their code editor, outside of ts-node. Even if ts-node supported .ts extensions, any typechecking outside of ts-node would throw errors, so this would be counter-productive.

tl;dr; we recommend the options that will keep a project happy external to ts-node.

There are a few other references in the thread but I believe these two are the most pertinent for now.

koonfoon commented 3 years ago

@gadicc both of your moduleNameMapper regex work for me. Actually I had a Q&A and ended up @ahnpnl gave me the same idea, but I am not that familiar with regex😅, so this is my regex '(.*).js': ['$1'], before yours posts.😅

puchm commented 3 years ago

The regular expression @gadicc mentioned (^(\\./.*)\\.js$) only matches paths starting with ./, however I suppose you should also match ../.

This is a slight modification that works for me:

moduleNameMapper: {
  "^(\\.{1,2}/.*)\\.js$": "$1",
}

It matches paths that either start with ./ or ../ and end with .js. Does this also work with the imports from packages you mentioned? I am not having any problems so far.

acusti commented 2 years ago

thanks very much @gadicc and @puchm for the moduleNameWrapper-based solution!

@ahnpnl i came across your comment in #1057 (https://github.com/kulshekhar/ts-jest/issues/1057#issuecomment-803642747) and this issue while trying to figure out the right way to create a typescript "type": "module" package that uses ts-jest. one thing that stood out for me is this explanation:

Actually, the preset means "A Jest configuration". This preset includes a transformer, which compiles ts into js. I know it's a bit hard to understand what's going on in internal ts-jest.

using moduleNameWrapper seems like a reasonable solution to enable testing typescript ESM packages with ts-jest, and it’s a part of “Jest configuration”. this makes me think that if feasible, it would be a reasonable thing to include in the ESM presets (e.g. ts-jest/presets/default-esm, the one i am using). and if not, i definitely think it warrants a mention in the docs. for the latter, i made a PR: https://github.com/kulshekhar/ts-jest/pull/2922

ani01335 commented 8 months ago

Does debugging work on Windows with VSCode (for typescript) if the test script is configured with --experimental-vm-modules ? "test" entry in package.json: "test": "cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest --runInBand",