norma.moore
norma.moore

Reputation: 71

How to import ESM modules in a Node.js app at runtime without 'type':'module' or .mjs extension?

I need to develop a nodejs application in which it should be possible to dynamically import ESM modules in a commonJS module during the run time.

  1. Without requiring the need to specify "type":"module" in package.json file / use .mjs extension.
  2. Also support importing of ESM module files which in turn import some pure ESM module (example: node-fetch@3) OR Is there some option in the "esm" npm package to also support importing ESM modules that in turn import a pure ESM module(example: node-fetch@3)

Here's what I have tried so far:-

  1. Using dynamic imports This approach works as expected for importing commonjs modules dynamically during run time. But, for importing ESM modules, there is a requirement to mention "type":"module" in package.json / naming the files with ".mjs" extension.

  2. Using the esm npm package. This is an old npm package that I found which is able to import ESM modules in commonJS modules dynamically without the need to specify "type":"module" in package.json file. However, trying to import an ESM module which imports a pure ESM module(example: node-fetch@3) fails with this.

Upvotes: 6

Views: 1999

Answers (2)

ThomasReggi
ThomasReggi

Reputation: 59495

I had to dig for it in the node codebase but the --experimental-default-type=module flag allows you to run esm without it defined in package.json or naming the file .mjs.

Upvotes: 0

nktnet
nktnet

Reputation: 486

TLDR:

$ npm install import-sync

then

const importSync = require('import-sync');
const yourModule = importSync('./path/to/your/module');

There is an open issue (#904) for the esm library about

Error [ERR_INVALID_PROTOCOL]: Protocol 'node:' not supported. Expected 'file:'

If we look into the source code of, for example, index.js in node-fetch version 3, you can see that there are imports such as

import http from 'node:http';
import https from 'node:https';

which isn't compatible with the esm library.

I'm sure you're aware of this too, the default require also doesn't work with node-fetch as described in their CommonJS documentation:

node-fetch from v3 is an ESM-only module - you are not able to import it with require().

If you cannot switch to ESM, please use v2 which remains compatible with CommonJS. Critical bug fixes will continue to be published for v2.


I had a similar requirement, so I wrote import-sync:

The library wraps around esm and will enable you to dynamically import ESM modules in a CommonJS module (or even an ESM module) at runtime, including submodules that leverage pure-esm libraries such as node-fetch.

You can install the library with:

npm install import-sync

Then use it as follows:

// import importSync from 'import-sync'; // for ESM
const importSync = require('import-sync');

if (someCondition) {
  const someEs6Module = importSync('../../someEs6Module');
  console.log(someEs6Module);
}

Specific to node-fetch, here is an example:

package.json

{
  "dependencies": {
    "import-sync": "^2.0.4",
    "node-fetch": "^3.3.2"
  }
}

index.js

const importSync = require('import-sync');

const nodeFetch = importSync('node-fetch');
console.log(nodeFetch);

const myModule = importSync('./myModule');
console.log(myModule);

myModule.js

import nodeFetch from 'node-fetch';

export const randomVariable = 'helloworld';

Running

node index.js

will give the expected results:

VSCode results

If you decide to add "type": "module" to your package.json later down the line, the logic still applies. See this answer here for an example:

Hope this helps :).

Upvotes: 3

Related Questions