Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript + Fully-Specified ESM import fails in cy.task #20580

Closed
karlhorky opened this issue Mar 11, 2022 · 9 comments
Closed

TypeScript + Fully-Specified ESM import fails in cy.task #20580

karlhorky opened this issue Mar 11, 2022 · 9 comments

Comments

@karlhorky
Copy link
Contributor

karlhorky commented Mar 11, 2022

Current behavior

Using cy.task fails when it is used along with TypeScript code which uses fully-specified ESM imports (imports using .js extension, where there is no matching .js file because all of the files are TypeScript files , see microsoft/TypeScript#41887 (comment)).

For example:

// website/cypress/plugins/index.ts
import { deleteUserByEmail } from '../../../database/queries/users';

export default function pluginConfig(on: Cypress.PluginEvents) {
  on('task', {
    async deleteUser(email) {
      return await deleteUserByEmail(email);
    },
  });
}
// database/queries/users.ts
import {sql} from '../util/connect.js'
// database/util/connect.ts
export async function sql() {}

Note the difference in file names above. As per microsoft/TypeScript#41887 (comment), this should work in TypeScript (already works when using tsc)

Error message:


  1) Employer sign up
       should sign up with new company and verify email:
     CypressError: `cy.task('deleteEmployer')` failed with the following error:

> Cannot find module '../util/connect.js'
Require stack:
- /Users/lukas/Documents/project/packages/database/queries/users.ts
- /Users/lukas/Documents/project/packages/website/cypress/plugins/index.ts
- /Users/lukas/Library/Caches/Cypress/9.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_plugins.js
- /Users/lukas/Library/Caches/Cypress/9.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/index.js

https://on.cypress.io/api/task
      at http://localhost:3020/__cypress/runner/cypress_runner.js:162240:78
      at tryCatcher (http://localhost:3020/__cypress/runner/cypress_runner.js:13022:23)
      at Promise._settlePromiseFromHandler (http://localhost:3020/__cypress/runner/cypress_runner.js:10957:31)
      at Promise._settlePromise (http://localhost:3020/__cypress/runner/cypress_runner.js:11014:18)
      at Promise._settlePromise0 (http://localhost:3020/__cypress/runner/cypress_runner.js:11059:10)
      at Promise._settlePromises (http://localhost:3020/__cypress/runner/cypress_runner.js:11135:18)
      at _drainQueueStep (http://localhost:3020/__cypress/runner/cypress_runner.js:7729:12)
      at _drainQueue (http://localhost:3020/__cypress/runner/cypress_runner.js:7722:9)
      at Async.../../node_modules/bluebird/js/release/async.js.Async._drainQueues (http://localhost:3020/__cypress/runner/cypress_runner.js:7738:5)
      at Async.drainQueues (http://localhost:3020/__cypress/runner/cypress_runner.js:7608:14)
  From Your Spec Code:
      at Context.eval (http://localhost:3020/__cypress/tests?p=cypress/integration/employerSignUp.spec.ts:188:10)
  
  From Node.js Internals:
    Error: Cannot find module '../util/connect.js'
    Require stack:
    - /Users/lukas/Documents/project/packages/database/queries/users.ts
    - /Users/lukas/Documents/project/packages/website/cypress/plugins/index.ts
    - /Users/lukas/Library/Caches/Cypress/9.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_plugins.js
    - /Users/lukas/Library/Caches/Cypress/9.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/index.js
        at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
        at Function.Module._load (node:internal/modules/cjs/loader:778:27)
        at Module.require (node:internal/modules/cjs/loader:999:19)
        at require (node:internal/modules/cjs/helpers:102:18)
        at Object.<anonymous> (/Users/lukas/Documents/project/packages/database/queries/users.ts:5:1)
        at Module._compile (node:internal/modules/cjs/loader:1097:14)
        at Module.m._compile (/Users/lukas/Library/Caches/Cypress/9.5.1/Cypress.app/Contents/Resources/app/node_modules/ts-node/src/index.ts:607:4)
        at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
        at Object.require.extensions.<computed> [as .ts] (/Users/lukas/Library/Caches/Cypress/9.5.1/Cypress.app/Contents/Resources/app/node_modules/ts-node/src/index.ts:610:10)
        at Module.load (node:internal/modules/cjs/loader:975:32)
        at Function.Module._load (node:internal/modules/cjs/loader:822:12)
        at Module.require (node:internal/modules/cjs/loader:999:19)
        at require (node:internal/modules/cjs/helpers:102:18)
        at /Users/lukas/Documents/project/packages/website/cypress/plugins/index.ts:30:37
        at processTicksAndRejections (node:internal/process/task_queues:96:5)
        at deleteEmployer (/Users/lukas/Documents/project/packages/website/cypress/plugins/index.ts:30:37)

Desired behavior

Fully-specified ESM imports using .js file extensions should work in TypeScript code

Two details:

  1. This will be fixed in one of the coming versions of ts-node feat(register): resolve .js to .ts in ts-node/register TypeStrong/ts-node#1361
  2. An old 8.5.4 version of ts-node is currently used in the macOS Cypress.app via the server package (apparently? I was surprised to find it there):
    "ts-node": "8.5.4",

Test code to reproduce

See above

Cypress Version

9.5.1

Other

It would be nice if Cypress also supported Node.js ESM code just generally, but I think there are other issues for that:

@davidmunechika davidmunechika added topic: typescript stage: needs investigating Someone from Cypress needs to look at this labels Mar 11, 2022
@karlhorky
Copy link
Contributor Author

Workaround: precompile the TypeScript files to CJS (run tsc using "module": "CommonJS")

@cypress-bot cypress-bot bot added stage: backlog and removed stage: needs investigating Someone from Cypress needs to look at this labels Apr 29, 2022
@spiffytech
Copy link

ts-node 10.8.0 has been released with .js extension resolution. Would that resolve this for Cypress?

I'm interested in a workaround that doesn't target CommonJS; my project is ESM and I'd like to have Cypress tasks import my app code.

@karlhorky
Copy link
Contributor Author

Yeah, it would be great to have ESM support in Cypress.

I don't think new versions of ts-node will be helpful - it has had ESM support for quite a few versions. The problem is the very old version of ts-node inside of the macOS app Cypress.app (I imagine, also the Electron apps for Windows and Linux). It will need to be updated.

@karlhorky
Copy link
Contributor Author

Ah, looks like Cypress v10 has updated this ts-node in the server package!

"ts-node": "^10.2.1",

Maybe fully-specified ESM imports will work now... 🤔

@karlhorky
Copy link
Contributor Author

karlhorky commented Jun 11, 2022

Everything is working now with the recent "type": "module" fix by @lmiller1990 (released in [email protected]).

To take advantage of this, make sure that you have:

  1. "type": "module" in the package where your cypress.config.ts is
  2. Fully-specified imports using the .js file extension (see below)

cypress.config.ts

import { defineConfig } from 'cypress';
-import * as users from './database/queries/users';
+import * as users from './database/queries/users.js';

export default defineConfig({
  e2e: {
    setupNodeEvents(on) {
      on('task', {
        async deleteUserByEmail(email: string) {
          return (await users.deleteUserByEmail(email)) || null;
        },
      });
    },
  },
});

@lmiller1990
Copy link
Contributor

lmiller1990 commented Jun 14, 2022

Alternatively, change your .js files to .ts - not always an option, but when it is, you won't need the extension. CJS + ESM interop is such a mess right now (not specifically here, but in the entire ecosystem...).

As an aside, you have no idea how good it feels to get a notification in an already closed issue that actually clarifies things are working and provides useful info for future users! Thank you for this!

@karlhorky
Copy link
Contributor Author

karlhorky commented Jun 14, 2022

Alternatively, change your .js files to .ts - not always an option, but when it is, you won't need the extension

This is not true with TypeScript and fully-specified imports in ESM, check out microsoft/TypeScript#41887 (comment)

The files associated with the .js paths are already written in TypeScript (the files already have a .ts extension). In the import path, the correct way is to write a .js extension for these .ts files.

As an aside, you have no idea how good it feels to get a notification in an already closed issue that actually clarifies things are working and provides useful info for future users! Thank you for this!

Great, really good to hear! 😃 I've been trying to open issues and post solutions to clear up some of the confusion around ESM + TS interop.

@lmiller1990
Copy link
Contributor

Right, so, if I want to use native ESM in my TS files, I actually need to do js in the import (even when importing a ts file? Or is this only to import a ESM js file into a ESM ts file?

I will need to play with this more. I've been using type: module and module: esnext but haven't imported JS into TS before - my understanding is in that case I need JS, but if all my files are ts, I don't?

@karlhorky
Copy link
Contributor Author

if I want to use native ESM in my TS files, I actually need to do js in the import (even when importing a ts file?

Yep, counterintuitive but true.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants