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

Loading .mjs file or esmodule dependency in cypress config #23540

Closed
joefiorini opened this issue Aug 24, 2022 · 5 comments
Closed

Loading .mjs file or esmodule dependency in cypress config #23540

joefiorini opened this issue Aug 24, 2022 · 5 comments
Assignees

Comments

@joefiorini
Copy link

Current behavior

I have some data seeding code that was imported into a Cypress test. I am now moving that code into a cy.task, thus trying to import via node. Once I did that I started seeing this error:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /src/cypress-esmodule-import-error/cypress.config.ts

I eventually figured out that one of our dependencies uses type: "module" in its package.json. Our app does not use esmodules, instead it is TypeScript that gets transpiled down to use commonjs. #22118 makes cypress use await import which breaks in our case since we aren't using esmodules.

Desired behavior

I would expect that Cypress can load any files in the config file that we would import into a test. I realize these are two completely separate things; one is bundled and the other is not. However, this difference is not really visible to the end user and therefore it creates a bad DX to not be able to import a file in cypress.config.ts that I can import just fine in a test.

Test code to reproduce

https://github.com/joefiorini/cypess-esmodule-import-error/tree/main

Cypress Version

10.6.0

Node version

18.4.0

Operating System

Linux 5.15.59-2-lts

Debug Logs

No response

Other

No response

@lmiller1990
Copy link
Contributor

lmiller1990 commented Sep 7, 2022

Thanks for the reproduction. We will need to look into this - is it actually valid in the TS compiler to write import "./blah.mjs" in a ts file when a project is not using type: module? I haven't seen this before.

I'm not sure TS actually does module transpiling - you'd normally need webpack for something this like, but I could be mistaken here.

@joefiorini
Copy link
Author

The blah.mjs was just to make a simpler reproduction than the issue we actually ran into. I have updated the repo using the library that originally caused this problem for us (konva) and structured it similarly to how I have our actual app setup. The problem here is that the import from konva is buried way down in production code, not actually in a cypress file. This is fine when bundled for the web, but when importing into node we get the error mentioned above.

@lmiller1990
Copy link
Contributor

Thanks for the updating reproduction.

Just to clarify... cy.task is for executing code in Node. It looks like Konva is canvas drawing libary for the browser - do you have a particular goal in mind for executing this in Node?

I looked at their README, specifically here: https://github.com/konvajs/konva#commonjs-modules. I tried their recommendation to use the konva/cmj build and it seems to execute without any errors:

const { Canvas } = require('konva/cmj/Canvas');
const { Context } = require('konva/cmj/Context');

export class DrawableObject {
  constructor(
    private canvas = new Canvas({}),
    private konvaContext = new Context(canvas)
  ) {}

  draw() {
    this.konvaContext.closePath();
  }
}

We use ts-node to handle running TS code in Node.js. I don't think importing .mjs into a non ESM project (eg, not using type: module or module: nodenext in supported. My minimal example:

## My file
$ cat index.ts
import * as Blah from "./blah.mjs"
console.log(Blah)

## Try it out
$ yarn ts-node index.ts

$ /Users/lachlan/code/dump/ts/node_modules/.bin/ts-node index.ts
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/lachlan/code/dump/ts/blah.mjs not supported.
Instead change the require of /Users/lachlan/code/dump/ts/blah.mjs to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (/Users/lachlan/code/dump/ts/index.ts:3:12)
    at Module.m._compile (/Users/lachlan/code/dump/ts/node_modules/ts-node/dist/index.js:857:29)
    at Object.require.extensions.<computed> [as .ts] (/Users/lachlan/code/dump/ts/node_modules/ts-node/dist/index.js:859:16)
    at phase4 (/Users/lachlan/code/dump/ts/node_modules/ts-node/dist/bin.js:466:20)
    at bootstrap (/Users/lachlan/code/dump/ts/node_modules/ts-node/dist/bin.js:54:12)
    at main (/Users/lachlan/code/dump/ts/node_modules/ts-node/dist/bin.js:33:12)
    at Object.<anonymous> (/Users/lachlan/code/dump/ts/node_modules/ts-node/dist/bin.js:579:5) {
  code: 'ERR_REQUIRE_ESM'
}

For now, I think your best option is using their cmj build - although I am still not clear on the end goal of running Konva in Node - maybe I'm missing something.

The reason this does work in your production build is webpack, unlike TypeScript, does bundle "everything" including making all the different module formats work together.

Hopefully this gives some insight into what's actually going on here - still curious on the use case of a Canvas rendered on the Node end.

@joefiorini
Copy link
Author

joefiorini commented Sep 12, 2022

I am still not clear on the end goal of running Konva in Node - maybe I'm missing something

I am not trying to run Konva in node. I am trying to import a class from our production app that happens to be importing a class from Konva (and I'm doing that just so I can use the class as a TypeScript type, it's not actually used at runtime, but since it's a class the import sticks around)). This problem is not unique to Konva but would happen with any library that uses esnext. As far as I know there is no way to determine whether a node package uses type: "module" or not (unless they have a documented workaround in their readme).

We use ts-node to handle running TS code in Node.js. I don't think importing .mjs into a non ESM project (eg, not using type: module or module: nodenext in supported.

I thought that if you tell TypeScript to include a particular node module in its compilation (ie. via the include option in tsconfig.json) then it will convert the imports to whatever format you have configured. I cannot reproduce this behavior therefore I must have been mistaken.

I guess if ts-node doesn't support it, it would be hard to make Canvas support it. Closing this issue then, sounds like it's more of a problem with ts-node than Cypress.

@lmiller1990
Copy link
Contributor

lmiller1990 commented Sep 13, 2022

I thought that if you tell TypeScript to include a particular node module in its compilation (ie. via the include option in tsconfig.json) then it will convert the imports to whatever format you have configured. I cannot reproduce this behavior therefore I must have been mistaken.

This is the confusing part for sure - my understanding is TS won't transform ESM to CJS (source of your bug).

It's a bit confusing, since TS does give the appearance of doling some module transforms. Here's an example (and how I usually debug this - might be useful for other people googling this issue).

package.json

  "name": "ts",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "typescript": "^4.8.3"
  }
}

dep.js (note: it is commonjs)

module.exports = {
  foo: 'BAR'
}

index.ts (using ESM)

import { foo } from './dep.js'

let bar = foo

If you compile it with npx tsc -p . you get index.js:

"use strict";
exports.__esModule = true;
var dep_js_1 = require("./dep.js");
var bar = dep_js_1.foo;

Although you wrote ESM syntax, it actually transformed to commonjs, since your tsconfig.json is module: commonjs. If you make it module: es6...

index.ts

import { foo } from './dep.js';
var bar = foo;

dep.js

module.exports = {
  foo: 'BAR'
}

No change - that's because TS only compiles .ts files, and leaves JS as-is. Same if you change dep.js to dep.mjs - TS will happily compile it, leaving the (m)js files as-is - no transform.

Somewhat confusingly, the opposite does not work. If you do write module.exports and set module: es6, it will not change to ESM syntax. I think this is what you were expecting (understandably), thus the confusing. Basically, TS can take ESM syntax and trasnform it to commonjs, but not the other way around.

This problem is solved by bundlers, like webpack or rollup. But that's a lot of messing around, and probably not ideal - the best solution is using a single module system (which is what you are doing, it's just for whatever reason TS did not find the correct CJS module for Konva, but the ESM one - not really in your control).


All in all, it's very confusing and hard to remember. When I run into problems and don't know if it's on my end or their end, I usually make a minimal TS project and try to make it work (minimal reproduction, if you will).

if you do think you find another bug in our module handling, or my logic is incorrect (could well be) please open an issue again (or comment on this one). Thanks!

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

No branches or pull requests

3 participants