NodeJS:在同一项目中加载 ES 模块和原生插件
NodeJS: loading ES Modules and native addons in the same 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)
正在安装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 will treat the following as ES modules when passed to node
as the initial input, or when referenced by import
statements within ES module code:
• Files ending in .js
when the nearest parent package.json
file contains a top-level "type" field with a value of "module"
.
我们在项目的package.json
文件中添加"type": "module"
字段
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
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
它 ceases to be supported(如 与 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.
临时解决方案
经过一段时间搜索文档,我找到了一个临时解决方案: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 the node
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'
]
有一些帖子达到了同样的分辨率 (ref 1, )。
但是,使用 实验性标志 似乎不是 运行 生产应用程序的正确方法。
使用 esm
包
的替代方案失败
从那时起,已经尝试了几次失败的尝试来避免使用 --experimental-*
标志。通过搜索,我发现一些帖子 (, ) 推荐使用 esm
包。
然而,此时,当我尝试这个 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
addons 的问题?
- 避免使用
--experimental-specifier-resolution=node
标志。
- 也许
esm
可以解决上述问题。 usage 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 模块然后尝试切换到 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');
在实际问题之前(见最后),请让我通过示例展示导致该问题的步骤:
正在创建项目
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 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"
.
我们在项目的package.json
文件中添加"type": "module"
字段
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
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
它 ceases to be supported(如 与 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.
临时解决方案
经过一段时间搜索文档,我找到了一个临时解决方案: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'
]
有一些帖子达到了同样的分辨率 (ref 1,
使用 esm
包
从那时起,已经尝试了几次失败的尝试来避免使用 --experimental-*
标志。通过搜索,我发现一些帖子 (esm
包。
然而,此时,当我尝试这个 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
addons 的问题?- 避免使用
--experimental-specifier-resolution=node
标志。
- 避免使用
- 也许
esm
可以解决上述问题。 usageesm
包有什么我做错了吗?- 如果它是正确的用法,有没有办法使用 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 模块然后尝试切换到 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');