mocha
mocha copied to clipboard
🚀 Feature: Support ESM (ECMAScript Modules) in watch mode
Prerequisites
- [x] Checked that your issue hasn't already been filed by cross-referencing issues with the
faq
label - [x] Checked next-gen ES issues and syntax problems by using the same environment and/or transpiler configuration without Mocha to ensure it isn't just a feature that actually isn't supported in the environment in question or a bug in your code.
- [x] 'Smoke tested' the code to be tested by running it outside the real test suite to get a better sense of whether the problem is in the code under test, your usage of Mocha, or Mocha itself
- [x] Ensured that there is no discrepancy between the locally and globally installed versions of Mocha. You can find them with:
node node_modules/.bin/mocha --version
(Local) andmocha --version
(Global). We recommend that you not install Mocha globally.
Description
When running --watch
within a package that has "type": "module"
set in its package.json
, Mocha fails to properly import
, instead attempting to require()
the file.
$ npm run test-mocha:watch
> @ test-mocha:watch /home/jdalrymple/src/bugs/mochajs
> mocha --require @babel/register $(find ./src -type f -name '*.test.js') --watch
internal/modules/cjs/loader.js:1217
throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
^
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /.../src/index.test.js
require() of ES modules is not supported.
require() of /.../src/index.test.js from /.../node_modules/mocha/lib/mocha.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.test.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /.../package.json.
at Module._extensions..js (internal/modules/cjs/loader.js:1217:13)
at Object.newLoader [as .js] (/home/jdalrymple/src/bugs/mochajs/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:1050:32)
at Function.Module._load (internal/modules/cjs/loader.js:938:14)
at Module.require (internal/modules/cjs/loader.js:1090:19)
at require (internal/modules/cjs/helpers.js:75:18)
at /home/jdalrymple/src/bugs/mochajs/node_modules/mocha/lib/mocha.js:384:36
at Array.forEach (<anonymous>)
at Mocha.loadFiles (/home/jdalrymple/src/bugs/mochajs/node_modules/mocha/lib/mocha.js:381:14)
at Mocha.run (/home/jdalrymple/src/bugs/mochajs/node_modules/mocha/lib/mocha.js:954:10)
at Object.run (/home/jdalrymple/src/bugs/mochajs/node_modules/mocha/lib/cli/watch-run.js:223:20)
at FSWatcher.<anonymous> (/home/jdalrymple/src/bugs/mochajs/node_modules/mocha/lib/cli/watch-run.js:169:14)
at FSWatcher.emit (events.js:314:20)
at /home/jdalrymple/src/bugs/mochajs/node_modules/mocha/node_modules/chokidar/index.js:364:35
at processTicksAndRejections (internal/process/task_queues.js:75:11) {
code: 'ERR_REQUIRE_ESM'
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! @ test-mocha:watch: `mocha --require @babel/register $(find ./src -type f -name '*.test.js') --watch`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the @ test-mocha:watch script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /home/jdalrymple/.npm/_logs/2020-07-14T00_20_36_063Z-debug.log
Steps to Reproduce
For convenience, the following respository was created that adequately reproduces the issue:
- Ensure your node version matches the one listed below
- Clone the repo: https://github.com/Swivelgames/issues-mochajs-4374
- Install its dependencies:
npm i
- Test the repo:
npm run test-mocha
- Start a watch:
npm run test-mocha:watch
Expected Behavior:
- Mocha would begin watching the files
Actual Behavior:
- Error is thrown when attempting to watch files
Versions
Package Versions
- node: v14.5.0
- mocha: 8.0.1
- babel: v7.10.4
Environment
- OS: Linux, Arch, x86_64
- Shell: tmux + zsh
Additional Information
It is my understanding that the following scenarios are applicable:
import
should be used under the following conditions:
- If the target file's associated
package.json
file contains"type": "module"
and its file extension is.js
- If the target file's extension is
.mjs
, regardless of what is inside its associatedpackage.json
file
require()
should be used under the following conditions:
- If the target file's associated
package.json
file does NOT contain"type": "module"
and its file extension is.js
- If the target file's extension is
.cjs
, regardless of what is inside its associatedpackage.json
file
Conclusion
Presently, it appears as though --watch
is assuming the target file is a CommonJS module when its extension is .js
, and failing to yield to its package.json
file's "type": "module"
.
Considerations
It's worth considering the fact that the package.json
file associated with the file being imported may be different than the package.json
in the directory that mocha is being called, even in the event that the target file's path does not include node_modules
.
Please see our docs: current limitations.
We do not (yet?) support ESModules in watch
mode. Node's ESM implementation is using its separate cache (not require.cache
), which currently can't be cleared before re-running the tests in watch
mode.
@Swivelgames Have you tried --parallel
combined with --watch
? It might work, since we dump the workers after each run, but I don't think we are testing it anywhere yet.
@boneskull I'm not @Swivelgames but your suggestion of adding --parallel works for me. Thanks!
To support ESM in watch mode, I think you will need to implement your own ESM loader hook. It can append query params to each imported module, effectively making them unique. By appending a different query param the second time, you can ensure a fresh copy of the module is imported. This may, however, be a memory leak, if node has no way to clear the previously-imported copies of those modules.
It is probably worth sharing this use-case with the node maintainers so they can add the necessary API surface to node.
https://github.com/nodejs/modules/issues
EDIT: I see this was already raised with the node team: https://github.com/nodejs/node/issues/49442
Well I've "fixed" my issue by switching to jest... Very happy about the change so far.
@djfm that's funny, as I've switched from Jest to Mocha using testdouble.js
I had a nightmare of a time getting Jest and ESM mocking to work: https://github.com/facebook/jest/issues/10025#issuecomment-921942214
Based on the comment of @cspotcode and the experiment by @vsnthdev in https://github.com/nodejs/node/issues/49442, I've gone ahead and implemented this as a proof of concept for mocha --watch
, and it does indeed seem to work:
https://github.com/lehni/mocha/commit/99d5a14d3a1b11f81dbb08e1895d43061e7b0541
To run mocha in this mode, you simply pass the --esm
option along with --watch
. Without --esm
, nothing changes.
This may not be ready for primetime yet, but it works well for my use-case and it shows a viable path forward for ESM.
Perhaps it can be wrapped up for release?
I've just made my branch work for NPM installs, so if you want to test the esm watch mode in your project, you can replace the mocha dependency:
yarn add -D mocha@https://github.com/lehni/mocha#99d5a14d3a1b11f81dbb08e1895d43061e7b0541
# Or:
npm install -D https://github.com/lehni/mocha#99d5a14d3a1b11f81dbb08e1895d43061e7b0541
And after that, simply run your test command with --watch --esm
:
yarn test --watch --esm
Please let me know how this is working for you!
I've added auto-detection of esm watch mode now, so the --esm
argument is not required anymore:
https://github.com/lehni/mocha/commit/64c5fcc79ce4f2232b0450db2a7f5e87e0f538a7
Just fyi as of May 2022 this is still an open issue and quite a significant impediment.
As an interim workaround I ended up with nodemon in front of Mocha, which works flawlessly and doesn't have a memleak issue mentioned in the PR 👆.
As a reference, here's the nodemon.test.json
that works for me (I'm using TypeScript, hence out
dir):
{
"exec": "npx mocha --node-option=experimental-network-imports",
"watch": [
"./out/**/*"
],
"delay": 100,
"env": {
"NODE_ENV": "test"
}
}
Possible workaround, using fswatch:
fswatch lib/ test/ --event Updated | xargs -I _ mocha test/file.js -b
Having to use
"test-watch":"nodemon --exec mocha spec",
in package.json scripts
is a workaround, but it'd be better if it Just Worked
Another simple workaround is to use entr to watch your source files:
{
"scripts": {
"test:watch": "ls lib/*.js | entr -r mocha"
}
}
I've tried @boneskull solution and combined the --parallel
with --watch
flag. It worked for me.
Here is the part of my package.json
scripts
section
"test:watch": "mocha --watch --parallel --slow 100"
I found one pretty simple solution for typescript and esm. I use tsx
in mocha config file. Post here. Now mocha works with simple script "test:watch": "mocha --watch"