How to export from within an imported module's Promise?

Viewed 141

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?

2 Answers

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

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