-
Notifications
You must be signed in to change notification settings - Fork 147
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 ES modules with .js extension and package type module
#170
Comments
If anyone else stumbles on this issue, here is the workaround that I am using in the meantime until official support arrives: import glob from 'glob';
import Jasmine from 'jasmine';
glob('./dist/spec/**/*.js', async (err, files) => {
if (err) { throw err; }
let jasmine = new Jasmine();
await Promise.all(files.map(f => import(f)));
jasmine.execute();
}); I save this as The only problem with it is that I'm losing the ability to pass CLI arguments and IDE integration suffers a bit. Anyway, Jasmine is amazing for making it so easy to run the tests from arbitrary sources! |
Loading files as ES modules when |
I would be happy to work on this, even though I am not familiar with the Jasmine codebase yet. I suppose the first step is to decide which detection logic to use. As far as I am aware, there is no specific Node API to detect the module type of a file. Option 1: I think it would be easiest to use something like In theory, each sub-directory could have its own package.json and Node seems to respect the settings of the 'closest' package.json. Option 2: let file = './path/to/some-es-module.js';
try {
require(file);
} catch (e) {
if (e.code === 'ERR_REQUIRE_ESM') {
import(file);
} else {
throw e;
}
} I found another instance where you already rely on an Error's Line 113 in 5fc7e6a
Not sure if there is any other way to detect the file type that I am not aware of. I'm still looking at the documentation trying to find alternatives. |
After thinking about this for a little bit I would probably favour the second option, because it allows us to have exactly the same detection algorithm as Node without having to worry about re-implementing it (or tracking possible future changes) |
Looking into documentation, I wonder if "import" could be used t import both cjs and esm... Reading it here: https://nodejs.org/api/esm.html#esm_interoperability_with_commonjs This is the test I did: main.mjs:
output But this would be a breaking change, since it requires the main.mjs to be an esm module. |
That's a good point. Node.js seems to allow importing CommonJS modules with Does it even require In conclusion, using that strategy would definitely be a breaking change because it would drop support for Node.js 10. You can see Jasmine's officially supported Node versions here: Line 35 in 5fc7e6a
Btw, not sure if you noticed it, but I had already created pull request #173 which uses |
i need to point out !!!! that no detection of the cases is needed Node Top Level Import allows Import of CJS and ESM
so you can always go for Top Level Import no dynamic import needed. And also other big news Node Now got Top Level Await also so you could do await import() but as sayed before general top level import for everything is fine already |
That’s correct, but in this case we are talking about a situation where:
|
I spent some time both experimenting with Node's actual behavior and digging into the spec a bit. Since ES modules and CommonJS modules have different semantics, I wanted to answer two questions:
For the first question, I used a few different approaches to try to figure out what kind of module the file was being loaded as. For each approach I tried loading the module via
That's not a complete exploration of all the possible differences. But it's enough to show that loading a CommonJS module via For the second question, I can't find anything in either the Node documentation or the dynamic import proposal that discusses using either static or dynamic import to load CommonJS modules. It could be that the Node team thought carefully about what should happen when a CommonJS module is imported and made a decision that they plan to stick with, or it could be that the current behavior is an accidental result of how things are implemented. In any case, I don’t see anything in the draft proposal that prevents future versions of Node from either defaulting to ES module semantics or disallowing So where does that leave us?
We could check the package type as described in the title of this issue. That’s slightly fiddly (we’d need to make sure we check the correct package.json in cases where the imported module is in a different package). But it should be pretty robust, and it should just work without users having to do any configuration. Thoughts? |
@sgravrock even if this is the wrong topic for that but we in nodejs got import { createRequire } from 'module' and can this way init a require = createRequrire(import.,meta.url); we are also able to change the behavior of all loaders via hooks and we are able to write a own require shim via dynamic import like async function require(path) {
let _module = window.module;
window.module = {};
await import(path);
let exports = module.exports;
window.module = _module; // restore global
return exports;
}
(async () => { // top-level await cannot come soon enough…
let x = await require("https://cdn.jsdelivr.net/gh/x/lib/index.js");
})(); hope that helps you a bit while this is true not everything we have but we can not write books in so short time |
@sgravrock : here is the doc to the dynamic import in node JS: https://nodejs.org/dist/latest-v14.x/docs/api/esm.html#esm_import_expressions |
@sgravrock sorry for a stupid question but cant' we just simply read package.json from current working dir? |
Not a stupid question at all. The problem is that the module being imported may be in a different package than the one the current working dir is in. That's actually pretty common. In normal Jasmine usage, the current working dir is in the user's package but all library code, including Jasmine's, is in other packages. We need to make sure that Jasmine's own helpers as well as any other helper files that come from libraries are loaded correctly even when the library and the user's package have different package types. |
@jehon If I'm reading it correctly, that doc says that import statements can be used to import either ES or CommonJS modules. It doesn't explicitly say whether dynamic import can be used to import CommonJS modules, but I think it's reasonable to infer that it can. I'm not sure how to reconcile that with the differences I was able to observe between the CommonJS environment and the environment provided by both static import and dynamic import. I want to be able to just import everything and let Node figure it out. But I'm worried about breaking things, especially since I don't have access to a large set of real-world Jasmine test suites to try it on. Maybe one option would be to add an off-by-default option to use dynamic import for everything, and then (if all goes well) make it the default in the next major release. |
@sgravrock import and import() can load both types thats correct but only when the correct package.json is in the module dir at all you should avoid mixing CJS and ESM Code Project Wide so you transpil everything down to CJS or Everything up to ESM All the Interop stuff is NodeJS Special Magic that only happens in Nodejs and is not Part of any Spec or any Other Environment. |
Indeed, import() (and all nodejs stuff) make all the magic when the project is ... correctly configured :-) @sgravrock : I agree with you, I would do that: add a flag in a minor release to allow esm loading with "import", and then make that mandatory for the next major release. A bit more complex to maintain in the meantime, but more "error" prone. Here, in jasmine, we "import" packages, but we don't care about the "exported" stuff from the package, so this should be the easy path... Stuff become more complex when dealing with exported stuff from cjs into esm (to my experience). |
* Uses import rather than require if jsLoader: "import" is set in config * Opt-in for now. import is supposed to work for all CommonJS modules too, but there are subtle differences. * Fixes #170.
Hmm. Jasmine fails to import its own specs on Windows, when configured to use dynamic import for everything:
I don't have easy access to Windows, so it might take a while to get to the bottom of this. If any of you have Windows & would be willing to try to figure out what's wrong on the |
Also, it looks like Node 10 |
It turns out that the problem isn't Windows. It's that importing .js files in Node 12 is a compatibility minefield, with different point releases behaving very differently:
Unlike Node 10, we’re going to be living with Node 12 for years to come. So I think we need to be more cautious about this. Using dynamic |
Ok, I think I have something that works reliably in Node <= 12.17 and fails gracefully in older versions. I'm making it opt-in (by setting |
@sgravrock hi, testing this new version but still having problems with this, when I enable that jsLoader setting (with type:module in package.json also), it still can't follow import statements unless I include .js at the end of them (which obviously I don't want to do). I.e. |
@roger-marley-lrn You probably have to use the If you're curious, there's some background information in these threads: |
@sgravrock I see, well currently I have to transpile ESM back into CJS in order to execute a old jasmine test suite (that I'm busy resurrecting) against it due to this, since the whole codebase its to be executed against uses .js extension files containing ESM using bare imports, and I wouldn't be able to get the suggestion to rewrite hundreds of import statements that work under normal operation in order to have jasmine tests will work past PR. I'll do some reading and look into those links you've suggested (cheers!) and see if there's some switch I can pass to node or something to have it tolerate the bare imports. |
@roger-marley-lrn as node resolve works inside ESM you can import anything that has a package.json via bare identifier |
Alright looks like executing with |
To get out of trouble from the scenario (esm source + .js extension) , you'd better
Or if you use older version of Node.js / Jasmine and cannot upgrade them easily, try to install and use 'jasmine-esm' instead of 'jasmine', replace terminal command Example Scenario: nodejs version: 16.10
jasmine version: 3.9.0
file extension: .js
source type: esm spec/a.spec.js import path from 'path'; Test Result:
Footnotes |
I tried to use the new ES module support from version 3.6.2 with some *.js files that are generated via TypeScript.
The 3.6.2 release notes only mention *.mjs files, but I can't see any information on how to use ES modules with
"type": "module"
in package.json.Unfortunately, TypeScript cannot emit *.mjs files yet. There is an open issue on the project, but it seems to be far from resolved: microsoft/TypeScript#18442
As an alternative I am using
"type": "module"
, which is supported by node. However, it doesn't seem to be supported by Jasmine yet.Node version: 15.0.1
Jasmine version: 3.6.2
The text was updated successfully, but these errors were encountered: