diff --git a/MODULE.bazel b/MODULE.bazel index b6f9f37a..d7ffb69f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -32,6 +32,7 @@ bazel_dep(name = "rules_nodejs", version = "6.3.5") bazel_dep(name = "rules_oci", version = "2.0.1") bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "rules_python", version = "0.40.0") +bazel_dep(name = "rules_rust", version = "0.63.0") bazel_dep(name = "rules_mypy", version = "0.29.0") bazel_dep(name = "rules_python_gazelle_plugin", version = "0.35.0") bazel_dep(name = "rules_shell", version = "0.4.1") @@ -179,7 +180,10 @@ use_repo( # https://github.com/aspect-build/rules_ts/tree/main/e2e/bzlmod node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node", dev_dependency = True) -node.toolchain(node_version = "20.18.0") +node.toolchain( + include_headers = True, + node_version = "20.18.0", +) use_repo(node, "nodejs_toolchains") pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") @@ -277,3 +281,20 @@ oci.pull( tag = "latest", ) use_repo(oci, "distroless_base", "distroless_base_linux_amd64", "distroless_base_linux_arm64", "ubuntu", "ubuntu_linux_amd64", "ubuntu_linux_arm64_v8") + +######################### +# Support for Rust, see https://github.com/bazelbuild/rules_rust +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain( + edition = "2024", + versions = ["1.89.0"], +) + +crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate.from_cargo( + name = "crate_index", + # TODO: we should have a Cargo.toml file in the repository root + cargo_lockfile = "//npm_packages/napi:Cargo.lock", + manifests = ["//npm_packages/napi:Cargo.toml"], +) +use_repo(crate, "crate_index") diff --git a/nodejs_apps/typescript/BUILD.bazel b/nodejs_apps/typescript/BUILD.bazel index 1eab769c..00227010 100644 --- a/nodejs_apps/typescript/BUILD.bazel +++ b/nodejs_apps/typescript/BUILD.bazel @@ -11,6 +11,7 @@ ts_project( declaration = True, tsconfig = {}, deps = [ + ":node_modules/@bazel-examples/napi", ":node_modules/@bazel-examples/one", ":node_modules/@bazel-examples/shared", ":node_modules/@types/node", diff --git a/nodejs_apps/typescript/package.json b/nodejs_apps/typescript/package.json index a4df71ea..2d527258 100644 --- a/nodejs_apps/typescript/package.json +++ b/nodejs_apps/typescript/package.json @@ -3,6 +3,7 @@ "inspirational-quotes": "2.0.1", "@bazel-examples/one": "workspace:*", "@bazel-examples/shared": "workspace:*", + "@bazel-examples/napi": "workspace:*", "@types/node": "18.11.9", "star-wars-quotes": "1.0.2" } diff --git a/nodejs_apps/typescript/src/main.ts b/nodejs_apps/typescript/src/main.ts index 926736b3..1561fc3d 100644 --- a/nodejs_apps/typescript/src/main.ts +++ b/nodejs_apps/typescript/src/main.ts @@ -1,6 +1,7 @@ import { platform, arch } from 'node:os'; import { one } from '@bazel-examples/one'; import { shared } from '@bazel-examples/shared'; +import { rust, c } from '@bazel-examples/napi'; import { getRandomQuote } from 'inspirational-quotes'; import * as quotes from 'star-wars-quotes'; @@ -8,5 +9,7 @@ shared(); one(); console.log(getRandomQuote()); console.log(quotes()); +console.log(rust.hello('NodeJS')); +console.log('C code does math: 1 + 2 = ' + c.add(1, 2)); console.log('Platform: ' + platform()); console.log('Architecture: ' + arch()); diff --git a/npm_packages/napi/BUILD b/npm_packages/napi/BUILD new file mode 100644 index 00000000..fc2738fe --- /dev/null +++ b/npm_packages/napi/BUILD @@ -0,0 +1,11 @@ +load("@aspect_rules_js//js:defs.bzl", "js_library") +load("@npm//:defs.bzl", "npm_link_all_packages") + +npm_link_all_packages(name = "node_modules") + +js_library( + name = "pkg", + srcs = ["package.json"], + visibility = ["//visibility:public"], + deps = ["//npm_packages/napi/src:tsc"], +) diff --git a/npm_packages/napi/Cargo.lock b/npm_packages/napi/Cargo.lock new file mode 100644 index 00000000..123a4b64 --- /dev/null +++ b/npm_packages/napi/Cargo.lock @@ -0,0 +1,260 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bazel-examples-napi" +version = "0.1.0" +dependencies = [ + "napi", + "napi-build", + "napi-derive", +] + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "ctor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "dtor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "napi" +version = "3.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1d56eed08a179e1c5bedb1613c58e882087cec97fcaa7788cc42366335a41d" +dependencies = [ + "bitflags", + "ctor", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash", +] + +[[package]] +name = "napi-build" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcae8ad5609d14afb3a3b91dee88c757016261b151e9dcecabf1b2a31a6cab14" + +[[package]] +name = "napi-derive" +version = "3.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9ad4169931359f24cc9ad3fae4ab272f0390c59f7a764f7719b1b8a3dde1af" +dependencies = [ + "convert_case", + "ctor", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a014314cb458d5832b4fc7d0041764ac136396447303d95e3fa0a5ea7cf5d3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "semver", + "syn", +] + +[[package]] +name = "napi-sys" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4e7135a8f97aa0f1509cce21a8a1f9dcec1b50d8dee006b48a5adb69a9d64d" +dependencies = [ + "libloading", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/npm_packages/napi/Cargo.toml b/npm_packages/napi/Cargo.toml new file mode 100644 index 00000000..f6c718d5 --- /dev/null +++ b/npm_packages/napi/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bazel-examples-napi" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = "3.1.0" +napi-derive = "3.1.0" + +[build-dependencies] +napi-build = "2" + +[profile.release] +lto = true +strip = "symbols" diff --git a/npm_packages/napi/README.md b/npm_packages/napi/README.md new file mode 100644 index 00000000..3e4052f3 --- /dev/null +++ b/npm_packages/napi/README.md @@ -0,0 +1,119 @@ +# Node-API for Node.js + +This package demonstrates interop between languages, using https://nodejs.org/api/n-api.html + +As described on https://nodejs.org/api/addons.html, + +> Addons are dynamically-linked shared objects written in C++. The require() function can load addons as ordinary Node.js modules. Addons provide an interface between JavaScript and C/C++ libraries. + +Since Bazel can easily produce such shared objects, there's no need for a purpose-built build system layer like https://napi.rs or https://github.com/cmake-js/cmake-js (both of which seem hacky compared with Bazel). + +## Setup + +We'll need standard rules_rust and rules_cc to get the compilation rules, and use rules_js to execute the result. + +One important bit of setup: we need the header files to develop against the Node.js toolchain, so set [`include_headers`](https://github.com/bazel-contrib/rules_nodejs/blob/main/docs/Core.md#nodejs_repositories-include_headers) in the `MODULE.bazel` file: + +``` +node.toolchain( + include_headers = True, + node_version = "20.18.0", +) +``` + +## From Rust + +We simply use the documented API at https://docs.rs/napi/latest/napi/. See the code listings in the `src/` folder beneath this README. + +As a simple example here's `src/lib.rs` containing: + +```rust +use napi_derive::napi; + +#[napi] +pub fn hello(name: String) -> String { + format!("Hello from Rust code, {}!", name) +} +``` + +To call this code from Node.js, we need a shared object, produced by the [rust_shared_library](https://bazelbuild.github.io/rules_rust/rust.html#rust_shared_library) rule. It needs to depend on `@rules_nodejs//nodejs/headers:current_node_cc_headers` so the linker can find the symbols referenced by the napi crate. + +Since the output of `rust_shared_library` has a `.dylib` extension on MacOS or a `.so` extension on Linux, we need a stable name. +Node.js standard is for bindings to be named with a `.node` extension, so we use a `copy_file` rule. + +Finally we need to wrap the `.node` file with a Node.js interface. We ought to be able to generate this, perhaps using https://crates.io/crates/tslink. In the meantime it's easy enough to write it, but we have to keep the interface up-to-date: + +```typescript +declare interface Lib { + hello: (name: string) => string; +} + +export const lib: Lib = require(require('path').resolve(__dirname, 'lib.node')); +``` + +Now we can write any Node.js code we like and call this wrapper without needing to know it has Rust code behind the scenes. + +## From C + +Node.js ships with the [`node_api.h`](https://github.com/nodejs/node-api-headers/blob/main/include/node_api.h) header file, +so just follow the instructions at https://nodejs.org/api/n-api.html#usage to produce a binding file. See the code listings in the `src/` folder beneath this README. + +```c +#define NAPI_VERSION 3 +#include +#include "adder.h" + +static napi_value Add(napi_env env, napi_callback_info info) { + ... + return return_val; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_value fn; + napi_status status = napi_create_function(env, NULL, 0, Add, NULL, &fn); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Unable to wrap native function"); + return NULL; + } + + status = napi_set_named_property(env, exports, "add", fn); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Unable to populate exports"); + return NULL; + } + + return exports; +} + +NAPI_MODULE_INIT() { + return Init(env, exports); +} +``` + +Then we use the [`linkshared`](https://bazel.build/reference/be/c-cpp#cc_binary.linkshared) attribute of `cc_binary` to +request a shared object output, and similar to above we use a `copy_file` rule to rename the result to the `.node` extension. + +The TypeScript wrapper looks the same as for Rust, since we are just pointing to any shared object file. + +## Putting it together + +Now we just have a Node.js app in `nodejs_apps/typescript` which has a type-checked usage of the native libraries above, +by importing the wrapper and calling the functions: + +```typescript +import { rust, c } from '@bazel-examples/napi'; + +console.log(rust.hello('NodeJS')); +console.log('C code does math: 1 + 2 = ' + c.add(1, 2)); +``` + +and we can verify by running it: + +```sh +alexeagle@aspect-build bazel-examples % bazel run nodejs_apps/typescript:main +INFO: Build completed successfully, 3 total actions +INFO: Running command line: bazel-bin/nodejs_apps/typescript/main_/main + +Hello from Rust code, NodeJS! +C code does math: 1 + 2 = 3 +``` diff --git a/npm_packages/napi/package.json b/npm_packages/napi/package.json new file mode 100644 index 00000000..a2fd0453 --- /dev/null +++ b/npm_packages/napi/package.json @@ -0,0 +1,10 @@ +{ + "name": "@bazel-examples/napi", + "description": "expose native Rust or C code as a Node.js module", + "private": true, + "main": "src/index.js", + "types": "src/index.d.ts", + "devDependencies": { + "@types/node": "^20.0.0" + } +} diff --git a/npm_packages/napi/src/BUILD b/npm_packages/napi/src/BUILD new file mode 100644 index 00000000..67503a61 --- /dev/null +++ b/npm_packages/napi/src/BUILD @@ -0,0 +1,82 @@ +load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file") +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") +load("@rules_rust//rust:defs.bzl", "rust_shared_library") + +cc_library( + name = "adder", + srcs = ["adder.c"], + hdrs = ["adder.h"], +) + +cc_binary( + name = "adder_binding", + srcs = ["adder_binding.c"], + linkopts = [ + "-undefined", + "dynamic_lookup", # required on macOS + ], + linkshared = True, + linkstatic = True, + visibility = ["//visibility:public"], + deps = [ + ":adder", + "@rules_nodejs//nodejs/headers:current_node_cc_headers", # keep + ], +) + +rust_shared_library( + name = "hello_binding", + srcs = ["lib.rs"], + proc_macro_deps = [ + "@crate_index//:napi-derive", + ], + rustc_flags = select({ + "@platforms//os:macos": [ + "--codegen=link-arg=-undefined", + "--codegen=link-arg=dynamic_lookup", + ], + "//conditions:default": [], + }), + deps = [ + "@crate_index//:napi", + "@rules_nodejs//nodejs/headers:current_node_cc_headers", + ], +) + +# Need to rename the files from .so/.dylib to .node for Node.js +copy_file( + name = "copy_binding", + src = ":hello_binding", + out = "my_native_rust.node", + visibility = ["//visibility:public"], +) + +copy_file( + name = "copy_c_binding", + src = ":adder_binding", + out = "my_native_c.node", + visibility = ["//visibility:public"], +) + +ts_project( + name = "tsc", + srcs = ["index.ts"], + declaration = True, + tsconfig = "//npm_packages:tsconfig", + visibility = ["//visibility:public"], + deps = [ + "//npm_packages/napi:node_modules/@types/node", + ], +) + +filegroup( + name = "pkg_contents", + srcs = [ + ":copy_binding", + ":copy_c_binding", + ":index.d.ts", + ":index.js", + ], + visibility = ["//npm_packages/napi:__pkg__"], +) diff --git a/npm_packages/napi/src/adder.c b/npm_packages/napi/src/adder.c new file mode 100644 index 00000000..833464af --- /dev/null +++ b/npm_packages/napi/src/adder.c @@ -0,0 +1,5 @@ +#include "adder.h" + +int add(int first, int second) { + return first + second; +} diff --git a/npm_packages/napi/src/adder.h b/npm_packages/napi/src/adder.h new file mode 100644 index 00000000..75ecc02c --- /dev/null +++ b/npm_packages/napi/src/adder.h @@ -0,0 +1,12 @@ +#ifndef ADDER_H +#define ADDER_H + +/** + * Adds two integers and returns their sum + * @param a First number + * @param b Second number + * @return Sum of a and b + */ +int add(int first, int second); + +#endif /* ADDER_H */ diff --git a/npm_packages/napi/src/adder_binding.c b/npm_packages/napi/src/adder_binding.c new file mode 100644 index 00000000..47147797 --- /dev/null +++ b/npm_packages/napi/src/adder_binding.c @@ -0,0 +1,67 @@ +#define NAPI_VERSION 3 +#include +#include "adder.h" + +static napi_value Add(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + napi_value this_arg = NULL; + void* data = NULL; + + napi_status status = napi_get_cb_info(env, info, &argc, args, &this_arg, &data); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to parse arguments"); + return NULL; + } + + if (argc < 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + int32_t first = 0; + int32_t second = 0; + status = napi_get_value_int32(env, args[0], &first); + if (status != napi_ok) { + napi_throw_type_error(env, NULL, "First argument must be a number"); + return NULL; + } + + status = napi_get_value_int32(env, args[1], &second); + if (status != napi_ok) { + napi_throw_type_error(env, NULL, "Second argument must be a number"); + return NULL; + } + + int result = add(first, second); + + napi_value return_val = NULL; + status = napi_create_int32(env, result, &return_val); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Unable to create return value"); + return NULL; + } + + return return_val; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_value fn = NULL; + napi_status status = napi_create_function(env, NULL, 0, Add, NULL, &fn); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Unable to wrap native function"); + return NULL; + } + + status = napi_set_named_property(env, exports, "add", fn); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Unable to populate exports"); + return NULL; + } + + return exports; +} + +NAPI_MODULE_INIT() { + return Init(env, exports); +} diff --git a/npm_packages/napi/src/index.ts b/npm_packages/napi/src/index.ts new file mode 100644 index 00000000..bbae0daa --- /dev/null +++ b/npm_packages/napi/src/index.ts @@ -0,0 +1,20 @@ +/// +import { resolve } from 'node:path'; + +// TODO: codegen from lib.rs to avoid manually keeping them in sync +// maybe https://crates.io/crates/tslink +declare interface RustNative { + hello: (name: string) => string; +} + +// TODO: codegen from adder.h to avoid manually keeping them in sync +declare interface CNative { + add: (first: number, second: number) => number; +} + +// Resolve the native.node file relative to this library's location +export const rust: RustNative = require(resolve( + __dirname, + 'my_native_rust.node' +)); +export const c: CNative = require(resolve(__dirname, 'my_native_c.node')); diff --git a/npm_packages/napi/src/lib.rs b/npm_packages/napi/src/lib.rs new file mode 100644 index 00000000..a4410650 --- /dev/null +++ b/npm_packages/napi/src/lib.rs @@ -0,0 +1,8 @@ +#![deny(clippy::all)] + +use napi_derive::napi; + +#[napi] +pub fn hello(name: String) -> String { + format!("Hello from Rust code, {}!", name) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31aa8414..8f9db165 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,6 +156,9 @@ importers: nodejs_apps/typescript: dependencies: + '@bazel-examples/napi': + specifier: workspace:* + version: link:../../npm_packages/napi '@bazel-examples/one': specifier: workspace:* version: link:../../npm_packages/one @@ -174,6 +177,12 @@ importers: npm_packages/first: {} + npm_packages/napi: + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.14.11 + npm_packages/one: {} npm_packages/shared: {}