cucumber / cucumber-js

Cucumber for JavaScript
https://cucumber.io
MIT License
5.07k stars 1.09k forks source link

ESM mode with ts-node breaks TypeScript path alias resolution in cucumber-js #2403

Closed samydoesit closed 6 months ago

samydoesit commented 6 months ago

👓 What did you see?

When using cucumber-js with ts-node as the loader in ESM mode, TypeScript path aliases specified in tsconfig.json fail to resolve.

Path aliases are not resolved, resulting in module resolution errors when attempting to execute tests.

Error:

Error: Cannot find package '@/index.js' imported from /cucumber-typescript-path-alias-reproduction/features/step_definitions/steps.ts
    at packageResolve (/cucumber-typescript-path-alias-reproduction/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:757:9)
    at moduleResolve (/cucumber-typescript-path-alias-reproduction/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:798:18)
    at Object.defaultResolve (/cucumber-typescript-path-alias-reproduction/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:912:11)
    at /cucumber-typescript-path-alias-reproduction/node_modules/ts-node/src/esm.ts:218:35
    at entrypointFallback (/cucumber-typescript-path-alias-reproduction/node_modules/ts-node/src/esm.ts:168:34)
    at /cucumber-typescript-path-alias-reproduction/node_modules/ts-node/src/esm.ts:217:14
    at addShortCircuitFlag (/cucumber-typescript-path-alias-reproduction/node_modules/ts-node/src/esm.ts:409:21)
    at resolve (/cucumber-typescript-path-alias-reproduction/node_modules/ts-node/src/esm.ts:197:12)
    at nextResolve (node:internal/modules/esm/hooks:865:28)
    at Hooks.resolve (node:internal/modules/esm/hooks:303:30)

✅ What did you expect to see?

TypeScript path aliases should resolve correctly when cucumber-js is run in ESM mode using ts-node.

📦 Which tool/library version are you using?

🔬 How could we reproduce it?

Steps to Reproduce

  1. Clone the provided repository from https://github.com/samydoesit/cucumber-typescript-path-alias-reproduction
  2. Install dependencies using npm install
  3. Attempt to run the tests using npm test
  4. Notice the module resolution errors related to TypeScript path aliases

📚 Any additional context?

No response

GRmeB commented 6 months ago

+1 Same issue here...

davidjgoss commented 6 months ago

Thanks @samydoesit for raising, and taking the time to make that repro.

I've looked at the config there, but my understanding is that it would not work with or without Cucumber. From the relevant ts-node documentation:

..."paths" are intended to describe mappings that the build tool or runtime already performs, not to tell the build tool or runtime how to resolve modules. In other words, they intend us to write our imports in a way node already understands. For this reason, ts-node does not modify node's module resolution behavior to implement "paths" mappings.

ts-config-paths tends to be used for this, but from what I can see lacks ESM support for now, so unfortunately to have path mapping like this you may need to go back to CommonJS output.

samydoesit commented 6 months ago

Thanks for your quick response. :) I thought maybe there is another way.

We will keep the CommonJS output for now.

dnotes commented 4 months ago

To clarify, is there no way to import from the local files when using ts-node/esm? I can't seem to import even if I use a relative path, even if the files are in the same directory, but perhaps that's a different issue.

dnotes commented 4 months ago

For anyone else who finds this issue, my workaround was to use compilerOptions.moduleResolution: "node" in the tsconfig.json, and then import modules using relative paths and the .js file extension even for .ts files. So in tests/steps/search.ts:

import searchRequest from '../../src/lib/searchRequest.js'; // imports $lib/searchRequest.ts
hynding commented 3 weeks ago

Adding my two cents as I got it to work without ts-node nor tsconfig-paths despite trying everything I could think of. I did try some custom loader scripts that worked but were a bit clunky for my taste and less than ideal from a maintenance standpoint.

I came across several others pointing out they replaced their configuration with the tsx package but couldn't get it to work following the documented ESM principles. I wanted to use TypeScript for my step definitions and path aliases within those files, plus include type: module in my package.json and have the module and moduleResolution in tsconfig compilerOptions set to ES2022 and Bundler respectively. I felt obligated to use import over require in my cucumber config file when trying out tsx but I wasn't having any success.

If anyone else is having the same issue, here's what I did to get everything to run successfully:

My cucumber.yaml file looks like this:

default:
  paths:
    - "../../features/**/*.feature"
  requireModule:
    - tsx/cjs
  require:
    - "tests/**/*.ts"

Nit: I declare my step definitions outside of my features (which rests in my repo's root directory as I intend it to target other languages in the future [hopefully with step definitions being able to be targeted outside of it, but I've read that may be wishful thinking]) and src folders in my sub-project root called tests (as I'm not using any other testing framework at the moment).

My tests also had a 4x performance improvement as well, which is always a plus.