节点与锈:永远的友谊。 NAPI-RS方式。
#node #rust #rspack #napirs

NodeJS解决方案不是那么表现,尤其是如果我们考虑使用一堆同步操作的解决方案,反之亦然 - 我们可以使用棘手的多线程解决方案。一个很好的例子是图像处理或密码。尽管有一些性能问题,但Nodejs仍保持其主流声誉。此外,Nodejs试图更灵活。功能强大的NodeJS Addons功能使开发人员可以在C ++上编写一些NodeJS功能。 Node.js with Rust上一次变得流行。我的意思是这种技术,因为我将与nodejs讨论生锈的编程语言集成。为什么要生锈?这是一个很好的问题...我想简要提供有关生锈的一些基本事实。

  • 内存安全方法可防止内存泄漏。
  • 类型安全语法控制。
  • 由于并发管理,没有“数据竞赛”问题。
  • 程序以“提前”方式编译。
  • 利用并促进零成本的抽象。
  • 没有资源的“垃圾收集器”,没有JIT编译器,没有虚拟机。
  • 最小的运行时和内存足迹。
  • 非常好的依赖性管理工具。
  • 有用的编译器错误带有清晰可行的建议。

除此之外,Rust是多线程友好的,与C/C ++相比,它具有更简单的语法。

您可以找到following resource有价值的。 This resource会说服您关于生锈性能。

看到上面描述的生锈整合有点困难。幸运的是,进化并没有停止。今天,我很高兴向我们的技术动物园介绍一种新动物。

见Daily-Rs!

NAPI-RS是构建预编译节点的框架。

让我们跳下蝙蝠!

目的

当然,本文旨在将napi-rs介绍为将nodej与Rust集成的最简单方法。最好的方法是提供一个比标准示例更复杂的例子。

我将提供一个nodejs应用程序,该应用程序获取文件,将其上传并随后将其转换。可以说,它正在减少饱和度。上面的图像操作应在生锈的一侧提供。

,但在此之前,让我们尝试标准功能。

包装模板

首先,您需要安装RustCargo建造者包括那里。

其次,我建议通过以下template创建一个新项目。
第三,这里建议使用yarn

是时候涵盖所有要点了。

安装nodejs依赖关系并构建

yarn install
yarn build

生锈部分

Cargo.toml包含有关生锈软件包的所有信息,包括依赖项。该文件类似于Nodejs中的package.json

src/lib.rs

上面的文件包含用于未来导出的Rust定义功能。在此示例中,定义的函数plus_100将100添加到输入参数。

#![deny(clippy::all)]

use napi_derive::napi;

#[napi]
pub fn plus_100(input: u32) -> u32 {
  input + 100
}

nodejs部分

很明显,在这里看到package.json和其他JS的东西,因为我们在谈论Rust和Nodejs集成。 package.json包含诸如@napi-rs/cli之类的依赖项,使您可以构建解决方案。另外,请注意以下文件。

./index.js

此文件包含您的库与其导出。请查看代码的最后一行。

const { plus100 } = nativeBinding;

module.exports.plus100 = plus100;

您还记得上述Rust的plus100定义吗?这些线
精确地表示Rust和Nodejs之间的桥梁。

./index.d.ts

此文件包含您的Rust功能的打字稿定义(签名)。

/* tslint:disable */
/* eslint-disable */

/* auto-generated by NAPI-RS */

export function plus100(input: number): number

重要说明!您不应该编辑上述文件,因为它们已自动化并在完成yarn build命令后更改每个Rust定义更新。

./simple-test.js

以下代码说明了如何运行RUST定义的函数。注意第一行。您应该从./index.js导入功能(请参见上文)。

const { plus100 } = require("./index");

console.assert(plus100(0) === 100, "Simple test failed");

console.info("Simple test passed");

让我们运行。

node simple-test

图像处理

我们确定您的解决方案效果很好,让我们对解决方案进行友好处理。让我们通过以下步骤。

更改./Cargo.toml

[lib]
crate-type = ["cdylib"]
path = "lib/lib.rs"

path = "lib/lib.rs"已添加。现在,我们将lib文件夹代替src作为生锈代码。 src文件夹可以保留用于未来的JS/TS代码。让我们暂时删除src文件夹。

生锈的东西

首先,安装预期的生锈依赖性(image软件包)。

cargo add image

第二,创建lib/lib.rs

#![deny(clippy::all)]

use image::{GenericImageView, ImageBuffer, Pixel};

use napi_derive::napi;

#[napi]
pub fn darker(filename: String, saturation: u8) {
  let img = image::open(filename.clone()).expect("File not found!");
  let (w, h) = img.dimensions();
  let mut output = ImageBuffer::new(w, h);

  for (x, y, pixel) in img.pixels() {
    output.put_pixel(x, y, pixel.map(|p| p.saturating_sub(saturation)));
  }

  output.save(filename).unwrap();
}

#[napi]属性是一个标记,该功能应在JS/TS代码中使用。

上面的功能以文件名和饱和度,读取文件,应用饱和并重写文件。

让我们重建...

yarn build

因此,应更新index.jsindex.d.ts

https://github.com/buchslava/napi-rs-images/blob/main/cube.png复制到项目的根源。

另外,让我们更改simple-test.js

const { darker } = require("./index");

darker("./cube.png", 50);

是时候运行它了。

node simple-test

或在下面运行以下命令,如果要从一开始就重现所有步骤。

git clone git@github.com:buchslava/napi-rs-images.git
cd napi-rs-images
yarn
yarn build
node simple-test

查看以下更改。

The Cube

我们的生锈部分已经准备就绪,是时候实施一个Web应用程序,该应用程序允许我们上传/删除文件并在后面显示。

如果您想立即尝试使用该应用程序,则可以使用https://github.com/buchslava/napi-rs-images玩。否则,请在下面阅读我的解释。

最后的针迹

首先,我们需要安装预期的nodejs依赖项。

yarn add ejs
yarn add express
yarn add express-ejs-layouts
yarn add express-fileupload
yarn add uuid

在项目的根部下制作storage文件夹,然后将其添加到./.gitignore

./server.js添加到项目的根源。

const fs = require("fs");
const path = require("path");

const express = require("express");
const ejsLayouts = require("express-ejs-layouts");
const fileUpload = require("express-fileupload");
const uuidv4 = require("uuid").v4;

const { darker } = require("./index");

const STORAGE_DIR = "storage";

const app = express();

app.use(fileUpload());
app.set("view engine", "ejs");
app.use(ejsLayouts);
app.use("/storage", express.static(path.join(__dirname, STORAGE_DIR)));
app.use(express.urlencoded({ extended: true }));

app.get("/", async (req, res) => {
  let files = await fs.promises.readdir(path.join(__dirname, STORAGE_DIR));
  files = files
    .map((fileName) => ({
      name: fileName,
      time: fs
        .statSync(path.join(__dirname, STORAGE_DIR, fileName))
        .mtime.getTime(),
    }))
    .sort((a, b) => a.time - b.time)
    .map((v) => v.name);
  return res.render("upload", { files: files.reverse() });
});

app.post("/uploads", function (req, res) {
  const file = req.files.upload;
  const extname = path.extname(file.name);
  const uuid = uuidv4();
  const filePath = path.join(__dirname, STORAGE_DIR, `${uuid}${extname}`);

  file.mv(filePath, (err) => {
    if (err) {
      return res.status(500).send(err);
    }
    try {
      darker(filePath, +req.body.saturation);
    } catch (e) {
      return res.status(500).send(e);
    }
    res.redirect("/");
  });
});

app.listen(3000);

另外,将"start": "node server",添加到./package.jsonscripts部分。

我不想解释上面的许多解决方案,因为这对于Nodejs的人来说是显而易见的。我只想注意以下要点。

  1. 有两个端点://upload
  2. /为我们提供了上传表格和上传和去饱和图像的列表。
  3. /upload上传并保留上载的图像并重定向到/

另外,请查看图像删节

try {
  darker(filePath, +req.body.saturation);
} catch (e) {
  return res.status(500).send(e);
}

我们从请求+req.body.saturation中获取饱和值的事实,

let files = await fs.promises.readdir(path.join(__dirname, STORAGE_DIR));
files = files
  .map((fileName) => ({
    name: fileName,
    time: fs
      .statSync(path.join(__dirname, STORAGE_DIR, fileName))
      .mtime.getTime(),
  }))
  .sort((a, b) => a.time - b.time)
  .map((v) => v.name);
return res.render("upload", { files: files.reverse() });

workat_dir是storage(见上文),我们将上传文件的排序列表传递给相关的EJS模板。

相关的EJS模板在下面。

views/layout.ejs

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
      crossorigin="anonymous"
    />
    <title>Uploads</title>
  </head>
  <body>
    <%- body %>
  </body>
</html>

views/upload.ejs

<div class="container">
  <form
    class="w-50 mx-auto my-3"
    action="/uploads"
    method="post"
    enctype="multipart/form-data"
  >
    <div class="mb-3">
      <input class="form-control" type="file" name="upload" required />
    </div>
    <div class="w-50 d-flex form-outline align-middle">
      <label class="form-label text-nowrap pr-3" for="saturation"
        >% saturation&nbsp;</label
      >
      <input
        name="saturation"
        value="65"
        type="number"
        id="saturation"
        class="form-control"
      />
    </div>
    <button class="btn btn-primary">Upload</button>
  </form>

  <div class="container">
    <% for (const file of files){ %>
    <div class="row mb-3">
      <img src="/storage/<%= file %>" class="card-img-top" alt="Image" />
    </div>
    <% } %>
  </div>
</div>

是时候测试整个解决方案了。

yarn start

尝试http://localhost:3000

最后,让我们上传几个图像。

Example 1 1

Example 1 2

和另一个

Example 2 1

Example 2 2

我想如果您上传和处理更大的图像,您会满足您对性能的好奇心。

总而言之,我想指的是here的事实。

“一个不错的功能是,该板条箱允许您纯粹使用Rust/JavaScript工具链构建附加组件,而无需涉及Node-GYP。”

那样的音乐喜欢节点的人的耳朵。

愉快的编码!