Before the actual questions (see at the end), please let me show the steps that lead to that question through an example:
tests$ mkdir esm && cd esm
tests/esm$ nvm -v
0.37.2
tests/esm$ nvm use v15
Now using node v15.6.0 (npm v7.5.6)
tests/esm$ node -v
v15.6.0
tests/esm$ npm -v
7.5.6
tests/esm$ npm init
package name: (esm) test-esm
entry point: (index.js)
Installing nodehun
tests/esm$ npm install nodehun
added 2 packages, and audited 3 packages in 11s
tests/esm$ npm ls
test-esm@1.0.0 tests/esm
└── nodehun@3.0.2
index.js
import { suggest } from './checker.js'
suggest("misspeling");
checker.js
import Nodehun from 'nodehun'
import fs from 'fs';
const affix = fs.readFileSync('dictionaries/en_NZ.aff')
const dictionary = fs.readFileSync('dictionaries/en_NZ.dic')
const nodehun = new Nodehun(affix, dictionary)
export const suggest = (word) => hun_suggest(word);
async function hun_suggest(word) {
let suggestions = await nodehun.suggest(word);
console.log(suggestions);
}
To obtain the required Hunspell dictionary files (aff
ix and dic
tionary):
tests/esm$ mkdir dictionaries && cd dictionaries
tests/esm/dictionaries$ curl https://www.softmaker.net/down/hunspell/softmaker-hunspell-english-nz-101.sox > en_NZ.sox
tests/esm/dictionaries$ unzip en_NZ.sox en_NZ.aff en_NZ.dic
As per nodejs
documentation (Determining Module System) to support the import
/ export
:
Node.js will treat the following as ES modules when passed to
node
as the initial input, or when referenced byimport
statements within ES module code: • Files ending in.js
when the nearest parentpackage.json
file contains a top-level "type" field with a value of"module"
.
We add "type": "module"
field in the package.json
file of the project.
package.json
{
...
"main": "index.js",
"type": "module",
...
}
First Failed Run
tests/esm$ node index.js
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".node" for tests/esm/node_modules/nodehun/build/Release/Nodehun.node
... omitted ...
at async link (node:internal/modules/esm/module_job:64:9) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
Digging a bit on the reason of the above error:
require
The filename extension of the compiled addon binary is
.node
(as opposed to.dll
or.so
). The require() function is written to look for files with the.node
file extension and initialize those as dynamically-linked libraries.
"type": "module"
, require
it ceases to be supported (as specified in Interoperability with CommonJS):Using
require
to load an ES module is not supported because ES modules have asynchronous execution. Instead, use import() to load an ES module from a CommonJS module.
Temporary Solution
After some time searching the documentation, I found a temporary solution: Customizing ESM specifier resolution algorithm:
The current specifier resolution does not support all default behavior of the CommonJS loader. One of the behavior differences is automatic resolution of file extensions and the ability to import directories that have an index file. The
--experimental-specifier-resolution=[mode]
flag can be used to customize the extension resolution algorithm. To enable the automatic extension resolution and importing from directories that include an index file use thenode
mode.
tests/esm$ node --experimental-specifier-resolution=node index.js
(node:XXXXX) ExperimentalWarning: The Node.js specifier resolution in ESM is experimental.
(Use `node --trace-warnings ...` to show where the warning was created)
[
'misspelling',
'misspending',
'misspeaking',
'misspell',
'dispelling',
'misapplier',
'respelling'
]
There are a some posts that get to this same resolution (ref 1, ref 2). However, using experimental flags does not seem a proper way to run your application on production.
Failed Alternative with esm
package
From that point, several failed attempts have been tried to avoid the use of --experimental-*
flags. Doing some search, I found some posts (ref 1, ref 2) recommending the use of the esm
package.
However, at this point, when I try this node -r esm index.js
, a new error appears:
tests/esm$ npm install esm
added 1 package, and audited 4 packages in 709ms
tests/esm$ npm ls
test-esm@0.1.0 tests/esm
├── esm@3.2.25
└── nodehun@3.0.2
tests/esm$ node -r esm index.js
tests/esm/index.js:1
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: tests/esm/index.js
at new NodeError (node:internal/errors:329:5)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1125:13) {
code: 'ERR_REQUIRE_ESM'
}
The above could be due to a reported issue (Error [ERR_REQUIRE_ESM]: Must use import to load ES Module / require() of ES modules is not supported).
const module = require('module');
module.Module._extensions['.js'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8');
module._compile(content, filename);
};
import
/ export
(ES Modules) without incurring in issues with import
addons?
--experimental-specifier-resolution=node
flag.esm
could be the solution to the above. Is there anything I am doing wrong with the usage esm
package?
Any hints to help to solve it would be really appreciated.
Note: the final status of the example can be cloned from https://github.com/rellampec/test-esm.git
After some ramblings trying to figure this out got to the root cause.
When using node -r esm index.js
, the esm
package does already all the work for your (as noted in other answers), and therefore (not mentioned in other answers):
package.json
should be updated by removing "type:" "module"
(as it creates unintended interactions between the native node
ES Modules feature and the esm
package you installed)Aside note: if you tried to use node
ES Modules and then you try to switch to esm
package, it is very easy to miss this point.