Basic introduction to native C modules for QuickJS
I know this engine was created with embedded systems in mind, is really tiny and lightweight, and taking advantage of fact that the code base is still small, I’m trying to understand how it works and mainly, how to extend it.
And that’s what I’m trying to explain here, not form an expert point of view, but a rookie and someone with basic knowledge about C++.
QuickJS is not a kind of NodeJS, it’s more like V8
I’ve seen a lot of confusion about if QuickJS is just a NodeJS replacement, it is not, actually, you can’t port code directly from NodeJS to QuickJS because NodeJS has it’s own APIs (fs, path, process, net, etc) and QuickJS has a very small set of native functions to play with.
What we want at the end of this article is to make this code work in an independent executable after compiling with QuickJS compiler:
Prepare the source
You can read the compiling instructions from https://bellard.org/quickjs/quickjs.html#Installation
After compiling all the source code with
make, try to compile and run the code I described above with:
./qjsc -m -o my_module my_module.js# run
You will see this error:
ReferenceError: could not load module filename 'my_module'
Add the module name to the compiler
Edit the compiler source code
qjsc.c in the line 455 where the system modules are added to be recognized later:
/* add system modules */
namelist_add(&cmodule_list, "std", "std", 0);
namelist_add(&cmodule_list, "os", "os", 0);// our module
namelist_add(&cmodule_list, "my_module", "my_module", 0);
Build the compiler again and test:
# This will build the compiler only, it's faster
make qjsc# Test again
./qjsc -m -o my_module my_module.js
Then, an error like this will be shown:
/tmp/ccenbi7V.o: In function `main’:
out19678.c:(.text.startup+0x84): undefined reference to `js_init_module_my_module’
collect2: error: ld returned 1 exit status
The compiler cannot be built because we’re telling it we have another system module called
my_module, but the implementation is not found, since we want a static linking the compiler looks for a function called
js_init_module_my_module, this name is created dynamically in compilation time, so, out binary is not created.
Native module template
Add the following C code, created based on the examples found in the QuickJS source code:
As you can see
*js_init_module_my_module is the entry point to the module, also there’s an initialization function
js_my_module_init, a function’s list
js_my_module_funcs and the function itself
plusNumbers, the only mandatory function’s name is the entry point, because is dynamically generated based on the module name described in
qjsc.c , it has to have this format:
To add more functions to our module, just extend
JS_CFUNC_DEF , it waits 3 parameters, the function’s name, the number of parameters and the function definition.
plusNumbers has to return a
quickjs.h , there you’ll find all the others structures available, in this case we’re returning
JS_NewInt32 structure for representing numbers.
For reading the parameters we’re using the following lines:
JS_ToInt32(ctx, &a, argv)
Here we’re converting a JS Number to C number by passing the variable as a reference and reading the coming parameter from
Adding the C code to Makefile
Since we’re trying to extend the QuickJS compiler, we must add it as a dependency in the Makefile:
Add a new object a the end of
QJS_LIB_OBJS= ... $(OBJDIR)/my_module.o
So next time we build the compiler, it will make sure that target
./my_module.o is present, if not, it will be compiled, there’s already a target for compiling all C code in the source directory.
So, the final tests will be:
# build the compiler
make qjsc# compile our example code
./qjsc -m -o my_module my_module.js# run the program
./my_moduleoutput => Result 5
After this, we have an independent executable with 3090416 bytes (3~ MB) of size, it’s not huge, but it can be optimized to be smaller and faster with
./qjsc -flto -m -o my_module my_module.js
Now we have a binary with 652~KB with a program coded by using ES2019!
Happy coding! :)