首页>>前端>>Node->由npm install 报关于node

由npm install 报关于node

时间:2023-11-29 本站 点击:0

最近在启动一个老旧的 vue 项目的时候,发现在npm install的时候,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 插件不可用。

为了解决这一问题,在 Node 8.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,需要和 V8 API 结合使用,现在已经不再推荐。

通过使用 node-addon-api,插件代码比直接使用 N-API 更加简洁、易读。 NODE_MODULE 第一个参数是插件名称,第二个参数是 Initialize 注册函数。Initialize 注册函数中,将 hello 绑定到函数 Method 上。

hello_world.cc

#include <node.h>void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {  v8::Isolate* isolate = args.GetIsolate();  args.GetReturnValue().Set(v8::String::NewFromUtf8(      isolate, "world").ToLocalChecked());}void Initialize(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

const binding = require('./build/Release/hello_world'); console.log(binding.hello());

package.json

"scripts": {     "build": "node-gyp configure && node-gyp build",     "dev": "node index.js" }

执行npm run build,生成hello_world.node文件,然后像引入其他 js 模块一样就可以正常使用了。

在 javascript 端,也可以使用 bindings 来加载模块。因为 Node 插件历史发展中,二进制文件会被编译产出到很多不同的位置,使用 bindings 可以解决寻找插件路径的问题,bindings 检查所有可能的插件构建位置,返回第一个成功的加载位置。首先安装 bindings

npm install bindings

node-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等编译工具,而这些编译工具又一般在Visual Studio安装的时候,也一并安装在了机器上。这就是为什么有些朋友没有安装Visual Studio的时候,会报错。

原文:https://juejin.cn/post/7101945682277171213


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Node/934.html