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

Cannot use mjs file synchronously in commonJS without await / .then() #33605

Closed
Raynos opened this issue May 28, 2020 · 16 comments
Closed

Cannot use mjs file synchronously in commonJS without await / .then() #33605

Raynos opened this issue May 28, 2020 · 16 comments

Comments

@Raynos
Copy link
Contributor

Raynos commented May 28, 2020

  • Version: v12.17.0
  • Platform: Linux
  • Subsystem: Ubuntu

What steps will reproduce the bug?

Require an mjs file.

const isPromise = require('./third-party-is-promise.mjs')

console.log(typeof isPromise)

Example https://gist.github.com/Raynos/2c6bb0a8537dbf7d464a9b85813cd3b1

How often does it reproduce? Is there a required condition?

Always

What is the expected behavior?

I can load an ESM module from a commonJS file.

What do you see instead?

~/tmp/temp123
$ node broken.cjs 
internal/modules/cjs/loader.js:984
    throw new ERR_REQUIRE_ESM(filename);
    ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /home/raynos/tmp/temp123/third-party-is-promise.mjs
    at Module.load (internal/modules/cjs/loader.js:984:11)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Module.require (internal/modules/cjs/loader.js:1026:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (/home/raynos/tmp/temp123/broken.cjs:1:19)
    at Module._compile (internal/modules/cjs/loader.js:1138:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12) {
  code: 'ERR_REQUIRE_ESM'
}

Additional information

I would like to import an ESM synchronously from a commonJS file.

Node added child_process.execSync() a while ago but that used to not be possible.

It would be really nice if node had some kind of importSync() feature.

I also tried

const isPromise = import('./third-party-is-promise.mjs')

console.log(typeof isPromise)
console.log('then method', typeof isPromise.then)

This returns a promise instead of the code which is not what I want

@Raynos
Copy link
Contributor Author

Raynos commented May 28, 2020

I see from a related PR that a function promiseWait exists.

If I can just call require.promiseWait(import('./esm.mjs')) then that would also allow me to get a promise for a ESM module using import and then just blocking wait until the promise resolves.

We don't have to do anything "special" with require we just expose a process.awaitSync(p) function.

@bmeck
Copy link
Member

bmeck commented May 28, 2020

Relevant discussion here nodejs/modules#454

@GeoffreyBooth
Copy link
Member

And there's a PR at #30891, but it has so many issues that that's why nodejs/modules#454 was opened. It's not clear that the issues raised will be addressable; or in plainer terms, require of ESM might not be possible.

ESM code can be loaded into a CommonJS module via import(). https://nodejs.org/api/esm.html#esm_import_expressions

@Raynos
Copy link
Contributor Author

Raynos commented May 28, 2020

Ok, I would like to importSync or require a ESM module and if it has a top level await you can just throw an exception saying "Some ESM has top level await; not supported"

@Raynos
Copy link
Contributor Author

Raynos commented May 28, 2020

I've also found a userland solution

const syncify = require('@snek/syncify')
const isPromise = syncify(import('./third-party-is-promise.mjs'))

There are native modules on npm that implement awaitSync() as a C++ function.

@devsnek
Copy link
Member

devsnek commented May 28, 2020

Those who use @snek/syncify are doomed to a life of sadness

@GeoffreyBooth
Copy link
Member

More realistically, if you're looking for a way to "bottle up" promises and resolve them synchronously, you should look into node-fibers and its use in the framework Meteor. That's at least one example of doing so in a widely used framework that has many production apps. Meteor (at least currently) only supports ESM by transpiling it to CommonJS, however; it's unclear whether it can ever support native ESM along with node-fibers. Based on the discussion in nodejs/modules#454, such a thing might not be possible, or possible without violating spec (which is something that Node can't do).

@bmeck
Copy link
Member

bmeck commented May 29, 2020

The ECMAScript spec does actually provide data on if top level await is used: https://tc39.es/proposal-top-level-await/#sec-cyclic-module-records in a new internal [[Async]] slot, however this is set for more than just top level await; all WASM modules have that as true due to their initialization lifecycle as I understand. This internal slot is not currently reflected in V8's public API though to my knowledge. I think we could review this if/when it is exposed as a means to help transitioning.

@Raynos
Copy link
Contributor Author

Raynos commented May 29, 2020

Could you explain more about the limitations of loading a WASM module synchronously ?

I think require(mjs) and require(wasm) are two completely different beasts.

I would be fine with a require(mjs) that fails on transitive dependencies that use Top level await and WASM.

The fast majority of the uses cases for ESM is trivial code that doesn't do anything special with top level await or WASM.

@bmeck
Copy link
Member

bmeck commented May 29, 2020

@Raynos I am not as clear on the reasoning, but they always initialize async in module form. Someone more vested in their design/implementation would be more knowledgable.

@devsnek
Copy link
Member

devsnek commented May 29, 2020

wasm esm modules take advantage of off-thread compilation and things of that nature, using wasm's async apis.

@Raynos Raynos changed the title Cannot require mjs file. Cannot use mjs file in commonJS without importSync. Jul 20, 2020
@Raynos
Copy link
Contributor Author

Raynos commented Jul 20, 2020

I was looking at using ESM again and sharing some ideas on twitter ( https://twitter.com/Raynos/status/1285109697662640130 ).

Having importSync() available would help migrate to ESM without breaking all my commonJS users.

@devsnek
Copy link
Member

devsnek commented Jul 20, 2020

so, we're not ever going to have importSync. cjs to esm interop is a problem we'd like to solve but it wouldn't be in this manner, and I think we already have other issues tracking this more generally, so I'm going to close this.

@devsnek devsnek closed this as completed Jul 20, 2020
@Raynos
Copy link
Contributor Author

Raynos commented Jul 20, 2020

Could you link to the more general issue for using esm from cjs ? I just did a search and could not find anything.

@Raynos Raynos changed the title Cannot use mjs file in commonJS without importSync. Cannot use mjs file synchronously in commonJS without await / .then() Jul 20, 2020
@devsnek
Copy link
Member

devsnek commented Jul 20, 2020

@Raynos nodejs/modules#454

@Raynos
Copy link
Contributor Author

Raynos commented Jul 20, 2020

Oh it's not in this repository. And there's this PR which was already referenced ( #30891 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants