NodeJS:在同一项目中加载 ES 模块和本机插件

rel*_*pec 3 node.js hunspell ecmascript-6

在提出实际问题(见最后)之前,请让我通过示例展示导致该问题的步骤:

\n

创建项目

\n
tests$ mkdir esm && cd esm\ntests/esm$ nvm -v\n0.37.2\ntests/esm$ nvm use v15\nNow using node v15.6.0 (npm v7.5.6)\ntests/esm$ node -v\nv15.6.0\ntests/esm$ npm -v\n7.5.6\ntests/esm$ npm init\npackage name: (esm) test-esm\nentry point: (index.js)\n
Run Code Online (Sandbox Code Playgroud)\n

安装nodehun

\n
tests/esm$ npm install nodehun\nadded 2 packages, and audited 3 packages in 11s\ntests/esm$ npm ls\ntest-esm@1.0.0 tests/esm\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 nodehun@3.0.2\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 这里是nodehun的依赖项
  • \n
\n

index.js

\n
tests$ mkdir esm && cd esm\ntests/esm$ nvm -v\n0.37.2\ntests/esm$ nvm use v15\nNow using node v15.6.0 (npm v7.5.6)\ntests/esm$ node -v\nv15.6.0\ntests/esm$ npm -v\n7.5.6\ntests/esm$ npm init\npackage name: (esm) test-esm\nentry point: (index.js)\n
Run Code Online (Sandbox Code Playgroud)\n

checker.js

\n
tests/esm$ npm install nodehun\nadded 2 packages, and audited 3 packages in 11s\ntests/esm$ npm ls\ntest-esm@1.0.0 tests/esm\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 nodehun@3.0.2\n
Run Code Online (Sandbox Code Playgroud)\n

要获取所需的Hunspell 词典文件(affix 和dictionary):

\n
tests/esm$ mkdir dictionaries && cd dictionaries\ntests/esm/dictionaries$ curl https://www.softmaker.net/down/hunspell/softmaker-hunspell-english-nz-101.sox > en_NZ.sox\ntests/esm/dictionaries$ unzip en_NZ.sox en_NZ.aff en_NZ.dic\n
Run Code Online (Sandbox Code Playgroud)\n

运行项目

\n

根据nodejs文档(确定模块系统)支持import/ export

\n
\n

当作为初始输入传递给Node.js 或由ES 模块代码中的语句引用时, Node.js 会将以下内容视为ES 模块:\n\xe2\x80\xa2当最近的父文件包含顶级"时以 结尾的文件类型”字段,值为.nodeimport.jspackage.json"module"

\n
\n

我们在项目的文件中添加"type": "module"字段。package.json

\n

package.json

\n
import { suggest } from \'./checker.js\'\nsuggest("misspeling");\n
Run Code Online (Sandbox Code Playgroud)\n

第一次运行失败

\n
import Nodehun  from \'nodehun\'\nimport fs from \'fs\';\n\nconst affix       = fs.readFileSync(\'dictionaries/en_NZ.aff\')\nconst dictionary  = fs.readFileSync(\'dictionaries/en_NZ.dic\')\nconst nodehun     = new Nodehun(affix, dictionary)\n\nexport const suggest = (word) => hun_suggest(word);\n\nasync function hun_suggest(word) {\n  let suggestions = await nodehun.suggest(word);\n  console.log(suggestions);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

深入研究一下上述错误的原因:

\n\n
\n

已编译的插件二进制文件的文件扩展名是.node(而不是.dll.so)。require() 函数用于查找具有.node文件扩展名的文件并将它们初始化为动态链接库。

\n
\n
    \n
  • 一旦将节点项目定义为 a "type": "module"require它就不再受支持(如与 CommonJS 的互操作性中所指定):
  • \n
\n
\n

不支持使用require加载ES模块,因为ES模块是异步执行的。相反,使用 import() 从 CommonJS 模块加载 ES 模块。

\n
\n

临时解决方案

\n

经过一段时间的文档搜索,我找到了一个临时解决方案自定义 ESM 说明符解析算法

\n
\n

当前说明符解析不支持 CommonJS 加载器的所有默认行为。行为差异之一是文件扩展名的自动解析以及导入具有索引文件的目录的能力。\n该--experimental-specifier-resolution=[mode]标志可用于自定义扩展名解析算法。\n要启用自动扩展名解析并从包含索引文件的目录导入索引文件使用该node模式。

\n
\n
tests/esm$ mkdir dictionaries && cd dictionaries\ntests/esm/dictionaries$ curl https://www.softmaker.net/down/hunspell/softmaker-hunspell-english-nz-101.sox > en_NZ.sox\ntests/esm/dictionaries$ unzip en_NZ.sox en_NZ.aff en_NZ.dic\n
Run Code Online (Sandbox Code Playgroud)\n

有一些帖子达到了相同的分辨率(参考 1参考 2)。\n但是,使用实验标志似乎不是在生产环境中运行应用程序的正确方法。

\n

esm包的替代方案失败

\n

从那时起,已经尝试了几次失败的尝试来避免使用--experimental-*标志。经过一些搜索,我发现了一些推荐使用该包的帖子(参考文献 1参考文献 2esm ) 。

\n
    \n
  • esm每周下载量为 130 万次。
  • \n
  • 根据GitHub中的自述文件,不需要任何更改。
  • \n
\n

然而,此时,当我尝试这个时node -r esm index.js,出现了一个新错误:

\n
{\n  ...\n  "main": "index.js",\n  "type": "module",\n  ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n

上述问题可能是由于报告的问题造成的(错误 [ERR_REQUIRE_ESM]:必须使用导入来加载 ES 模块/不支持 ES 模块的 require())。

\n
    \n
  • 建议的补丁可以修复它,尽管我自己不知道如何使用它。
  • \n
\n
tests/esm$ node index.js\nTypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".node" for tests/esm/node_modules/nodehun/build/Release/Nodehun.node\n... omitted ...\nat async link (node:internal/modules/esm/module_job:64:9) {\n  code: \'ERR_UNKNOWN_FILE_EXTENSION\'\n}\n
Run Code Online (Sandbox Code Playgroud)\n

问题

\n
    \n
  1. 是否有一种(标准)方法可以使用import/ export(ES 模块)而不会引发import 插件问题?\n
      \n
    • 避免使用标志--experimental-specifier-resolution=node
    • \n
    \n
  2. \n
  3. 也许esm可以解决上述问题。我的使用包有什么问题吗esm?\n
      \n
    • 如果这是正确的用法,有没有办法自己使用建议的补丁作为解决方法?
    • \n
    \n
  4. \n
\n

任何有助于解决该问题的提示将不胜感激。

\n

注意:示例的最终状态可以从https://github.com/rellampec/test-esm.git克隆

\n

小智 5

我遇到了类似的问题并以这种方式修复: 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'); 
Run Code Online (Sandbox Code Playgroud)