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

Support ES6 style tests without transpiler usage #3006

Closed
4 tasks done
SGD1953 opened this issue Sep 18, 2017 · 75 comments
Closed
4 tasks done

Support ES6 style tests without transpiler usage #3006

SGD1953 opened this issue Sep 18, 2017 · 75 comments
Labels
area: usability concerning user experience or interface type: feature enhancement proposal

Comments

@SGD1953
Copy link

SGD1953 commented Sep 18, 2017

Prerequisites

  • Checked that your issue isn't already filed by cross referencing issues with the common mistake label
  • 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.
  • '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
  • 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.

@ScottFreeCode
Copy link
Contributor

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.

@SGD1953
Copy link
Author

SGD1953 commented Sep 18, 2017

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.

@ScottFreeCode
Copy link
Contributor

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...

@SGD1953
Copy link
Author

SGD1953 commented Sep 19, 2017

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);

?

@ScottFreeCode
Copy link
Contributor

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:

mocha/lib/mocha.js

Lines 220 to 235 in 1cc0fc0

/**
* Load registered files.
*
* @api private
*/
Mocha.prototype.loadFiles = function (fn) {
var self = this;
var suite = this.suite;
this.files.forEach(function (file) {
file = path.resolve(file);
suite.emit('pre-require', global, file, self);
suite.emit('require', require(file), file, self);
suite.emit('post-require', global, file, self);
});
fn && fn();
};

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).

@SGD1953
Copy link
Author

SGD1953 commented Sep 20, 2017

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

@ScottFreeCode ScottFreeCode self-assigned this Sep 22, 2017
@ScottFreeCode
Copy link
Contributor

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.

@ScottFreeCode ScottFreeCode removed their assignment Sep 25, 2017
@boneskull
Copy link
Contributor

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.

@boneskull
Copy link
Contributor

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?

@boneskull
Copy link
Contributor

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

@vitalets
Copy link

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

@boneskull
Copy link
Contributor

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

@boneskull
Copy link
Contributor

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

@robogeek
Copy link

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.

@robogeek
Copy link

robogeek commented Feb 10, 2018

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');

@sorgloomer
Copy link

@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

@harrysarson
Copy link
Contributor

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.

@sorgloomer
Copy link

@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?

@stale
Copy link

stale bot commented Jun 26, 2018

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!

@stale stale bot added the stale this has been inactive for a while... label Jun 26, 2018
@demurgos
Copy link
Contributor

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

@stale stale bot removed the stale this has been inactive for a while... label Jun 26, 2018
@stefanpenner
Copy link

stefanpenner commented Jun 27, 2018

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/

@stale stale bot added the stale this has been inactive for a while... label Oct 3, 2019
@maxnordlund
Copy link

I think this is still relevant.

@stale stale bot removed the stale this has been inactive for a while... label Oct 3, 2019
@tomalec
Copy link

tomalec commented Oct 3, 2019

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

@vanslly
Copy link

vanslly commented Oct 10, 2019

@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.

@tomalec
Copy link

tomalec commented Oct 14, 2019

@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 (Palindrom/Palindrom@4983596#diff-24eabf03aee8844b2b4747aa95a6af7d),

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

@npfedwards
Copy link

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

@boneskull
Copy link
Contributor

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

@SeanOfTheHillPeople

This comment has been minimized.

@luijar
Copy link

luijar commented Nov 27, 2019

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

@demurgos
Copy link
Contributor

@luijar This is being worked on at #4038

@juergba
Copy link
Contributor

juergba commented Jan 13, 2020

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

@brettz9
Copy link

brettz9 commented Jan 13, 2020

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).

@juergba
Copy link
Contributor

juergba commented Jan 14, 2020

@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.

@brettz9
Copy link

brettz9 commented Jan 14, 2020

@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.

@boneskull
Copy link
Contributor

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

@brettz9
Copy link

brettz9 commented Jan 14, 2020

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).

@outsideris outsideris added type: feature enhancement proposal area: usability concerning user experience or interface labels Jan 19, 2020
@concatime
Copy link

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.

@cedx
Copy link

cedx commented Feb 4, 2020

@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

@concatime
Copy link

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

@juergba
Copy link
Contributor

juergba commented Feb 29, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: usability concerning user experience or interface type: feature enhancement proposal
Projects
None yet
Development

No branches or pull requests