CMakeRC as a NodeJs C++ Addon.
Serve a binary-compiled embedded filesystem as a Javascript object in NodeJS.
import noderc from '@nathanjhood/noderc';
const fs = noderc.getFileSystemObject();
const favicon_ico = fs["favicon.ico"];
console.log(JSON.stringify(fs, null, "\n \t"));
To see how to serve binary-compiled resources as Javascript objects, follow these steps:
- clone the repo, and
cd
into it - run
npm run install
oryarn install
to install the dependencies and build the test project - the file
/test/index.js
calls some functions that are defined and exported in/src/noderc.cpp
- the actual files and directories to be compiled are specified in the
CMakeLists.txt
file:
list (APPEND RESOURCES
# Resources to compile (add more as you please)...
favicon.ico
tst.txt
)
- the resources added to the above list shall be available in Javascript with
noderc.open("filename")
. - see the test file for further functionality, such as testing if a given filename or directory exists (within the compiled library), or whether a file on disk matches the compiled resource (an excellent safety feature).
- run the tests with
npm run start
oryarn start
To build your own custom resource library and use it in your own NodeJs projects:
- fork this repo
- customize the
RESOURCES
to be compiled inCMakeLists.txt
- they also need to be under version control - push your changes to your fork
- add the URL of your fork to the
package.json
dependencies of the NodeJs project which will consume your library
"dependencies": {
"@<GithubUserName>/noderc": "https://github.com/<GithubUserName>/noderc",
// etc...
}
- run
npm run install
oryarn install
in your NodeJs project to acquire and build your library - your library will be available in your NodeJs project by requiring/importing:
const noderc = require("@<GithubUserName>/noderc")
// or
import noderc from '@<GithubUserName>/noderc'
// then
const my_compiled_resource = noderc.open("somefile.ext")
- NodeJs
- CMake
- A C/C++ compiler toolchain (GCC, LLVM, MSVC...)
- A build generator (Ninja, Make, MSVC...)
The remaining dependencies either ship with the repo (such as the 'CMakeRC.cmake' file), or are acquired when running npm install
or yarn install
to initialize the project.
When building this project from the npm/yarn command, the package cmake-js
is invoked, which then reads from the same build script - CMakeLists.txt
- as the CMake CLI does in a typical build scenario. However, cmake-js
also passes an argument to the compiler equivalent to passing -DCMAKE_CXX_FLAGS:STRING=-DBUILDING_NODE_EXTENSION
on the command line. You may also pass the above arg manually when building with CMake directly on the command line, as long as you have already done npm install
or yarn install
to acquire these headers; they should be found in the usual node_modules
folder at the project root.
You will also need to install the cmake-js
package globally in order to acquire necessary NodeJS development files; just run npm -g install cmake-js
or yarn global add cmake-js
before building. Note that the entire content of 'noderc.cpp' is guarded by requiring napi.h
to be found and BUILDING_NODE_EXTENSION
to be defined. See the next section if you can't find napi.h
.
When building the NodeJS addon, your IDE might not find the <napi.h>
file, even when building successfully. The file(s) you need should be in the node_modules
directory, under node-api-headers/include
(C headers for NodeJs) and node-addon-api
(C++ headers which wrap the C headers)*. The build script(s) will pick them up automatically, but your IDE might not. You just need to add these two directories appropriately to your IDE's intellisense engine path.
VSCode with C++ extension example:
// .vscode/c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"node_modules/node-addon-api",
"node_modules/node-api-headers/include"
],
"defines": [],
"compilerPath": "/usr/bin/g++",
"cStandard": "c17",
"cppStandard": "c++14",
"intelliSenseMode": "linux-gcc-x64",
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4
}
*Important distinction: <napi.h>
is the C++ addon header, and is ABI-stable.
If you are using nvm (node version manager), or have different Node installations on your system, Node addons written using the Node API
C headers will complain that the Node version used during build is different to the one attempting to run the built module. The Node Addon API
C++ header provides an ABI stability promise, which circumvents this issue.
When choosing to build an addon using the Node Node API
C headers directly, you must build against the same Node version that you intend to run on.
With this in mind, noderc
is written using Node Addon API
in C++; you should not experience any issues with differing Node versions and nvm when building this project, thanks to the Node Addon API
's' ABI stability promise.
The build
step generates a dynamic library in the output directory. The contents of the dynamic library work like an 'embedded' file system - the files listed in RESOURCES
are compiled in to this embedded filesystem, making their contents available as a file
and their path prefixes available as a directory
tree.
A second library is generated by the build
step, which contains an interface that connects the embedded filesystem library to the NodeJs Addon interface, all written in C++.
The functionality exposed by this interface just provides and exports Javascript-side wrappers around the CMakeRC functions.
All of the currently-implemented functionality available on the Javascript is described below.
(method) noderc.open(path: string): string
Opens and returns a non-directory file
object at path
, or throws std::system_error()
(as a Javascript exception) on error. The file
object, if it exists, is returned as a Javascript string
(for now).
Example:
const favicon = noderc.open("favicon.ico")
The file
objects returned by this method are the ones added to the RESOURCES
list in CMakeLists.txt
. It should be possible to add basically any file to this list and make it available in Javascript using noderc.open("filename.ext")
.
When adding to RESOURCES
, the items you want to compile should be specified relative to the project root folder, ideally. CMakeRC uses the full filename specified in this list, including any path prefixes, when making the compiled resource available via noderc.open()
.
This means that you could add the following entries to the list to be compiled (in CMakeLists.txt
):
list (APPEND RESOURCES
# Resources to compile (add/remove as you please)...
favicon.ico
tst.txt
test/views/layout.pug # note that the path prefix was used here...
)
Now, the added layout.pug
file should be available via noderc.open()
, but you must specifiy the entire path prefix in order to find it:
const tst_txt = noderc.open("tst.txt");
const favicon_ico = noderc.open("favicon.ico");
const layout_pug = noderc.open("test/views/layout.pug"); // path prefix required
(method) noderc.isFile(path: string): boolean
Returns true
if the given path
names a regular file, false
otherwise.
Example:
const thisWillBeFalse = noderc.isFile("test/views")
const thisWillBeTrue = noderc.isFile("test/views/layout.pug")
(method) noderc.isDirectory(path: string): boolean
Returns true
if the given path
names a directory, false
otherwise.
Example:
const thisWillBeFalse = noderc.isDirectory("test/views/layout.pug")
const thisWillBeTrue = noderc.isDirectory("test/views")
(method) noderc.exists(path: string): boolean
Returns true
if the given path
names an existing file or directory, false
otherwise.
Example:
// missing path prefix....
const thisWillBeFalse = noderc.exists("layout.pug")
// is a file
const thisWillBeTrueA = noderc.exists("test/views/layout.pug")
// is a directory
const thisWillBeTrueB = noderc.exists("test/views")
(method) noderc.compare(file: string, path: string): boolean
Compare a file
on disk to a compiled resource at path
; returns false
if the file
is not the same size (in bytes) as the compiled resource at path
, or if the content of file
(in bytes) does not match the content of the compiled resource at path
. Otherwise, returns true. Can also print to STDOUT
.
Example:
Say we have a file located on disk at /home/myconfig.cfg
, and that we compile it with noderc
, so that the file is accessible at noderc.open("myconfig.cfg")
.
test = true, if the file (param A) matches the resource (param B)
const test = noderc.compare("/home/myconfig.cfg", "myconfig.cfg")
test = false, if the file (param A) does not match the resource (param B)
const test = noderc.compare("/backup/my_backup_config.cfg", "myconfig.cfg")
(method) noderc.compareSize(file: string, path: string): boolean
Compare a file
on disk to a compiled resource at path
; returns false
if the file
is not the same size (in bytes) as the compiled resource at path
. Otherwise, returns true. Can also print to STDOUT
.
Example:
const trueIfSameSize = noderc.compareSize("/home/myconfig.cfg", "myconfig.cfg")
(method) noderc.compareContent(file: string, path: string): boolean
Compare a file
on disk to a compiled resource at path
; returns false
if the file
does not match (in byte-to-byte comparison) the compiled resource at path
. Otherwise, returns true. Can also print to STDOUT
.
Example:
const trueIfSameBytes = noderc.compareContent("/home/myconfig.cfg", "myconfig.cfg")
(method) noderc.getFileSystemObject(): object
Returns the entire cmrc::embedded_filesystem()
as a Javascript object
.
Javascript object
semantics can then be used to access the entries in the embedded filesystem in various ways.
Examples:
// Cast the entire embedded filesystem object to JSON for inspection
const fs_to_json = JSON.stringify(noderc.getFileSystemObject(), null, "\n \t")
console.log(fs_to_json)
// Cast a single embedded filesystem entry to a Javascript object
const fs = noderc.getFileSystemObject();
const tst_txt = fs["tst.txt"];
- CMakeRC - Copyright 2018 vector-of-bool
- CMakeRC Blog article
- Node-API Resource
- Node Addon API documentation
Please note that the version of CMakeRC in the project root has been slightly enhanced to provide intellisense, and to ensure a consistent C++ exceptions policy is used throughout the codebase. These changes may be reverted in a future update which intends to incorporate throwing CMakeRC's exceptions as a Napi::Exception
to the Javascript side.