Open GeorgeXCV opened 1 year ago
Right now there doesn't seem to be enough information to reproduce the problem on our end. Unless we receive a reliable reproduction, we'll eventually have to close this issue until we can reproduce it. This does not mean that your issue is not happening - it just means that we do not have a path to move forward.
Please provide a reproducible example of the issue you're encountering. Here are some tips for providing a Short, Self Contained, Correct, Example and our own Troubleshooting Cypress guide.
Run any test with the above config file or just the module import in config file, super easy to reproduce.
Hi @GeorgeXCV, I was able to recreate and fix this issue in this repo: cypress-del-ts
The ticket for my project was adding "type": "module"
to package.json.
I also made some slight changes in comparison to what the guide describes.
import {deleteSync} from 'del';
and deleteSync(results.video)
It seems to work, but it would be awesome if you could confirm the changes work for you.
Hi @GeorgeXCV, I was able to recreate and fix this issue in this repo: cypress-del-ts
The ticket for my project was adding
"type": "module"
to package.json.I also made some slight changes in comparison to what the guide describes.
import {deleteSync} from 'del';
anddeleteSync(results.video)
It seems to work, but it would be awesome if you could confirm the changes work for you.
I get this error:
Your configFile is invalid: /Users/georgeashton/Documents/core/cypress.config.ts: —record
It threw an error when required, check the stack trace below:
ReferenceError: exports is not defined in ES module scope
at file:///Users/georgeashton/Documents/core/cypress.config.ts:25:23
at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:385:24)
at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15)
at async loadFile (/Users/georgeashton/Library/Caches/Cypress/10.3.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:106:14)
at async EventEmitter.<anonymous> (/Users/georgeashton/Library/Caches/Cypress/10.3.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:116:32)
Line 25 starts here:
on('after:spec', async (_spec, results) => {
if (results && results.stats.failures === 0 && results.video) {
// `del()` returns a promise, so it's important to return it to ensure
// deleting the video is finished before moving on
deleteSync(results.video)
}
})
Also why does Cypress recommend del
module? Wouldn't built in fs
module work?
import * as fs from "fs"
on('after:spec', async (_spec, results) => {
if (results && results.stats.failures === 0 && results.video) {
fs.unlink(results.video, (err) => {
if (err) throw err
return
})
}
})
Hi @GeorgeXCV - could you verify the contents of your tsconfig.json
file at your project root? I was able to reproduce the "exports is not defined in ES module scope" issue you reported by removing this portion of the tsconfig.json
from @rockindahizzy 's example:
"target": "es2016",
"module": "ES6",
In order to use imports in the config file the TS compiler will need to know how to handle module resolution which is provided by these keys
Hi @GeorgeXCV - could you verify the contents of your
tsconfig.json
file at your project root? I was able to reproduce the "exports is not defined in ES module scope" issue you reported by removing this portion of thetsconfig.json
from @rockindahizzy 's example:"target": "es2016", "module": "ES6",
In order to use imports in the config file the TS compiler will need to know how to handle module resolution which is provided by these keys
Strange, I changed my tsconfig.json
(previously using es5) but error persists:
{
"compilerOptions": {
"target": "es2016",
"module": "ES6",
"moduleResolution": "node",
"esModuleInterop": true,
"types": ["cypress", "node"]
},
"include": ["**/*.ts"]
}
I am currently using fs.unlink
instead using the snippet I posted here as I think that's better, doesn't require external module and don't need to reconfigure my TypeScript project for it.
@GeorgeXCV , sorry to hear that didn't fix your issue. There must be some other configuration at play here that hasn't been communicated in this issue. Unfortunately, copying config files into the issue here only gives us a partial view that doesn't capture the entirety of your project context. To help us diagnose what's going on it would be very helpful to get a simple reproduction case as a Git repo. Here are some tips for providing a Short, Self Contained, Correct, Example and our own Troubleshooting Cypress guide.
Unfortunately we have to close this issue due to inactivity. Please comment if there is new information to provide concerning the original issue and we can reopen.
I have encountered the same issue with 10.3.1 and 10.9.0.
It seems to be related to the import itself. 10.9.0 gives a better error output but it doesn't help to solve the issue.
Your configFile is invalid: projects\e2e-tests\cypress.config.ts
It threw an error when required, check the stack trace below:
Error: Cannot find module '@e2e-commons/config'
Require stack:
- project\projects\hus\ph-e2e-vrk\cypress.config.ts
- Cypress\Cache\10.9.0\Cypress\resources\app\node_modules\@packages\server\lib\plugins\child\run_require_async_child.js
- Cypress\Cache\10.9.0\Cypress\resources\app\node_modules\@packages\server\lib\plugins\child\require_async_child.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._resolveFilename (Cypress\Cache\10.9.0\Cypress\resources\app\node_modules\tsconfig-paths\lib\register.js:76:40)
at Function.Module._resolveFilename.sharedData.moduleResolveFilenameHook.installedValue [as _resolveFilename] (Cypress\Cache\10.9.0\Cypress\resources\app\node_modules\@cspotcod
e\source-map-support\source-map-support.js:811:30)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (project\projects\hus\ph-e2e-vrk\cypress.config.ts:1:1)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Module.m._compile (Cypress\Cache\10.9.0\Cypress\resources\app\node_modules\ts-node\src\index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
at Object.require.extensions.<computed> [as .ts] (Cypress\Cache\10.9.0\Cypress\resources\app\node_modules\ts-node\src\index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
at loadFile (Cypress\Cache\10.9.0\Cypress\resources\app\node_modules\@packages\server\lib\plugins\child\run_require_async_child.js:89:14)
My paths of tsconfig.json seems to get ignored:
"paths": {
"@e2e-commons": [
"dist/e2e-commons",
"node_modules/@e2e-commons"
],
"@e2e-commons/config": [
"dist/e2e-commons/config",
"node_modules/@e2e-commons/config"
]
}
Can you include your full tsconfig.json
Also what is @e2e-commons/config
? Can you share the package.json
and whatever it is supposed to be pointing to (what's in dist/e2e-commons
and node_modules/@e2e-commons/config
? I didn't know you could point to multiple things in paths
like this, how does it decide which one to use?)
I need more information to reproduce the issue.
I think I have found the issue in my setup. We have changed the build process from CJS to module builds. In our case the build of the package @e2e-commons/config
also changed. You find CJS in the require stacktrace (see before).
@e2e-commons/config
is our shared e2e test setup containing thinks like setupNodeEvents and commons config options.
Typescript takes the first path which can be resolved. More details can be found here, https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping. But it doesn't matter even if there is only one path included.
I have noticed that it works if I don't use the built package and build it on the fly.
The complete tsconfig.json:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": "../../..",
"declaration": false,
"downlevelIteration": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"lib": [
"es2020",
"dom"
],
"module": "es2020",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"sourceMap": true,
"strict": true,
"strictPropertyInitialization": false,
"target": "es2017",
"typeRoots": [
"project/node_modules/@types"
],
"paths": {
"@e2e-commons": [
"dist/e2e-commons",
"node_modules/@e2e-commons"
],
"@e2e-commons/config": [
"dist/e2e-commons/config",
"node_modules/@e2e-commons/config"
]
},
"types": [
"cypress",
"node",
]
},
"include": [
"**/*.ts"
]
}
The resulting package.json from the build looks like the following:
{
"module": "../fesm2015/e2e-commons-config.mjs",
"es2020": "../fesm2020/e2e-commons-config.mjs",
"esm2020": "../esm2020/config/e2e-commons-config.mjs",
"fesm2020": "../fesm2020/e2e-commons-config.mjs",
"fesm2015": "../fesm2015/e2e-commons-config.mjs",
"typings": "e2e-commons-config.d.ts",
"sideEffects": false,
"name": "@e2e-commons/config"
}
I ran into a similar issue yesterday and found a solution (although it may not be applicable to all cases). So, it's not documented anywhere, but looks like this error happens if your tsconfig.json
file has compiler options that are not supported by your current Typescript version. In this case it just fails with this extension error and TS itself doesn't provide any hints to indicate that the config is not relevant for the current version
I have checked the bundled ressources of cypress, but didn't find any integrated typescript version. Also the changelog doesn't mention any typescript version. We are using typescript@4.6.4.
Sorry for the slow reply, I was out of office in October. We don't ship a TypeScript version - we use the one you've got installed (if there is one).
We have changed the build process from CJS to module builds I have noticed that it works if I don't use the built package and build it on the fly
Right, so you changed from CJS to ESM, and the built version is in ESM (JS extension) so it's trying to consume a ESM package using a CJS loader, I think?
Can you provide a minimal reproduction by any chance? It sounds like this is the problem. Most of the issues around ERR_UNKNOWN stem from some erroneous ESM/CJS interop.
Also what version of Node.js are you on? 16.16 and 16.17 handled the loaders a little differently (shot in the dark, but just throwing some ideas out).
I can try to setup a small reproduction repo.
I have tried 16.15.0, 16.17.1 and 16.18.1. All have the same error. Even an update to cypress 10.11 doesn't change anything.
Just wanted to report that I'm seeing this, as well! Here's my config file. If I get rid of the on
function, the error goes away. Since this is the case, I'm wondering if it has something to do with the fact that this code is running in the node
environment before Cypress loads all its stuff?
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('after:run', () => {
fetch(url, {
method: 'POST',
body,
});
});
},
supportFile: 'cypress/support/e2e.ts',
experimentalSessionAndOrigin: true,
chromeWebSecurity: false,
defaultCommandTimeout: 6000,
env: {
...process.env,
},
},
});
Edit: Strike that. It seems to be the use of fetch
, which is imported, that's causing the issue.
How did you import the fetch?
@CSchulz import fetch from 'node-fetch';
I ended up taking another route with this, but node-fetch's current version is using ESModules, so that was probably the issue, I think?
I had a similar problem node-fetch
, latest version is ESM only - you can use the older version (v2.x I think?) if your project isn't using type: module
.
Is this still an issue or can we close it off? Is there a minimal repro I can pull?
But this would not fix the issue with ESM only libraries like we would have. I am trying to create a repro the next days.
Agreed, this is not perfectly solved. I'd like to see us move to esbuild eventually. It's faster and solves these problems fairly well. Until then, if you have a repro, I will do my best to fix it.
After some digging I can confirm the issue is similar to the node-fetch import with most recent version. As long an imported library is ESM, it is not supported yet.
If you are using type: module
and your tsconfig is set to consume esnext
, I think it should work? The thing we don't handle well is ESM only mixed with CJS.
@lmiller1990 considering that some projects cannot use "type": "module"
in package.json
(eg. frameworks which do not support ESM yet), would Cypress consider also supporting either A) a Cypress config file with .mts
extension (cypress.config.mts
) or B) an additional cypress open
flag that would force ESM, both of which that would signal that the Cypress files should be interpreted as ESM modules?
The following scenario doesn't work, for example:
cypress.config.ts
file in "type": "commonjs"
packagecypress.config.ts
of an ESM module (this should work with ts-node
I guess?)The error messages ranged from:
cypress.config.ts
file at all, even though it was thereTypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
Or any other way of forcing the ts-node
in Cypress to use the ESM version, even if the "type": "module"
is not specified in package.json
.
I also tried a combination of the ts-node
config options below, but these were also not helpful to configure the internal ts-node
the way that it should be:
"ts-node": {
"compilerOptions": {
"module": "esnext"
}
"esm": true
"experimentalSpecifierResolution": "node",
"moduleTypes": {
"cypress.config.ts": "esm"
}
}
Lastly, I tried the "ESM dynamic import()
" suggestions from a CommonJS TypeScript file consumed via ts-node
here, which also did not help:
https://github.com/TypeStrong/ts-node/discussions/1290
To be clear, the use case here is:
cypress
installed in same package as non-ESM lib packagecypress
config imports ESM, and all test files are ESMThe files do not have overlap above - all Cypress code will be ESM.
@lmiller1990 would you be open to supporting this use case somehow? Eg. suggestion A) a Cypress config file with .mts
extension (cypress.config.mts
) or B) an additional cypress open
flag that would force ESM?
Until this is properly fixed, some workarounds for the problem of a CommonJS config file importing an ESM module:
Transpile TypeScript code from imported ESM modules before running tests, either with esbuild
or tsc
.
Downside: extra build step and complexity
Use the script below as your test runner - this edits the package.json
file to have "type": "module"
and removes it again afterwards:
// scripts/cy.ts
import { exec } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const packageJsonPath = resolve(
dirname(fileURLToPath(import.meta.url)),
'..',
'package.json',
);
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
writeFileSync(
packageJsonPath,
JSON.stringify({ type: 'module', ...packageJson }, null, 2),
);
console.log(`✅ Added "type": "module" to ${packageJsonPath}`);
process.on('SIGINT', () => {
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
console.log(`\n✅ Reverted ${packageJsonPath}`);
});
const cypressProcess = exec('cypress open --e2e --browser chrome');
cypressProcess.stdout?.pipe(process.stdout);
cypressProcess.stderr?.pipe(process.stderr);
Add to your package.json
using tsm
or tsx
:
{
"scripts": {
"cy": "node --loader tsm scripts/cy.ts"
},
"devDependencies": {
"tsm": "2.3.0"
}
}
If you're using cypress-io/github-action
, then you'll need to add an additional step to your GitHub Actions workflow beforehand to edit your package.json
using yq
(because this cy.ts
script above will not be run by the action):
# Add "type": "module" to package.json for proper Cypress ESM interop
# https://github.com/cypress-io/cypress/issues/23141#issuecomment-1336532043
- name: 'Add "type": "module" to package.json for Cypress ESM interop'
run: yq --inplace --output-format=json '.type = "module"' package.json
If you need to revert it afterwards:
# Revert Cypress ESM interop changes to package.json
# https://github.com/cypress-io/cypress/issues/23141#issuecomment-1336532043
- name: 'Revert Cypress ESM interop changes to package.json'
run: yq --inplace --output-format=json 'del(.type)' package.json
Downside: extra build steps, script and complexity
@lmiller1990 would you be open to supporting this use case somehow? Eg. suggestion A) a Cypress config file with .mts extension (cypress.config.mts) or B) an additional cypress open flag that would force ESM?
I think we should just do whatever makes sense in the Node.js work - eg, if you are using CJS but want a specific file to use ESM, you can add a mjs
file, and that'll work just fine.
I think there's some other edge cases we've missed - eg, you said
cypress.config.ts file in "type": "commonjs" package
Our entire code base is using the default (which is commonjs) and cypress.config.ts
and it works fine, eg cypress.config.ts
here, etc.
import in cypress.config.ts of an ESM module (this should work with ts-node I guess?)
This should work too - I tried it out just now - you need to set module
in your tsconfig.json
. I was able to import a module using .ts
and ES module syntax (import
and export
).
The full suite of supported combinations is here (test projects are in system-tests/projects
).
If you do have a use case that isn't working that should work, please share a minimal reproduction (eg, a Cypress project that doesn't start as expected) and I look into it. We should support all project configurations.
Edit right, I see your case:
cypress installed in same package as non-ESM lib package cypress config imports ESM, and all test files are ESM lib package needs to stay as CommonJS
Does this correctly reproduce the problem if you uncomment this line? https://github.com/lmiller1990/cypress-modules/blob/main/cypress.config.ts#L3 @karlhorky? If I set my module
to es2015
in tsconfig.json
, it does not like .js
files using ESM
syntax. es_module.js
errors. es_module.ts
works fine, though. So, I guess this problem is limted to modules installed from npm that are not ESM compatible? Is this correct?
eg, if you are using CJS but want a specific file to use ESM, you can add a
mjs
file, and that'll work just fine
Yeah, tried this as well, but unfortunately we're using TypeScript elsewhere, so that means an extra manual build step of transpiling those TypeScript files (Workaround 1 above), which is also not desirable.
Edit right, I see your case
Yeah, this was the one that I was struggling with. Some dragons there! Workarounds above are an option for now, but would be cool to have other options for switching to ESM mode from Cypress - other than "type": "module"
in package.json
, which won't work for some users.
I suggested a few different solutions to signal ESM modules usage - A) allowing the .mts
config format and B) a new cypress open
CLI flag (maybe --esm
?)
Does this correctly reproduce the problem if you uncomment this line?
I don't think I meant that exactly, I forked and (I believe) reproduced the issue: https://github.com/lmiller1990/cypress-modules/pull/1/files
"type": "module"
in package.json
import
in the file in the PR abovecypress open
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/k/p/cypress-modules/packages/cjs/cypress.config.ts
at new NodeError (node:internal/errors:393:5)
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:79:11)
at defaultGetFormat (node:internal/modules/esm/get_format:121:38)
at defaultLoad (node:internal/modules/esm/load:81:20)
at nextLoad (node:internal/modules/esm/loader:163:28)
at ESMLoader.load (node:internal/modules/esm/loader:605:26)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22)
at new ModuleJob (node:internal/modules/esm/module_job:63:26)
at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:480:17)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
at async importModuleDynamicallyWrapper (node:internal/vm/module:438:15)
at async loadFile (/Users/k/Library/Caches/Cypress/11.2.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:106:14)
at async EventEmitter. (/Users/k/Library/Caches/Cypress/11.2.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:116:32)
Right, I see reproduction. Thanks for this.
I don't think we should add an --esm
flag. It should be possible to configuring things using the actual tooling, eg ts-node and Node.js. We should support mts
, though -- I didn't realize we didn't support that, but it looks like we do not check for it. Here's the files we do check for: https://github.com/cypress-io/cypress/blob/develop/packages/data-context/src/data/ProjectLifecycleManager.ts#L37-L42. Would that solve this problem?
Is there any way to execute that cypress.config.ts
outside of Cypress, eg with ts-node
? If that works, but Cypress doesn't, it's definitely a bug here. I don't think we can force ts-node to do something it doesn't support, but it's possible this will work with either a mts
extension?
If ts-node
cannot handle it independently, I don't think we can hack Cypress to do it, either. Let me play around a bit.
Just to clarify: is there any combination outside of Cypress where a CJS project can import an ESM file (using ts-node/typescript, since that's what Cypress is using)? Eg - can we execute that cypress.config.ts
outside of the context of Cypress? If so, we should support this and we've got a bug - but if not, we'd be supporting an otherwise impossible use case.
My understanding is this isn't something Node.js generally supports - this module stuff is really confusing, though, so it's entire possible there's some combination of tooling that does support this.
Edit: I wonder if bundle-require would be a potential work around, until we either decide if this is a bug, or if it's intended behavior. That module uses esbuild
to do the transformation.
Let me know if you have any ideas on the general question of whether this configuration of import .ts
using esm into a CJS project is actually valid, and something we can support. We can definitely support mts
, too, but that's separate from this - we should support a common format, even if it doesn't fix a specific bug. I'll file an issue.
I'm not 100% sure on that, but there was this part of my original comment above which may shed some light on that (I tried all 4 options, failed to get any of them to work with Cypress):
Lastly, I tried the "ESM dynamic
import()
" suggestions from a CommonJS TypeScript file consumed viats-node
here, which also did not help:
Right, so basically they are recommending to try tsimportlib
(or the helper). Neat hack, I didn't know CJS supported dynamic import
(I wonder why that is; it's not CJS syntax?)
Isn't this something that would need to happen in user-land (eg, we couldn't do this in Cypress - we don't know about your module system, we just slurp it all up and run it using ts-node
for CJS, or ts-node/esm
for ESM)?
To be clear, I'm not sure whether it's supported or not - it's probably worth a bit of research to absolutely pin down what ts-node
supports, considering it's a dependency of Cypress. I wouldn't be surprised if common scenarios do work out of the box with little ts-node
configuration. Some ideas:
A) .mts
files - maybe this just works
B) can ts-node/esm
always be used, also for CJS files?
C) what interop options are there with ts-node
? I posted some configuration options in my original comment above
Isn't this something that would need to happen in user-land
Hopefully not, this stuff is a nightmare to deal with, and leads to the feeling of Cypress being "broken", although we both know that this is a complicated interplay of module formats and dependencies. But just that we know - that doesn't help the users who are going through it.
I would suggest that ts-node
module research should be done, and then the following solutions should be considered in order of priority:
ts-node/esm
always be used? eg. even with CJS files?). Cypress should contain all of the correct ts-node
configuration options so that the user can avoid thinking about ESM interop - this is where the .mts
option would come into play--esm
to force ts-node/esm
only for Cypress files. Still not the best, because doesn't work out of the box, but better than userland solutionsWe can definitely look into this more, but quite a bit of research has been done, eg
A) .mts files - maybe this just works
Maybe - mts
just turns into mjs
, but this might help with consuming ts
filers (like your example). I made an issue for that: https://github.com/cypress-io/cypress/issues/24962
B) can ts-node/esm always be used, also for CJS files?
I don't think this is supported. See here. "... Does not transform import ...". Then you are left with import
- not require
- so unless you've got mjs
or type: module
, Node.js won't know what to do.
C) what interop options are there with ts-node? I posted some configuration options in https://github.com/cypress-io/cypress/issues/23141#issuecomment-1336477281
From my understanding (could need more research) you basically pick one module system (cjs or esm) and if you need to interop, you do so by setting the extension (eg, if you are using cjs world, you use mjs
, and if you are in esm world, you use cjs
). I just tried this out, and it works - I have an mjs
file that imports from a cjs
file (which exports via module.exports
).
this stuff is a nightmare to deal with, and leads to the feeling of Cypress being "broken"
This is the crux of the issue - Cypress feels broken, but it's generally incorrect configuration. Forcing --esm
won't work, though - this is the same as adding type: module
, isn't it?
Here's another explanation of the issue: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c.
If my understanding is correct, I think the best (and only?) action in this situation is a better error message? Cypress isn't a bundler, we can't do anything else when presented with an impossible module combination. Something like "You are trying to import a ES module into a CommonJS project. This isn't supported. See ____ for recommendations" may be the best option. WDYT?
Forcing
--esm
won't work, though - this is the same as addingtype: module
, isn't it?
No, a flag like this would signal the following, as I mentioned above:
Taking your diagram:
What I'm suggesting is that "type": "module"
should not be the only signal in the middle box - there should be other signals that indicate that this should use the ts-node/esm
loader.
mts
just turns intomjs
.mts
could be another example of a "signal" like --esm
that ts-node/esm
should be used (switching to the bottom right branch in your diagram above).
If my understanding is correct, I think the best (and only?) action in this situation is a better error message?
I don't think that's the only option - I think there are at least 3 things to try out (try out using configuration to always transform to ESM, .mts
extension and --esm
flag)
B) can ts-node/esm always be used, also for CJS files?
I don't think this is supported. See here. "... Does not transform import ...". Then you are left with import - not require - so unless you've got mjs or type: module, Node.js won't know what to do.
We can definitely look into this more, but quite a bit of research has been done
Yeah as mentioned above, I think there's probably more configuration options to look into. ts-node
has configuration options that allow treating a module as ESM:
https://replit.com/@karlhorky/ts-node-cjs-imports-esm#cjs/package.json
tsconfig.json
{
"ts-node": {
"compilerOptions": {
"module": "esnext"
},
"moduleTypes": {
"index.ts": "esm"
}
}
}
Closer to my original suggestion of a cypress.config.mts
config file, this moduleTypes
configuration above is not needed if Cypress would support this config file format:
https://replit.com/@karlhorky/ts-node-mts-imports-esm#cjs/tsconfig.json
I don't think this is supported. See here. "... Does not transform import ...". Then you are left with
import
- notrequire
- so unless you've gotmjs
ortype: module
, Node.js won't know what to do.
Also, why is this important that require()
is in the transpiled code? If the transpiled code is going to be run by Cypress, can't Cypress decide for itself that ESM is an acceptable module format for running code internally in Cypress? (since this is already working for code written in ESM format)
ts-node
by itself has no problem running ESM syntax within a package with "type": "commonjs"
in package.json
. In the example below, the ESM-only import.meta.url
feature is used:
https://replit.com/@karlhorky/ts-node-cjs-with-esnext#package.json
index.ts
console.log('\nimport.meta.url (ESM-only):', import.meta.url);
tsconfig.json
{
"ts-node": {
"compilerOptions": {
"module": "esnext"
},
"moduleTypes": {
"index.ts": "esm"
}
}
}
Thanks for the detailed reply. Let me spend a bit of time reading and researching this.
My main concern with adding any kind of Cypress-specific flag/hint is that the same flag/hint should be possible with ts-node (and whatever toolchain the user has in their project). As long as said hint is present (eg, the mts
in cypress.config.mts
) Cypress will run all the code through ts-node with the configuration/hints presented by the user and work as it would without Cypress.
I think there are at least 3 things to try out ...
I think the first two are the most viable. I am not sure if we need to modify Cypress to do the first, do we? This should be doable in ts-node/userland. Or, we might need an additional hint here.
For mts
, we should definitely do this, I created: https://github.com/cypress-io/cypress/issues/24962. We will look for mts
, use ts-node/esm
, and let ts-node
do the rest.
As for a new command line flag, I think we need to consider carefully what it would actually do - would we ultimately we would just do some additional conditions here (similar to your first option) https://github.com/cypress-io/cypress/blob/e3435b6fba05480765769974d829e69cf1e42ca0/packages/data-context/src/data/ProjectConfigIpc.ts#L273-L310?
Unfortunately we have to close this issue due to inactivity. Please comment if there is new information to provide concerning the original issue and we can reopen.
Hm, seems a bit unusual to close an issue that has a detailed description of a problem that is still open... can't imagine the motivation for closing it. I suppose maybe the Cypress team discussed that it would clean up clutter? But there are more reasons to keep it open:
At the very least, this should stay open until the accepted resolutions which are described above (eg. cypress.config.mts
) are merged + published in a release.
Then the authors / commenters in this issue (@GeorgeXCV, @CSchulz, @iamsellek, @nikolayeasygenerator, myself) should also be asked to comment whether the new release resolves their issue.
If they don't respond within a reasonable amount of time (2 weeks?) then closing seems reasonable at that point.
@karlhorky Sorry, this issue seems to have gotten incorrectly marked and not routed to the correct team and thus looked abandoned. I will reopen and route.
Thanks, no worries 👍
I was at the same point of the issue creator. My config was working before, and after trying to include removal of sucessful test videos, it broke.
I repeated many steps of this thread, then I reverted all my work and I only rewrote the function. It worked:
on('after:spec', (spec, results: CypressCommandLine.RunResult) => {
// delete the video if the spec passed and no tests retried
if (results && results.video) {
const failed = results.tests.some((test) => test.state === 'failed')
if (!failed) {
unlink(results.video, () => {
console.log(`${results.video} deleted`)
})
}
}
})
Seems like the problem appears using lodash or del.
Seems like the problem appears using lodash or del.
del has a similar issue https://github.com/sindresorhus/del/issues/152
Could be unrelated to Cypress - can you post a minimal reproduction? Lodash should be working fine, I tried this with a ESM only project and did not encounter any issues.
I think there is many ways to encounter this issue, some might be an issue in Cypress, some with external modules.
After I updated my config file using the changes from https://github.com/cypress-io/cypress-documentation/pull/5198, this issue was resolved for me. More specifically, the errors went away when I removed the return
and substituted the del
usage with its equivalent fs
functionality.
Seems there are many issues that can manifest in this error. If anyone finds one, please post a new issue with a minimal reproduction, and we will take a look.
@lmiller1990 I don't think that this should be closed.
There is a long, detailed discussion between you and me in this issue which still should be investigated more.
I hope the Cypress team takes this issue seriously and decides to reopen, for the sake of your community.
At the very least, this should stay open until the accepted resolutions which are described above (eg. cypress.config.mts
) are merged + published in a release.
Then the authors / commenters in this issue (@GeorgeXCV, @CSchulz, @iamsellek, @nikolayeasygenerator, myself) should also be asked to comment whether the new release resolves their issue.
Oh, I'm sorry, I think you are right - I didn't realize this was the issue with the mts
discussion. I prematurely closed it. I agree, we should make sure we are in a good place with the whole ESM / CJS / TS loading.
We can track specific progress for cypress.config.mts
here: https://github.com/cypress-io/cypress/issues/24962
Current behavior
If I use the code from the snippet here for TypeScript it breaks the project:
Even just this line breaks the project:
const del = require('del')
Desired behavior
I can run my tests and the videos are deleted if test passed.
Test code to reproduce
Cypress Version
10.1.0
Other
Current snippet also isn't 100% TypeScript/Eslint friendly because uses
require
instead ofimport
and has unused parameter. Something like this should work right?