NodeJS基于v8 JavaScript引擎,然后v8是基于C++语言写成的,所以Node可以通过C++来进行扩展,也就是增加所谓的Addon。
NodeJS的这篇官方文档说明了如何编写C++Addon。
node-gyp
NodeJS C++Addon采用的编译工具是node-gyp,所以还要先安装node-gyp
npm install -g node-gyp
或者yarn galobal add node-gyp
- node-gyp需要python2.7的支持,要确保系统里面安装了python2.7,并且可以被node-gyp找到
- 另外还需要有C/C++编译器
node-gyp是一个meta-build(元编译)系统。对于一个Addon项目,要先执行node-gyp configure
进行配置,在生成相应的配置文件之后,,然后才能执行node-gyp build
,对Addon项目进行编译。
在node-gyp configure
之前,项目必须提供一个binding.gyp
文件。这个文件是json格式的,用来告诉node-gyp如果配置这个项目。一个简单的binding.gyp
的例子:
{
"targets": [
{
"target_name": "greeting",
"sources": [ "hello.cc" ]
}
]
}
上面的例子指的是以hello.cc
为源代码,生成名为greeting的Addon。
下面是hello.cc
的示例:
// hello.cc
#include <node.h>
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Value;
void Method(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(String::NewFromUtf8(
isolate, "Hello, world!", NewStringType::kNormal).ToLocalChecked());
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
} // namespace demo
将上面的代码保存为hello.cc
,然后在当前的目录执行node-gyp build
,成功之后就会在build/Release
目录下生成一个名叫greeting.node
的文件,这就是相应的Addon文件。可以用下面的JavaScript程序中引用这个Addon:
// test.js
var greeting = require('./build/Release/greeting')
console.log(greeting.hello())
把上面额文件保存成test.js,然后执行node test.js
,可以看到输出结果Hello, world!
。
一些注意点:
- node-gyp默认的输出在
Release
目录下,可以在node-gyp configure
的时候加上--debug
选项,启动Debug模式,这样默认输出就会在Debug
目录下。 - 在导入Addon的时候,我们需要指定Release目录或者Debug目录,比如:
require('./build/Release/greeting')
。node-bindings插件可以帮助我们自动选择相应的插件,例如:var greeting = require('bindings')('greeting.node')
- 在导入Addon的时候,
.node
的后缀是可以省略的(就像上面例子中那样)。但是需要注意的是,如果在NodeJS的模块搜索路径中存在一个greeting.js
的模块,这个.js
的模块会在.node
的模块之前被优先加载。
API使用的一些问题
NodeJS是和V8,libuv以及openssl静态链接的,所以编写的Addon也是需要和这些库静态链接,并且可以使用这些库的功能。关于链接的问题node-gyp会帮你搞定。
一般来说,NodeJS的Addon涉及到很多跟JavaScript的互操作,所以会大量地使用v8的API。但是v8的API并不稳定,在不同的版本间会有改动,出现不兼容的情况。当NodeJS携带的v8版本升级之后,很可能v8的API会改动,这时候可能需要重新编译你的Addon。
v8的API有自己的文档:v8docs
Native Abstractions for Node.js提供了一些工具,可以帮你处理不同nodejs版本间的兼容性问题。
除了直接引用v8的API之外, 你还有另外一个选项。NodeJS对v8的API进行了封装,提供了一层N-API。NodeJS会在不同的版本间在ABI级别对N-API提供兼容性支持。这意味着,基于N-API的代码写成的Addon,有可能不用重新编译,而在不同的NodeJS版本中工作。
N—API其实是一个C语言的API,相关的文档在这里。如果直接使用C语言来编写的话,代码会比较冗长,所以NodeJS对N-API提供了C++封装:node-addon-api。
在设计一个Addon的时候,一个关键问题是:这个Addon会不会被多个NodeJS实例使用,如果会,那么就要建立一个类,来保存Addon自身的数据,请参考: Context-aware addons
其他
- Extending Node.js with native C++ modules
- GYP User Documentation
- “binding.gyp” files out in the wild
- Beginners guide to writing NodeJS Addons using C++ and N-API (node-addon-api)
- Extending Node.js with native C++ modules
(本篇完)
2022-04-18更新
Node.js v16.9.1允许这样引入模块:
let { date } = await import('quasar') // module under node_modules, or your own one, etc.
date.getWeekOfYear(new Date())
参考How can I import an ES module in the Node.js REPL?
(更新完)