Node.js10即将推出,并且有很多改进。令我们兴奋的是对本地模块库n-api的更新。它在即将发布的版本中脱离实验状态。
与其他语言相比,JavaScript总是拥有最低限度的标准库。一开始,我们只在浏览器中使用JavaScript。随着浏览器逐渐发展并成熟为应用程序虚拟机,需要通过浏览器库添加更多功能。这带来了新的应用程序,如Web蓝牙,WebUSB等;不断扩展我们可以使用JavaScript的东西。
一点历史
有一次,我们意识到,由于其事件驱动性,JavaScript将成为编写高度可扩展的服务器应用程序的绝佳语言。Node.js诞生了。一个新的最小标准库,由用于编写非浏览器应用程序(如文件系统绑定,TCP堆栈,模块加载程序等)所需的一些基本功能组成。很难准确估计未来的用例,所以为了使平台更加灵活,增加了用C/C++编写模块的能力。这使得开发人员可以充分利用其平台上提供的任何API,但仍然将其公开为JavaScriptAPI供用户使用。
许多伟大的模块是这样写的。LevelDB是一个嵌入式的快速数据库,它被编写为一个本地模块,它将LevelDBC++代码与易于使用的JavaScriptAPI相结合。LevelDB引发了一个生态系统,在其中开发了许多有趣的模块和应用程序。很少有LevelDB的用户知道模块中的C++是如何工作的,但幸运的是,我们不需要-本地模块将所有这些都抽象出来。
随着越来越多的人开始使用本地模块,我们也学到了一些缺点。事实证明,它们很难维护,因为用于实现模块的V8API变化很大。对于用户使用模块,他们需要在他们的机器上安装一个完整的编译器堆栈(在Windows上,这个过程涉及用户必须安装VisualStudio!)。
AlongCameNAN
为了解决不断变化的V8API的问题,NAN诞生了。NAN代表“Node.js的NativeAbstractions”,它是一系列抽象出不断变化的V8API之间差异的宏。实际上,NAN最初由RodVagg创建,以帮助LevelDB开发。这意味着您可以使用最新版本的Node.js编写本地模块,并且可以在大多数以前的版本中工作,而且不会太复杂。这也意味着在大多数较新的Node.js版本中,您的旧模块将继续编译。这是能够维护原生模块的巨大改进。
//sillyNANbackedmodulethatprintsastringfromc++#include#includeusingnamespacev8;NAN_METHOD(Print){if(!info[0]-IsString())returnNan::ThrowError(Mustpassastring);Nan::Utf8Stringpath(info[0]);printf(PrintedfromC++:%s\n,*path);}NAN_MODULE_INIT(InitAll){Nan::Set(target,Nan::New(print).ToLocalChecked(),Nan::GetFunction(Nan::New(Print)).ToLocalChecked());}NODE_MODULE(a_native_module,InitAll)
(请参阅完整的NAN示例回购)
预构建
为了避免必须安装编译工具链,在发布模块之前执行了一系列实验。预编译的二进制文件将在GitHub发布的地方在线托管,并npminstall在模块安装时通过脚本下载。如果没有可用的预构建可用,那么npminstall脚本将像往常一样回退编译模块。
你可以在leveldown仓库中看到这个例子。
尽管如此,还是有很多问题继续出现。偶尔,NAN将不得不做出向后不兼容的改变。这意味着旧模块不会在较新版本的Node.js上编译而不升级它们。引入新的Node.js兼容运行时间(如Electron)使情况更加复杂。使用Node.js编译的模块不会在Electron上运行,从而让用户留下不明显的错误。
网络代理和从第三方来源下载二进制文件的安全问题使预编译下载变得困难。具有讽刺意味的是,prebuilds使得Electron也很难使用。下载的预制版针对的npminstall是正在运行的Node.js版本,而不是Electron版本。另外,对npminstall脚本的硬性要求也可能是一个安全问题,因为用户禁用这些脚本以避免运行npm蠕虫。
现在
NAN和prebuilds使事情变得更好;但仍然不如我们想要的那么好。本地模块仍然被认为是“昂贵”的依赖关系,并且经常用作参数来包含Node.js核心与npm模块中的某些内容。
幸运的是,事情正在迅速改善,我们已经处于现在的阶段,我们拥有编写和发布本地模块的工具,用户可以在很少或没有技术开销的情况下安装它们。
N-API
为了使本地模块更易于编写和维护,Node.js核心贡献者一直在开发一种新的核心API,称为n-api(或node-api)。
n-api背后的想法是在您编写本机模块的V8API上构建一个稳定的接口。这种方法引入了一系列好处:
不需要重新编译模块,因为接口永远不会中断(将其视为系统调用,但是对于Node.js)。
允许V8以外的JavaScript引擎执行n-api。
只要实现n-api,就可以在Electron和其他运行时使用本机模块。
N-api的性能影响很小,因为您必须通过一个纤细的抽象层而不是原始的V8代码。但是,它有很好的文档。
一段时间以来,n-api一直是一个非实验性的API。它将在Node.js10中被释放,并且后端将会到达Node.js8和6(虽然现在是6的实验)。
这里是我们的NAN例子,从上面移植到n-api:
//sillyn-apibackedmodulethatprintsastringfromc#include#includenapi_valueprint(napi_envenv,napi_callback_infoinfo){napi_valueargv[1];size_targc=1;napi_get_cb_info(env,info,argc,argv,NULL,NULL);if(argc1){napi_throw_error(env,EINVAL,Toofewarguments);returnNULL;}charstr[];size_tstr_len;if(napi_get_value_string_utf8(env,argv[0],(char*)str,,str_len)!=napi_ok){napi_throw_error(env,EINVAL,Expectedstring);returnNULL;}printf(PrintedfromC:%s\n,str);returnNULL;}napi_valueinit_all(napi_envenv,napi_valueexports){napi_valueprint_fn;napi_create_function(env,NULL,0,print,NULL,print_fn);napi_set_named_property(env,exports,print,print_fn);returnexports;}NAPI_MODULE(NODE_GYP_MODULE_NAME,init_all)
(查看完整的n-api示例回购,并查看Node.js核心中的示例)
除了CAPI之外,还有更高级别的C++包装器,也称为node-addon-api。C++包装器是由n-API合作者维护的npm模块。使用C++包装器,您可以获得支持,直到Node.js4为止,因为它具有旧版本的兼容性层。
通常,在封装C接口时使用CAPI,而在封装C++API时使用C++API。
捆绑预制
使用n-api支持预编译变得更容易。由于n-api具有稳定的API,因此我们可以为Node.js10预先生成一个模块,并且它可以在Node.js11,12和更新版本上运行。
为了解决在安装时不得不下载预构建的问题,我和其他一些贡献者最近发布了一组模块,称为prebuildify,node-gyp-build和prebuildify-ci。
prebuildify将预先建立你的模块node-gyp-build可以在安装时测试预构建,并支持使用JavaScriptAPI从磁盘加载预构建。prebuildify-ci可帮助您在ci上设置prebuildify,以便为Linux32/64位,MacOS和Windows32/64位自动构建。
其他预建模块存在于npm上。那么他们与prebuildify有什么不同呢?使用prebuildify而不是在安装时为您的平台下载预构建,我们只需在node_modules发布之前将./prebuilds文件夹中的所有平台的所有预构建绑定到npm。在安装时,我们使用node-gyp-build来简单测试模块中捆绑的任何预构建组件是否可以加载到平台上。如果不是的话,我们通常会把编译器工具链叫做npm。
如果出于安全原因禁用安装脚本,预构建仍然会在运行时加载。安装脚本只是为了测试它是否工作。
当我们第一次尝试这种方法时,我们prebuildify的合作者担心在node_modules文件夹内添加多个预构建的脚印会由于更大的包装大小而使安装速度变慢。具有讽刺意味的是,它实际上使我们移植的所有模块能够更快地安装。下载所需的下载特定预构建的所有依赖通常需要更长的时间,而不是简单地将它们一起下载,并与模块的其余部分一起下载。
将prebuildify与n-api结合在一起非常合适。N-API意味着你需要做很少的预构建-每个平台需要一个平台来支持每个平台和一个Node.js版本。当发布新的Node.js版本时,您不需要发布新模块。
您可以在n-api示例回购中看到如何使用prebuildify与n-api和ci的完整示例。
在您正在开发的平台以外的平台上构建预构建可能有点乏味。这就是为什么我们创建了prebuildify-ci;它会设置travis和appveyor来为新版本添加标签时构建模块。
1.首先设置你的模块。运行prebuildify-ciinit将设置一个appveyor.yml与travis.yml文件,当它被标记的是你的预构建模块。在成功构建之后,它会暂时将预构建体上传到GitHub发布版,以便在发布模块之前从那里下载。
prebuildify-ciinit
2.gitpush一个标签发布(并且不会在npm上发布)。
git