Mocha: Support ES6 style tests without transpiler usage

Created on 18 Sep 2017  ·  75Comments  ·  Source: mochajs/mocha

Prerequisites

  • [x] Checked that your issue isn't already filed by cross referencing issues with the common mistake 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) and mocha --version(Global). We recommend avoiding the use of globally installed Mocha.

Description

Before I start, there are already some closed issues regarding this topic but as the prerequisites have changed I would like to start a new attempt.

Now that node supports running EMCAScript modules (yes, I know it is experimental) it would be great to see mocha to work in conjunction with mjs test definitions.

Steps to Reproduce

I have a very simple test

describe('Test', function () {
});

Which i have saved as test.js and test.mjs

Expected behavior: I would like both tests to show

- test/test.js 
  0 passing (1ms)
(node:70422) ExperimentalWarning: The ESM module loader is experimental.

Actual behavior: While the js test works, the mjs test gives me

- test/test.mjs 
module.js:658
    throw new errors.Error('ERR_REQUIRE_ESM', filename);
    ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /Users/dgehl/Repositories/LOreal/code/ecom-lora/test/frontend/components/global/test.mjs

Reproduces how often: 100%

Versions

node --version - v8.5.0
mocha --version - 3.5.3

Additional Information

I _think_ that this might be that mocha's runner is using commonjs and nodejs' current implementation disallows to use ECMAScript modules from a commonjs context.

Please don't reply with "use a transpiler", I want to explicitly not use one.

Edit: in an earlier version I accidentally used jsx instead of mjs.

feature usability

Most helpful comment

We implemented Node's native ESM support in Mocha v7.1.0.

All 75 comments

Initial thoughts off the top of my head, before doing any further research:

  • I seem to recall the Node people specifically got the standard changed to allow backwards compatibility with CommonJS modules. If that's (still?) true, then I'd expect that eventually it would be supported without needing to do anything with/to Mocha. (And by "eventually" I mean "possibly even faster than Mocha changes", given the rate of Node's release cycle; see the emphasized, penultimate below for a more detailed explanation of this.)
  • Can I assume that this was run like node --experimental-modules node_modules/mocha/bin/_mocha?
  • How exactly is .jsx involved? I see that the error example shown refers to .mjs; it's unclear, from what has been posted, where the jsx is.
  • I also vaguely recall hearing of Node's initial implementation requiring the .mjs file extension; if that's (still?) true, and if you're trying to use import/export with a .jsx file (either importing a .jsx file in .mjs file or loading a .jsx file that contains import/export), could that be the issue rather than Mocha?
  • We would need to come up with a way to ensure that changes to the experimental feature don't require changes to Mocha that would have to wait for a semver major -- otherwise you could end up right back where we are now except waiting even longer (since Mocha's major release cycle is not locked to twice a year like Node's).
  • Speaking purely from my own opinion and not on behalf of the team, if the new module format cannot interoperate with the old module format without modification to existing libraries written in the old format, then the new module format is not ready yet.

I seem to recall the Node people specifically got the standard changed to allow backwards compatibility with CommonJS modules. If that's (still?) true, then I'd expect that eventually it would be supported without needing to do anything with/to Mocha. (And by "eventually" I mean "possibly even faster than Mocha changes", given the rate of Node's release cycle; see the emphasized, penultimate below for a more detailed explanation of this.)

From my (little) research it seems that at least for now it is allowed to use require from a ECMAScript module but not import from a commonjs module.

Can I assume that this was run like node --experimental-modules node_modules/mocha/bin/_mocha?
How exactly is .jsx involved? I see that the error example shown refers to .mjs; it's unclear, from what has been posted, where the jsx is.

Yes, it was run with --experimental-modules. jsx is a typo, I meant mjs, will update the initial post.

I also vaguely recall hearing of Node's initial implementation requiring the .mjs file extension; if that's (still?) true, and if you're trying to use import/export with a .jsx file (either importing a .jsx file in .mjs file or loading a .jsx file that contains import/export), could that be the issue rather than Mocha?

The issue seems to be, and I might be missing something here, that mocha uses require to load the test (that's at least my current assumption as I'm not a mocha expert, more of a user) which then includes other modules via import. This in conjunction with the first point would explain the error.

We would need to come up with a way to ensure that changes to the experimental feature don't require changes to Mocha that would have to wait for a semver major -- otherwise you could end up right back where we are now except waiting even longer (since Mocha's major release cycle is not locked to twice a year like Node's).
Speaking purely from my own opinion and not on behalf of the team, if the new module format cannot interoperate with the old module format without modification to existing libraries written in the old format, then the new module format is not ready yet.

I was afraid that this would be the answer and I understand that this is not a top priority. If my assumption of the cause of the error above is correct, soemthing like #956 could help as the entry point of the test could be a mjs module rather than commonjs which is probably hard to achieve otherwise. It seems to be on the roadmap of the nodejs team to support import from current modules, not clear about timelines however.

From my (little) research it seems that at least for now it is allowed to use require from a ECMAScript module but not import from a commonjs module.

It seems to be on the roadmap of the nodejs team to support import from current modules, not clear about timelines however.

To clarify: given that in "older" (in some cases current non-experimental) environments import is a syntax error, which can't be avoided with branching logic or anything like that, what Mocha needs isn't to be able to use import itself but rather to be able to use require to load modules that use (or that use modules that use) the new format.

jsx is a typo, I meant mjs , will update the initial post.

Thanks, that eliminates one possible angle!

If my assumption of the cause of the error above is correct, soemthing like #956 could help as the entry point of the test could be a mjs module rather than commonjs which is probably hard to achieve otherwise.

Making Mocha not create global variables is unfortunately not possible without extensive rewriting of some of the more arcane internals (I tried and couldn't figure it out myself 😿 ); however, using your own JS entry point is possible now through the "programmatic" API (which might not be documented outside of an old wiki page and the JSDoc comments in the source files, but is officially supported):

// test.mjs
var Mocha = require('mocha'),
var mocha = new Mocha();

// your mission: create a file `example.mjs`
// in the `test` folder and have it `import` stuff
// as well as using `describe` and `it` to make tests
mocha.addFile("./test/example.mjs");

mocha.run(function(failures){
  process.on('exit', function () {
    process.exit(failures ? 1 : 0);
  });
});
node --experimental-modules test.mjs

I haven't actually tried that to see if it makes a difference (need to grab the latest version of Node first), but let me know if it works for you...

First of all thank you for your support on this!

I tried this

// runner.mjs
import Mocha from 'mocha';
var mocha = new Mocha();

// your mission: create a file `example.mjs`
// in the `test` folder and have it `import` stuff
// as well as using `describe` and `it` to make tests
mocha.addFile('./test/frontend/components/global/test.mjs');

mocha.run(function (failures) {
    process.on('exit', function () {
        process.exit(failures ? 1 : 0);
    });
});

So basically made the node entry point a ECMAScript module.

I run it via node --experimental-modules --harmony ./runner.mjs

I get

(node:88620) ExperimentalWarning: The ESM module loader is experimental.
{ Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /.../test/frontend/components/global/test.mjs
    at Object.Module._extensions..mjs (module.js:658:11)
    at Module.load (module.js:545:32)
    at tryModuleLoad (module.js:508:12)
    at Function.Module._load (module.js:500:3)

what Mocha needs isn't to be able to use import itself but rather to be able to use require to load modules that use (or that use modules that use) the new format.

That I'm afraid is currently not possible in node, you can only use require in modules you imported via import. Is there a way to avoid mocha.addFile('./test/frontend/components/global/test.mjs'); and instead import the test and add the imported script like this

import test from './test';
mocha.addTest(test);

?

There's no function like that in Mocha at the moment, but you can do something along those lines. addFile just appends the file to a list that is later required by the run function. The run function calls loadFiles to require them:

https://github.com/mochajs/mocha/blob/1cc0fc0e6153bbd746b0c2da565363570432cdf7/lib/mocha.js#L220-L235

What you'd want to do is for any files that need to be imported instead of required don't call addFile (so Mocha won't try to require it on run) and instead before calling run call some code that's like what's in loadFiles but using import instead of require. I don't recall off the top of my head whether there are any restrictions on use of import that would prevent this, but if it's possible at all then I imagine it would look pretty close to:

modules.forEach(function (file) {
  file = path.resolve(file);
  mocha.suite.emit('pre-require', global, file, mocha);
  import fileExport from file; // fileExport is used by the exports interface, not sure if anything else; most interfaces act as a side effect of running the file
  mocha.suite.emit('require', fileExport, file, mocha);
  mocha.suite.emit('post-require', global, file, mocha);
});

You can also look at how https://github.com/mochajs/mocha/blob/master/bin/_mocha uses Mocha's programmatic API to get a sense of how to supply other options and how to use things like Mocha's file lookup functionality. It's not very well organized but everything the commandline interface does is in there (either directly or because in there is a call to the functions in Mocha's programmatic API).

I can come one step further but the imported test now complains it does not know about describe (ReferenceError: describe is not defined). What is the proper way to inject it? If I do

import Mocha from 'mocha';
const describe = Mocha.describe;

it complains TypeError: describe is not a function

So, my distro finally got NodeJS 8.5, and I've had a chance to play with this and confirm a couple of hunches I had but didn't want to state till I'd been able to check:

  1. I can't find any way to load an ES module without hardcoding its name in a script/module file. That means that Mocha can't load them through the commandline interface no matter what we do and regardless of any other ES vs. CommonJS semantics. If and when that changes, we'll want to know, I guess. (If it changes such that the ES module can be loaded through a variable require we probably won't need to change anything, but I don't think we can say anything about how modules work for sure until it happens.)
  2. Whereas Mocha sets up the interface in the pre-require event emission (not when the module is first loaded; the chosen interface is specific to the new Mocha instance), the new module system actually parses the dependency tree and loads dependencies before the modules that depend upon them, so the reason describe is not defined is that Mocha isn't setting it up until after the test modules are loaded. That means that it's going to be convoluted to get this to happen at all (again, unless and until require(file) is allowed and loads the dependency at that specific line, preferably synchronously so we don't have to change anything else).

However, I did discover I can make it work by exploiting the fact that imported modules are loaded in the order of the import calls. (It would get a whole lot more redundant if you use the Exports interface or a reporter that needs the name of each individual file, as described in the code I'm about to link, but in principle it works.) If that fact were to change without any of the other above facts changing, then even this would not be possible at all. But for now I think this is what you'd have to do.

https://gist.github.com/anonymous/caba0883254a2349c5615df8e9286665

node --experimental-modules ./run.mjs

Unfortunately, I'm fairly sure that's the best we can do given the way ES modules work and what Node allows at the present time.

Think of it another way:

  • import is syntax.
  • require is a function.

You cannot dynamically import anything, just as you cannot dynamically run code without the use of, for example, eval().

There is this stage 3 proposal which would allow this behavior, but I'm not sure if any runtimes are shipping it yet.

As such, there's no way for Mocha to import an .mjs file when running via mocha without adding perhaps @std/esm and using its require implementation for files with the .mjs extension. That may be a viable solution and something we could consider supporting, but a discussion (and such a PR) would likely need to come from the community, at least until this behavior isn't behind a flag.

import describe from 'mocha' is pretty low on the priority list, unfortunately, due to the inherent difficulty around this sort of thing (#956). Best to run with node and stick to consuming the globals.

Actually, it occurs to me that we could load the tests and leverage vm.runInContext, assuming such a thing supports modules. Because Node's loading behavior is tied to the .mjs extension, and vm.runInContext expects a string, don't see how it could--and there's nothing mentioned about this in the docs. Maybe an issue somewhere?

(then again, this may be exactly what @std/esm does under the hood!)

I've got mocha tests working without transpiler in a browser. Maybe it helps for this issue.

that’s unrelated as you’re not pulling mocha in as a module, but rather a script...

sorry confused myself. it’s different in a browser.

I want to weigh in with a vote of support for doing something to allow Mocha to run tests located in an ES Module. I landed here after trying to write such a test and getting a weirdo error from the Node.js module loader. I'm using Node.js 9.5, which natively supports ES6 modules.

As it currently stands, Node.js 9.5 does not allow a CommonJS module to require() an ES6 module. Maybe they're working in the direction of allowing that, I don't know.

I wrote the test as a ES6 module with the .mjs extension and tried to run it. Got the error from the loader -- I assume the mocha command results in using require() and that's why it failed.

Redid the test with the .js extension and tried to use require() to load the module that was to be tested. That also got the error from the loader.

I'm of the opinion that the Node.js world needs to consider how they'll move to and support ES6 modules. Since Mocha is a very popular tool in this world, it would be best for the Mocha team to consider how to support ES6 modules.

To follow up ... After some pondering and searching I was able to get this sequence to work as a workaround.

Name the test script with .js extension (making it a CommonJS script)

Then add this in the test script:

require = require("@std/esm")(module,{"esm":"js"});

Then I can require() an ES module as so:

const model = require('../models/notes');

@robogeek Or it might be even better to use the @std/esm preloader from commandline, so you don't have to clutter your spec files with workarounds, and can have .mjs extensions.

mocha -r @std/esm spec.mjs

Dynamic import ships with node v9.6 behind the --harmony-dynamic-import flag. Dynamic imports allow mocha to load tests contained in es6 modules without needing a transpiler.

@harrysarson It is not going to work out of the box. Mocha uses cjs modules and require, you would have to write the test files using cjs, with some additional glue code to handle the async nature of import. Or am I missing something?

I am a bot that watches issues for inactivity.
This issue hasn't had any recent activity, and I'm labeling it stale. In 14 days, if there are no further comments or activity, I will close this issue.
Thanks for contributing to Mocha!

The issue is still relevant but relies on native support for ESM. Browsers have it, Node not yet.

I was just playing around, getting familiar with ESM/.mjs and decided I needed tests for my toy. Realizing mocha is not yet officially supporting .mjs files, I through together a quick interim module (until someone has time to add full support to mocha):

https://www.npmjs.com/package/mocha-esm
PRs/Issues welcome: https://github.com/stefanpenner/mocha-esm

There might be something better out there, but it was fun to through together. so \o/

I decided to fork mocha itself to support dynamic imports (based on some ideas above, but I couldn't get it to run in any other way).

This means you can run using e.g. node --experimental-modules --harmony-dynamic-import test.mjs

Then in test.mjs:

import 'should';
import Mocha from 'mocha';

const mocha = new Mocha();

mocha.addFile(() => import("./some-module.spec.mjs"));

mocha.run(failures => {
  process.on('exit', function () {
    process.exit(failures ? 1 : 0);
  });
});

I kept the changes in mocha to support this minimal, and I didn't have time to integrate this properly for a potential PR nor did I add a specialized npm module, but you can install this fork from github directly "mocha": "git+https://[email protected]/odolha/mocha".

Note you can use this approach to load files in any async way you might want, not only via dynamic import, as mocha is expecting a function that provides a Promise.

EDIT

I forgot to mention that your tests cannot not be pure scripts with this approach, because we cannot inject context, so instead you'll need to:

// some-module.psec.mjs
export const test = ({ describe, it }) => {
  describe('Something', () => {
    it('works', () => {
      ...
}

@odolha
Thanks for linking your fork.
There already was a PR to support native ESM but it was closed because module support is still experimental.

Your implementation comforts me that adding support for this should be easy. We're many to wait eagerly for this feature :)

@demurgos :no_mouth: yeah... I just saw that PR after I did my own thing, d'oh 😃.

@harrysarson @boneskull
The esm package (formerly named @std/esm) dropped support .mjs files in this commit.
It means that it is no longer possible to use it with Mocha to test .mjs files. This is discussed in this issue.

I still want to be able to test ES modules so I can safely run them in Node or browsers.

Regarding the current module discussions, there is consensus that .mjs should be available in the final result (maybe not as the only solution, but at least available) and that import("./foo.mjs") will return a promise for the corresponding ES namespace. The fact that CJS modules are converted to a module with a default export corresponding to module.exports is more debated, but it seems to be a safe assumptions.

Would it be possible to reconsider adding native ES support using dynamic import to Mocha? The feature flag could be renamed to --experimental-es-modules (from #3253) to better signal that this is dependent on the current advancement of Node's support.
According to the deadlines, the final spec wouldn't land until Node 12 so the current implementation will remain there for some time (and is a relatively safe subset of the final proposal).

@demurgos I personally prefer to wait on this a little more before committing to any code implementations on mocha. But maybe @boneskull or @ScottFreeCode disagree?

@demurgos

The esm package (formerly named @std/esm) dropped support .mjs files in this commit.

The esm loader did not drop support for .mjs. We simply follow the current --experimental-modules implementation and throw an error when attempting to load .mjs with require. Users can still use a .js file for their test entry file that uses ESM syntax or dynamic import() to load subsequent test files of .mjs or .js, much like esm does for its own tests.

According to the deadlines, the final spec wouldn't land until Node 12 so the current implementation will remain there for some time

There is no set time for --experimental-modules to land unflagged. The hope is that it can land sometime in Node 10's support cycle _(so some time in the next 2 years)_ but there's nothing set in stone.

(and is a relatively safe subset of the final proposal).

The current --experimental-modules implementation may not be compatible with the final proposal. There are several discussions around what ESM support in Node will look like. Some proposed directions are not compatible with the current experimental implementation. Depending on how things shake out the code you write today for --experimental-modules may not work with whatever the final form is.

The esm loader did not drop support for .mjs.

My point is that esm no longer enables requiring .mjs so you no longer can use Mocha's test discovery for .mjs. But you're right, it wasn't documented so it's not really a breaking change even if other people relied on it.

Regarding the deadlines, I was referring to this issue. There seem to be an attempt for Node 11 and a final implementation for Node 12 so it can be ported to Node 10 LTS. Some wish it would happen sooner, other warned to not rush it.

My proposition is to merge #3253. This only offers an opt-in mechanism to use import(...) instead of require to load the test cases. Since I expect it to mostly be applied for .mjs in the context of --experimental-modules, I still think that it's safe. (dynamic import of .mjs returning a namespace promise is likely to remain). But I'll leave you decide if you can merge it and avoid pushing too much for it.

Again, the main reason for this PR is that without it, you no longer can use Mocha's test discovery but have to use the workaround described above by @jdalton. (.js entry point and manual imports)

My proposition is to merge #3253.

3253 had a fair few flaws is definitely not a good idea to merge it as is.

Following @jdalton example, I've setup a small workflow to test native ESM without the esm package (pure Node + Mocha).
I use asynchronous test definitions (using --delay) and --experimental-modules.

Currently, _mocha_ can only import CJS, and CJS can only import ESM using the dynamic import() pseudo-function. So I generate the following CJS entry point (its name ends with .js) which imports the spec files and triggers the test execution:

test.esm.js:

(async () => {
  await import("./test/a.spec.mjs");
  await import("./test/b.spec.mjs");
  run();
})();

(I generate the entry point with the list of imports at build time, but you can write it manually or use glob there.)

I then execute it with the following command:

NODE_OPTIONS="--experimental-modules" mocha --delay build/test/test.esm.js

I have to pass through NODE_OPTIONS because the flag is not recognized by mocha.


I still hope mocha would provide better support for experimental ESM, but at least it's nice to know that there's a way to use it today without other tools.

@demurgos this is a nice little workarround you have found :+1:.

It is good to see that it is indeed possible (if not easy) to use es modules with mocha :smile:.

@demurgos This issue is about supporting ES6 style tests without transpiler usage. What is "build time"? That code which you use to generate the test entrypoints is a transpiler, just a specialized one.

@rulatir
I mentioned that I use build tools, but they're not a the level discussed in this issue: Mocha is running with native ESM, not ESM lowered to CJS by a transpiler.

See my message:

I generate the entry point with the list of imports at build time, but you can write it manually or use glob there.

I used a build step because I want my imports to be (1) statically defined and (2) not maintain the list myself.

If you only have a few spec files, dropping (2) is fine: just have an entry point importing your 1-2 spec files.
(1) is already a specific requirement for test files, so it's mostly just a "nice-to-have" thing and you can use glob at runtime (instead of build time like myself). This is just a detail that does not matter in the end once you understand the core idea.

If you just want something like a simple copy-paste solution finding the mjs spec files at runtime, here's an example:

const {sync: globSync} = require("glob");

(async () => {
  const matches = globSync("**/*.spec.mjs");
  for (const match of matches) {
    await import(match);
  }
  run();
})();

Run it with NODE_OPTIONS="--experimental-modules" mocha --delay test.esm.js.
As you see, no build at all, but a bit more noise in the code.

I am a bot that watches issues for inactivity.
This issue hasn't had any recent activity, and I'm labeling it stale. In 14 days, if there are no further comments or activity, I will close this issue.
Thanks for contributing to Mocha!

This issue is still valid and should not be closed.

The upcoming mocha@6 release will have the whitelisted --experimental-modules flag allowing to experiment with ES6 modules more easily. Would it be possible to have a minor or patch release before the v6? I am currently finalizing a new coverage tool that does use the V8 debugger instead of Istanbul and would like to test it with Mocha and ES6 modules (without having to use a git dependency in my package.json).

@demurgos

When I try like this...

const {sync: globSync} = require("glob");
(async () => {
    const matches = globSync("**/*.spec.mjs");
    for (const match of matches) {
        await import(match);
    }
    run();
})();

I get

(node:4632) UnhandledPromiseRejectionWarning: Error: Cannot find module test/Sanity.spec.mjs

But when I run like this...

const {sync: globSync} = require("glob");
(async () => {
    await import("./Sanity.spec.mjs");
    run();
})();

It runs perfect what am I missing?

@demurgos you should coordinate with @bcoe; see https://github.com/bcoe/c8

@demurgos to cut a minor release, we'd need to cherry pick all non-breaking changes since v5.2.0 into a branch and compile them into the CHANGELOG. if you or someone else is willing to do that work, then we can cut the release.

fwiw I recommend esm over --experimental-modules until Node.js has its story straight. that will be a considerable wait.

@boneskull
Haha, thanks. I'm already working around c8 since July (I opened a bunch of PRs and issues on this repo). There are also some design decision where we disagree so we try to share most of the dependencies (I wrote the merge algorithm for example) and decided that I'll publish another tool: c88. I got it to work this week-end and I'm now testing it on my libraries. I'm able to use it with native ESM and mocha in CI. I still need some time to document and fix it but it should be ready in January).

@jrgleason
I wrote the code above of the top of my head. It seems that the issue here is that globSync returns relative paths that do not start with ./ or ../. You may want to prepend it with ./: it should work with simple relative paths.
You should also note that dynamic imports use relative URLs: #, ? and other special characters may be handled differently. If you want a rock solid solution, you should resolve the absolute path of the module and then convert it to a file URL. As part of my work on coverage, I wrote a lib to convert between absolute paths and URLs: you may want to use fromSysPath from furi. Conversions should handle any kind of path (even the Windows namespaces and UNC paths...).

Here is what a complete example may look like:

const {fromSysPath} = require("furi");
const {sync: globSync} = require("glob");
const {resolve} = require("path");

(async () => {
    const matches = globSync("**/*.spec.mjs");
    for (const match of matches) {
        await import(fromSysPath(resolve(match)).href);
    }
    run();
})();

I mean, doesn't mocha --require esm just work? Do we really need automatic detection? That sounds difficult and adds overhead...

@demurgos to cut a minor release, we'd need to cherry pick all non-breaking changes since v5.2.0 into a branch and compile them into the CHANGELOG. if you or someone else is willing to do that work, then we can cut the release.

Thanks for the proposition. It's still possible to get --experimental-modules with NODE_OPTIONS so it's not a high priority (and it may complicate the git tree to cherry-pick commits). If I manage to close the issues I have with other dependencies, I'll see if I can spend some time on this. In the mean time, I just keep an eye on the v6 milestone.

fwiw I recommend esm over --experimental-modules until Node.js has its story straight. that will be a considerable wait.
I mean, doesn't mocha --require esm just work?

I definitely agree that it is the best solution right now: it's the easiest solution to set up and has been out for some time: it works. In my case, I am maintaining my own build tools and handle native ESM as an alternative to classic CJS builds. Despite being really eager for native ESM, I still recommend to not use it as the only way to run your code: it's experimental after all :stuck_out_tongue:.

Most of my recent messages are about sharing what can be done using native ESM. This is mostly experimental work and I expect to have to change it when Node's ESM gets stable. Long term there are benefits to have a solution that does not require the esm package. Here are my reasons:

  • It reduces the amount of tools needed (lower complexity, less configuration to understand)
  • There may be a few differences between real ESM and esm around edge cases (evaluation errors, cycles, load errors, async/dynamic modules, wasm, etc.). When writing isomorphic code, it may be safer to reduce any possible source of behavior divergence. This is also kinda related to native coverage: with esm, V8 sees the transpiled output so you have to deal with source maps (not yet supported by c8, but I'm preparing a PR, they work on c88). Other differences may also appear when debugging.
  • It avoids to have to dynamically transpile the code and helps improving performance.

Do we really need automatic detection? That sounds difficult and adds overhead...

I am not sure which automatic detection you are referring to. Is it related to the PR that was sent earlier this year?


Edit: I'm also on the Node tooling Slack (mostly active on the #c8 channel) if you want to discuss.

@demurgos I think I was a bit confused about what people wanted here. Anyway...

If NODE_OPTIONS=--experimental-modules works until --experimental-modules is supported in Mocha v6.0.0, then is there any other work to be done for this issue? That's what I'm missing.

I expect that this issue should remain open until native ESM ("ES6 style tests without transpiler usage") works out of the box / as easily as CJS works currently.

The solution I posted with --delay and NODE_OPTIONS=--experimental-modules is more a workaround than proper support. I'd consider this issue fixed once you'll be able to run mocha **/*.spec.mjs and get a report.

Unfortunately, for the moment I feel that we still have to wait for Node to figure out ESM support. I'd have to check, but I think that the PR did not use automatic detection but simply imported every module (CJS or ESM) using dynamic imports. The implementation will depend on the interop story for modules.


Edit: I am referring to https://github.com/mochajs/mocha/pull/3253. It allows to load all modules as ESM (no automatic detection).

I am a bot that watches issues for inactivity.
This issue hasn't had any recent activity, and I'm labeling it stale. In 14 days, if there are no further comments or activity, I will close this issue.
Thanks for contributing to Mocha!

Node 12 should include the new ESM implementation. It will be the occasion to check how to support ES modules in Mocha.

I hit GC bug when using Mocha an ESM, but it is reported and confirmed so it should be fixed: https://github.com/nodejs/node/issues/27492.

Beside this bug, the strategy described in my comment above still works to use Mocha with ESM.

Hi Mocha people, thank you for creating and maintaining nice tool!

This is just FYI comment. For the last few months I have been working on Mocha and * -test.mjs , using the patches like below. There is almost no problem to run mocha test/*.mjs (without transpiler or esm npm module).
https://gist.github.com/tadd/756d21bad38933c179f10e59bddee6b4

Of course this patch is not "production-ready" for Mocha committers; this is just a hack for Mocha users who want to use ESM in test codes as soon as possible.
I've been used Node.js v11 with --experimental-modules option. Currently I'm with v12 and also works.

It is another story, but Node v12 introduced .cjs extension and "type" field in package.json. It seems that these also need to be considered.

Hi Mocha people, thank you for creating and maintaining nice tool!

Indeed, and I also would like to give thanks 😄

I took a different approach to make loadFiles stay synchronous, see below. It has worked for me since February. Unlike some other hacks, this still allows all flags, inspect/devtools and accurate code coverage using c8. That last feature is why I really need native ESM, because the esm package have different offsets for each file. (The module's exports get listed and confusing istanbul).

https://gist.github.com/maxnordlund/a860dd67013beaf0f31ce776536f0a47

Hello! This is also needed to test any code which depends on native ES6 project, e.g. lit-element. Otherwise, it throws like this:

node_modules/lit-element/lit-element.js:14
import { TemplateResult } from 'lit-html';
       ^

SyntaxError: Unexpected token {
    at Module._compile (internal/modules/cjs/loader.js:703:23)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
    at Module.load (internal/modules/cjs/loader.js:628:32)
    at Function.Module._load (internal/modules/cjs/loader.js:555:12)
    at Module.require (internal/modules/cjs/loader.js:666:19)
    at require (internal/modules/cjs/helpers.js:16:16)
    ...

I don't know if there's a workaround for this, but currently I don't think there's a way to use Mocha with ES6 native framework.

@heruan I posted a solution working since Node 8 in the comments above. Here is an updated version that requires Node 10.12 for native file URL conversions:

Add the following test.esm.js file:

const {pathToFileURL} = require("url");
const {sync: globSync} = require("glob");
const {resolve} = require("path");

(async () => {
    const matches = globSync("**/*.spec.mjs"); // Change the glob to match your test files
    for (const match of matches) {
        await import(pathToFileURL(resolve(match)).href);
    }
    run();
})();

Then run the tests with mocha --experimental-modules --delay test.esm.js.

This code works by using test.esm.js as the commonjs bridge to load the ESM tests.

Node 12 has a reported issue where the IIAFE is collected by th GC (nodejs/node#27492), it should be fixed in one of the next minor versions (there may be some workarounds but I haven't looked into them yet). I recommend to use the latest Node 10 version until it is fixed.

Thar will trigger an UnhandledPromiseRejectionWarning warning if there's any error. Better to chain a console.error or otherwise handle the error.

import glob from "glob"
import { pathToFileURL } from "url"
import { resolve } from "path"
import { promisify } from "util"

const globAsync = promisify(glob)

async function main() {
  const matches = await glob("test/**/*.mjs")

  for (const match of matches) {
    await import(pathToFileURL(resolve(match)).href)
  }

  run()
}

main().catch(console.error)

_I know this is using import over require, but see my gist for a solution that let's you stay in ESM land_

@demurgos Thanks for the code snippet for Node 10.12 you posted ten days ago!

I am running Node 12.1 and it seems to work fine. Will this soon be added to Mocha, or is there an even easier way to do this with Node 12? Also, how to I use this in --watch mode?

https://github.com/standard-things/esm seems to work out of the box with --require esm, but it would be great to ditch another dependency :) Thanks.

If Mocha were to switch to ESM in source, Rollup could provide an ESM distribution file as well as a CommonJS and/or UMD ones.

Another advantage here is that browser HTML wouldn't need to be polluted with extra script tags to pull in Mocha. The test files (or main test entrance file) could do the importing (and avoid "magic" within the test files as far as figuring out where the variables are coming from--only need to trace the import paths).

One can use CSS plugins for Rollup as well to allow injecting mocha.css, further minimizing need for HTML clutter.

I am a bot that watches issues for inactivity.
This issue hasn't had any recent activity, and I'm labeling it stale. In 14 days, if there are no further comments or activity, I will close this issue.
Thanks for contributing to Mocha!

I think this is still relevant.

Was anybody able to run Mocha with ES6 Modules on (edit: ~Travis~) Node >=12.11.0?
On 12.10.0 it seems, I set it up successfully:
mocha-run.js

(async () => {
    await import("./tests.js");
    run();
})();

Then mocha --experimental-modules --delay ./mocha-run.js works like charm.

But for some unknown reason, on 12.11.0, it behaves as if there would be no --delay param:

>  mocha --experimental-modules --delay ./mocha-run.js

(node:6439) ExperimentalWarning: The ESM module loader is experimental.

internal/modules/cjs/loader.js:1007

      internalBinding('errors').triggerUncaughtException(

                                ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /home/travis/build/Palindrom/Palindrom/mocha-run.js

@tomalec I am running mocha with ES modules on node 12.11.1:

__mocha-run.js__

(async () => {
    await import("./tests.mjs");
    run();
})();

However, watch mode is not working. mocha waits for file changes, but doesn't run another test run after a file has been changed.

@vanslly Lucky you ;)
For me with Node 12.12.0 (https://travis-ci.org/Palindrom/Palindrom/builds/597771311#L450) and mocha-run.js as you suggested above (https://github.com/Palindrom/Palindrom/commit/49835962bdd61c849f115e271bbc6c3f82d30511#diff-24eabf03aee8844b2b4747aa95a6af7d),

mocha --experimental-modules --delay test/mocha-run.js https://travis-ci.org/Palindrom/Palindrom/builds/597771311#L643, , throws still the same error

That this is still an issue is crazy! ESM is no longer hidden away behind --experimental-modules. This is the future.

err, actually it was just announced a couple days ago...

Too late—we've all switched to Jest.

Hey guys, just want to make sure this is kept alive. Please make this a top priority and thanks for all the great work!

@luijar This is being worked on at #4038

We published yesterday an experimental release v7.0.0-esm1: see release notes.

This is great to see!

Can I ask though--does the lack of reference to the browser mean that ESM usage is not available on the browser or merely that you haven't needed to specify browser versions as with Node. I think it might help to mention in the release notes the status for browsers (and if not supported, what the plans might be for its support).

@brettz9
NodeJs ESM does not affect Mocha browser in any way. The tests you run in your browser are not loaded by NodeJs, you have to do that yourself in your HTML code.

If I remember well, you have to set the <script> tag to type="module" attribute. For both your test files and for the Mocha script, in order to keep the loading sequence. ESM should have been working with the browser for years.

@juergba : yes, sure, but one needs an ESM export distribution file so one can use such as import mocha from '../node_modules/mocha/mocha-esm.js'; without compilation--and for those using using compilation (e.g., so as to be able to just use import mocha from 'mocha';), they would want module in package.json so the bundlers can discover the ESM build automatically.

mocha is written in commonjs; we cannot put a “module” field in package.json. Mocha will support running tests in node written in ESM.

If you didn't want to refactor to use ESM internally, you should still be able to use Rollup with its CommonJS plugin and indicate the ESM target file in order to support module (such as Sinon offers and most packages of note I have encountered, jQuery being the only other notable exception and they have been refactoring to use ESM).

I’ve created a sample project to test mocha with ESM. I can successfully run the tests, but wasn’t (_yet_) able to run coverage with nyc/istanbul. Your help will be welcome.

@concatime Until nyc is made compatible, you can use c8: https://www.npmjs.com/package/c8

c8 --all --include=lib/**/*.js --reporter=lcovonly node_modules/.bin/mocha --recursive

@cedx I’ve updated my template repo., and it works. Neat!

We implemented Node's native ESM support in Mocha v7.1.0.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

luoxi001713 picture luoxi001713  ·  3Comments

niftylettuce picture niftylettuce  ·  3Comments

Aarbel picture Aarbel  ·  3Comments

robertherber picture robertherber  ·  3Comments

juergba picture juergba  ·  3Comments