问题描述
在实际问题之前(见文末),请让我通过一个例子展示导致该问题的步骤:
创建项目
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)
安装 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
- 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);
}
要获取所需的 Hunspell 词典 文件(aff
ix 和 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
运行项目
根据 nodejs
文档 (Determining Module System) 支持 import
/ export
:
Node.js 在作为初始输入传递给 node
或被 ES 模块代码中的 import
语句引用时,会将以下内容视为 ES modules:
• 当最近的父 .js
文件包含值为 package.json
的顶级 "type" 字段时,以 "module"
结尾的文件。
我们在项目的 "type": "module"
文件中添加 package.json
字段。
package.json
{
...
"main": "index.js","type": "module",...
}
第一次失败的运行
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'
}
挖掘一下上述错误的原因:
- 在关于如何load addons的文档中,它指的是使用
require
已编译的插件二进制文件的文件扩展名是 .node
(与 .dll
或 .so
相反)。编写 require() 函数以查找具有 .node
文件扩展名的文件并将其初始化为动态链接库。
- 一旦您将节点项目定义为
"type": "module"
,require
它ceases to be supported(如与 Commonjs 的互操作性中所述):
不支持使用 require
加载 ES 模块,因为 ES 模块具有异步执行。相反,使用 import() 从 Commonjs 模块加载 ES 模块。
临时解决方案
搜索文档一段时间后,我找到了一个临时解决方案:Customizing ESM specifier resolution algorithm:
当前的说明符解析不支持 Commonjs 加载器的所有默认行为。行为差异之一是文件扩展名的自动解析以及导入具有索引文件的目录的能力。
--experimental-specifier-resolution=[mode]
标志可用于自定义扩展解析算法。
要启用自动扩展解析并从包含索引文件的目录导入,请使用 node
模式。
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'
]
有一些帖子达到了相同的分辨率(ref 1,ref 2)。 但是,使用实验性标志似乎不是在生产环境中运行应用程序的正确方法。
失败的 esm
包替代方案
从那时起,为了避免使用 --experimental-*
标志,尝试了几次失败的尝试。经过一番搜索,我发现了一些推荐使用 esm
包的帖子(ref 1、ref 2)。
然而,此时,当我尝试这个node -r esm index.js
时,出现了一个新错误:
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'
}
上述情况可能是由于报告的问题 (Error [ERR_REQUIRE_ESM]: Must use import to load ES Module / require() of ES modules is not supported)。
- 有 proposed patch 可以修复它,虽然我自己不知道如何使用它。
const module = require('module');
module.Module._extensions['.js'] = function(module,filename) {
const content = fs.readFileSync(filename,'utf8');
module._compile(content,filename);
};
问题
- 是否有一种(标准)方法可以使用
import
/export
(ES 模块)而不会导致import
插件 出现问题?- 避免使用
--experimental-specifier-resolution=node
标志。
- 避免使用
- 也许
esm
可以解决上述问题。使用esm
包有什么问题吗?- 如果使用正确,有没有办法自己使用 the proposed patch 作为解决方法?
注意:可以从https://github.com/rellampec/test-esm.git
克隆示例的最终状态解决方法
经过一番胡说八道试图找出这个问题的根本原因。
使用 node -r esm index.js
时,esm
包已经为您完成了所有工作(如其他答案中所述),因此(其他答案中未提及):
-
package.json
应通过删除"type:" "module"
进行更新(因为它会在原生node
ES 模块功能和esm
您安装的软件包)
旁注:如果您尝试使用 node
ES Modules 然后尝试切换到 esm
包,很容易错过这一点。 >
我遇到了类似的问题并通过以下方式修复了它: https://nodejs.org/api/module.html#module_module_createrequire_filename
// The project is "type": "module" in package json
// createRequire is native in node version >= 12
import { createRequire } from 'module';
import path from 'path';
// Absolute path to node modules (or native addons)
const modulesPath = path.resolve(process.cwd(),'node_modules');
// Create the require method
const localRequire = createRequire(modulesPath);
// require the native add-on
const myNativeAddon = localRequire('my-native-addon');