发布网友 发布时间:2024-09-26 16:14
共1个回答
热心网友 时间:2024-11-16 15:25
最近在启动一个老旧的vue项目的时候,发现在npminstall的时候,node-sass在进行编译时报关于node-gyp的错,于是花了点时间来搞清楚node-gyp的作用和背后的机理。
javascript:一个跨平台脚本语言我们知道,JavaScript脚本语言不依赖于操作系统,仅需要浏览器的支持。因此一个JavaScript脚本在编写后可以带到任意机器上使用,前提上机器上的浏览器支持JavaScript脚本语言。
像C/C++它把源程序由特定平台的编译器一次性编译为平台相关的机器码,是无法跨平台。像JavaScript它不直接面向底层,它使用特定的解释器(chrome或者nodejs),把代码一行行解释为机器码,类似于同声翻译,它的优点是可以跨平台,缺点是执行速度慢,暴露源程序。
所以,nodejs也是跨平台的,所以对于任何的nodejs模块理论也应该是跨平台的。然而,有些nodejs模块直接或间接使用原生C/C++代码,这些东西要跨平台,就需要使用源码根据实际的操作平台环境进行原生模块编译。除了nodejs模块外,还有一些Node插件,它们也是由C/C++编写的,所以在使用的时候也需要编译。
Node插件Node插件是用C/C++语言编写的动态链接库,使用require()?加载到Node环境中,能够像普通Node模块一样使用。Node插件可以用于编写高性能C++算法,也可以用在Node环境与其他C/C++库之间提供接口封装,实现互相调用。
在早期的Node插件开发中,严重依赖V8引擎的API,可能都遇到过升级Node版本后插件不可用的情况,需要重新编译。这是因为Node版本升级,V8引擎的二进制ABI接口发生变化,导致之前编译的Node插件不可用。
为了解决这一问题,在Node8.0版本中发布了新的N-API接口。N-API并不是一种新的插件编写模式,N-API是对V8引擎API的封装,以C风格API提供对外接口,并且保证接口是ABI稳定的。使用N-API编写的Node插件能够一次编写、一次编译,跨多个Node版本运行。N-API接口在8.12.0以及更高版本中已经处于稳定状态(参见abi-stable-node),可以放心在生产环境投入使用。
编译Node插件使用node-gyp。下面我们尝试编译一个简单的hello-world插件。
在?github.com/nodejs/node…?中有官方提供的多个Node插件上手示例项目。其中多数小demo官方有提供了3种实现方式,分别是NAN,N-API以及node-addon-api。node-addon-api是对C形式的N-API的C++封装,同样是ABI兼容的。我个人推荐使用node-addon-api。NAN是早期的写插件使用的API,需要和V8API结合使用,现在已经不再推荐。
通过使用node-addon-api,插件代码比直接使用N-API更加简洁、易读。NODE_MODULE第一个参数是插件名称,第二个参数是Initialize注册函数。Initialize注册函数中,将?hello?绑定到函数?Method?上。
hello_world.cc
#include<node.h>voidMethod(constv8::FunctionCallbackInfo<v8::Value>&args){v8::Isolate*isolate=args.GetIsolate();args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate,"world").ToLocalChecked());}voidInitialize(v8::Local<v8::Object>exports){NODE_SET_METHOD(exports,"hello",Method);}NODE_MODULE(NODE_GYP_MODULE_NAME,Initialize)binding.gyp
{"targets":[{"target_name":"hello_world","sources":["hello_world.cc"]}]}index.js
constbinding=require('./build/Release/hello_world');console.log(binding.hello());package.json
"scripts":{"build":"node-gypconfigure&&node-gypbuild","dev":"nodeindex.js"}执行npmrunbuild,生成hello_world.node文件,然后像引入其他js模块一样就可以正常使用了。
在javascript端,也可以使用bindings来加载模块。因为Node插件历史发展中,二进制文件会被编译产出到很多不同的位置,使用bindings可以解决寻找插件路径的问题,bindings检查所有可能的插件构建位置,返回第一个成功的加载位置。首先安装bindings
npminstallbindingsnode-gyp在上面编译hello_world.cc的工具是node-gyp,要理解node-gyp首先要知道什么是gyp。
gyp其实是一个用来生成项目文件的工具,一开始是设计给chromium项目使用的,后来大家发现比较好用就用到了其他地方。生成项目文件后就可以调用GCC,vsbuild,xcode等编译平台来编译。至于为什么要有node-gyp,是由于node程序中需要调用一些其他语言编写的工具甚至是dll,需要先编译一下,否则就会有跨平台的问题,例如在windows上运行的软件copy到mac上就不能用了,但是如果源码支持,编译一下,在mac上还是可以用的。
长久以来linux的二进制分发一直是巨坑,npm为了方便干脆就直接源码分发,用户装的时候再现场编译。不过对另一些人二进制分发就比源码分发简单多了,所以还有个node-pre-gyp来干二进制扩展的分发。
node-pre-gyp上面node-gyp固然相当方便了,但是每一次安装node原生模块的时候,都需要根据平台(Windows、Linux、macOS以及对应的x86、x64、arm64等等)进行源码编译,这样做费时费力。为什么不一开始就针对这些平台编译好了做成二进制制品发布呢?反正一般来说主流的平台架构就那么一些(Windows、Linux、macOS)。所以node-pre--gyp就帮我们做了这件事。原生模块开发者将代码编译生成各个平台架构的二进制包直接发布到node-pre-gyp上,当我们的node项目安装原生模块时候。处理流程就是首先去node-pre-gyp上找有没有当前平台的组件包,有的话直接拉取使用,如果没有则进行原生编译。下图是node-sqlite3的二进制包:
于是乎,当我们进行node原生模块安装的时候,一般会有如下的流程:
针对当前平台架构优先考虑node-pre-gyp方式进行安装,但是为了防止无法获取针对对应平台编译好的二进制包(网络原因、暂时没有对应平台的二进制包),进入第2步;
下载原生模块源码,然后使用node-gyp进行项目构建,得到与平台相关的源码项目文件(Windows则生成vcxproj项目,Linux下是Makefile);在这个过程,node-gyp会使用Python进行自动化构建操作,这也是为什么有些朋友安装node原生模块的时候,会报错找不到Python。
调用平台对应的编译工具进行编译。在Windows的环境下,node-gyp会查找本地的MSBuild/CL等编译工具,而这些编译工具又一般在VisualStudio安装的时候,也一并安装在了机器上。这就是为什么有些朋友没有安装VisualStudio的时候,会报错。
原文:https://juejin.cn/post/7101945682277171213