Native meets modern.
Install NodeJS dependencies:
yarn install
Build the Native module:
yarn cmake:build
Build the NodeJS module:
yarn pkg:build
Start the application:
yarn start
Successful output:
$ yarn start
yarn run v1.22.22
$ node ./dist/cli.mjs
addon.node is online!
Node Addon API 8
Done in <time>s.
esnative
intends to create a simple, clean, and well-defined entry point for projects targeting native platforms that combines modern ECMAscript tooling, features, and workflow, with the power of CMake-driven, system-level languages such as C and C++, and bindings provided by Node-API.
This repository currently represents a project template. Once built (see Quickstart), the application exposes two components - a library, and a command-line interface - as entry points. The command-line interface's functionality is derived from the library.
-
The library consists of two distinct parts; one is written in
c++
(or optionallyc
orrust
), then compiled to a system-native binary file with the file extension.node
; the other part is written inTypescript
, then transpiled toJavascript
, and which imports the functions and variables from the binary file (on disk), binds them to corresponding ECMAscript, and then exports the bindings as an ECMA module. -
The command-line interface is purely - or, almost exclusively - written in modern Typescript, then compiled into a module-friendly,
npm
-ready bundle of it's own. It imports and consumes the library - via the bindings - , and is intended to be executed by thenode
runtime.
The particularly interesting part of this project, is using NodeJS as an entry point to system-level languages and their paradigms. Usually, in system-application development, a main()
function is written in the lower-level language and defines what the application should do when it is executed. The main()
call is necessarily compiled-in to the binary, and thus any changes by the developer (such as bug fixes, enhancements, etc) require re-configuring and rebuilding. In the case of a Node Addon, our application entrypoint is written in ECMAscript languages like Javascript
and Typescript
; of which, even the latter now offers near-instant transpilation times, and much more rapid iteration over development cycles in comparison to native coding languages.
In short, I'm trying to make the commands shown below a repeatable, accessible entry point for new project templates:
{
/** 'PUBLIC' COMMANDS INTENDED FOR CONSUMERS OF THE TEST MODULE */
"start": "node ./dist/cli.mjs",
"dev": "tsx ./src/cli.ts",
"build": "tsc --noEmit && cmake-js --build && pkgroll --build",
/** 'PRIVATE' COMMANDS INTENDED FOR DEVELOPERS OF THE TEST MODULE */
// ECMAscript commands
"pkg:build": "pkgroll --build",
"pkg:minify": "pkgroll --minify",
"pkg:sourcemap": "pkgroll --sourcemap",
"pkg:watch": "pkgroll --watch",
"pkg:clean": "pkgroll --clean-dist",
// Native Addon commands
"cmake:install": "cmake-js install",
"cmake:postinstall": "cmake-js compile",
"cmake:configure": "cmake-js configure",
"cmake:reconfigure": "cmake-js reconfigure",
"cmake:build": "cmake-js build",
"cmake:rebuild": "cmake-js rebuild",
"cmake:clean": "cmake-js clean",
"cmake:wipe": "cmake-js clean && rm -rvf ./node_modules",
// maintenance
"tsc:check": "tsc --noEmit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --check ./**/*.{js,jsx,ts,tsx,css,md,json} --config ./prettier.config.mjs",
"format:fix": "prettier --write ./**/*.{js,jsx,ts,tsx,css,md,json} --config ./prettier.config.mjs"
// tests (tbc...)
},
The template should build and run successfully using the commands above (and/or the others in package.json
). The built project, and commands above which interact with it, represent what I intend for this project to facilitate for others to do with it, eventually.
If these early concepts pass the validation stage, then the template project will be moved into a sub-directory (probably /packages/template
), and the main codebase of esnative
shall take the form of the required tooling for scaffolding and developing new projects which resemble the template; think of a command like npx create-native-app@latest myApp --template typescript
...
This project uses my fork of cmake-js; I drafted an API to provide the CMake side of consuming projects with some wrapper functions, based on some of my general requirements. See my CMakeLists.txt
for an example of how the .node
binary creation is configured; more documentation is available in the forked repo.
Both Node-API
and cmake-js offer an unshakeable level of compatibility; try building the test project against a variety of NodeJS versions using nvm
, and note how cmake-js automatically fetches the matching set of headers and other developer files for that NodeJS version. Node-API
's well-documented ABI stability further guarantees that the resulting binary is widely stable across NodeJs versions, system platforms and architectures, and so forth.
The additional functionalities of tsx
and pkgroll
, both seperately, and together, are worth looking at closer, if you're hacking on this project. They provided the impetus that inspired the creation of the esnative
project. The combined workflows of tsx
and pkgroll
alongside cmake-js
in the creation of Node-API
modules feels smooth, fast, and lightweight; no particular library or framework is required, other than NodeJS and it's Node-API
and your native compiler toolchain (GCC/MSVC/xCode/LLVM, etc...).
I expect to experiment with some front-end libraries and frameworks in addition to the current template; if, and once, the two are integrating with absolute ease, this project will likely change form as proposed.
For the time being, it is a show of final intentions, and one I thought was interesting enough to make public.
Thanks for reading, let me know if any interest, curiousity, or otherwise.