Before the actual questions (see at the end), please let me show the steps that lead to that question through an example:
Creating the project
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
- dependencies of nodehun here
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 (affix and dictionary):
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
Running the project
As per nodejs documentation (Determining Module System) to support the import / export:
Node.js will treat the following as ES modules when passed to
nodeas the initial input, or when referenced byimportstatements within ES module code: • Files ending in.jswhen the nearest parentpackage.jsonfile 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:
- in the documentation on how to load addons, it refers to the use of
require
The filename extension of the compiled addon binary is
.node(as opposed to.dllor.so). The require() function is written to look for files with the.nodefile extension and initialize those as dynamically-linked libraries.
- once you define your node project as a
"type": "module",requireit ceases to be supported (as specified in Interoperability with CommonJS):
Using
requireto 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 thenodemode.
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.
- esm gets 1.3M downloads per week.
- According the read-me file in GitHub, it does not require any changes.
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).
- There is proposed patch to fix it, although I do not know how to use it myself.
const module = require('module');
module.Module._extensions['.js'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8');
module._compile(content, filename);
};
Questions
- Is there a (standard) way to use
import/export(ES Modules) without incurring in issues withimportaddons?- Avoiding the use of the
--experimental-specifier-resolution=nodeflag.
- Avoiding the use of the
- Perhaps
esmcould be the solution to the above. Is there anything I am doing wrong with the usageesmpackage?- If it is a correct usage, is there a way to use the proposed patch myself as a working around?
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