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

其他

(本篇完)

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?

(更新完)