1

I'm trying to work around the fact that I need to import a pure ESM package into a non-module. I can't change that fact about the script.

The workaround I'm trying to use is the import() function (a "dynamic import"). That returns a Promise instead of the actual module. I can't use await since I'm not in a module, so instead I'm using .then().

The pure ESM package (unist-util-visit) is used in a function exported by my script, which is then used in another script. So the import chain goes:

importer.js imports imported.js imports unist-util-visit

So the issue is that anything I export from within the .then() function in imported.js does not show up in importer.js.

And it's not even a timing issue. I used an EventEmitter to make importer.js wait until imported.js's .then() is done executing:

imported.js:

const EventEmitter = require('events');

module.exports.emitter = new EventEmitter();
module.exports.outsideFxn = function () {
  console.log('hello');
}
import('unist-util-visit').then((unistUtilVisit) => {
  module.exports.fxn = function() {
    console.log(`unistUtilVisit: ${typeof unistUtilVisit}`);
  }
  module.exports.emitter.emit('ready');
});

importer.js:

import('./imported.js').then((imported) => {
  console.log("In importer.js's .then():");
  console.log('  fxn:', imported.fxn);
  console.log('  outsideFxn:', imported.outsideFxn);
  imported.emitter.on('ready', () => {
    console.log("After imported.js is done:")
    console.log('  fxn:', imported.fxn);
  });
});

When I execute it, this is the output:

$ node importer.js 
In importer.js's .then():
  fxn: undefined
  outsideFxn: [Function (anonymous)]
After imported.js is done:
  fxn: undefined

What am I missing? Why are no exports being defined in the .then() function? How can I get my function exported?

Nick S
  • 555
  • 4
  • 17

2 Answers2

2

Instead of

import('unist-util-visit').then((unistUtilVisit) => {
  module.exports.fxn = function() {
    console.log(`unistUtilVisit: ${typeof unistUtilVisit}`);
  }
  module.exports.emitter.emit('ready');
});

where you attempt to modify your module's exports after it has probably been consumed by dependents, why not export a promise that yields the function when it completes?

module.exports.fxnP = 
    import('unist-util-visit')
      .then((unistUtilVisit) => () => { 
          console.log(`unistUtilVisit: ${typeof unistUtilVisit}`); 
      });

Now you consume it:

import('./imported.js').then((imported) => {
    imported.fxnP.then((fxn) => {
        fxn();
    });
});

or, more neatly::

import('./imported.js')
    .then(({fxnP}) => fxnP)
    .then((fxn) => fxn());
spender
  • 117,338
  • 33
  • 229
  • 351
  • 1
    Oh wow, thank you! It works, though I'm not sure why. I really need to get a better handle on Promises. – Nick S Jul 14 '21 at 19:43
1

You could use a custom require hook like jiti which can do exactly what you want synchronously.

Without hooking require:

$ node test.cjs

// test.cjs
const jiti = require('jiti')();
const unistUtilVisit = jiti("unist-util-visit");

Hooking require programmatically:

$ node test.cjs

// test.cjs
require('jiti')().register();
const unistUtilVisit = require("unist-util-visit");

Hooking require through a command line option:

$ node -r jiti/register test.cjs

// test.cjs
const unistUtilVisit = require("unist-util-visit");
luawtf
  • 576
  • 8
  • 21
  • 1
    Wow, this is amazing. So it looks like it transpiles the imported module and its dependencies to convert them to CommonJS? Are there any compatibility issues with this? – Nick S Jul 14 '21 at 19:51
  • 1
    Not that I'm aware of, everything should just work! It's a direct translation. – luawtf Jul 14 '21 at 20:14
  • Okay I found my first incompatibility! My module exported a function as default, and when `jiti` imports it, instead of the result being the function, it's an object whose `default` key is the function. – Nick S Jul 16 '21 at 20:06
  • No, that's intended behavior for compatibility reasons. I would consider ot a bug if it didn't do that. See this answer: https://stackoverflow.com/a/40295288/12375929 – luawtf Jul 17 '21 at 00:02