QuickJS is a tiny JavaScript engine written in C. Its author, Fabrice Bellard, created FFMPEG and QEMU. QuickJS can run outside of traditional browser environments. This includes packaging JavaScript applications for distribution.
As of writing, QuickJS supports ECMAScript 2023, so you can write modern JavaScript. It is also fast and well-tested.
One of the engine’s features is compiling JavaScript code into bytecode. Bytecode is a compact set of instructions that an interpreter can execute. It makes execution more efficient, which is perfect where performance is important. Additionally, the engine supports compilation of JavaScript into dependency-free standalone executables. We will use this feature to package JavaScript apps.
QuickJS’s small footprint is also suitable for embedded systems and resource-constrained environments.
Recently, Amazon announced its Low Latency Runtime (LLRT), built on QuickJS. According to Amazon, LLRT starts 10x faster and is 2x less expensive than other JS runtimes on AWS Lambda. That’s a pretty impressive use case.
Installation
Installing QuickJS in your development environment is straightforward. Here’s how to get started. Note that on Windows, you can use WSL with Linux.
Before jumping in, make sure you have Make and a C compiler like GCC or Clang.
The first step is to get a copy of the source code. You can download the source from the QuickJS website or by cloning it from the GitHub repository. We’ll download it from the website and untar the file:
wget https://bellard.org/quickjs/quickjs-2024-01-13.tar.xz
tar -xJf quickjs-2024-01-13.tar.xz
This command creates the directory quickjs-2024-01-13
with all the necessary
source files. I’ll refer to this directory as quickjs
from now on.
Compatibility
QuickJS does not rely on V8, WebKit, or Gecko. It is not compatible with NodeJS or Deno APIs. It does have access to the OS through its own APIs. To make sure your code is compatible with QuickJS, ensure the following:
- Your code uses ECMAScript 2023 or lower
- You are not using browser-specific APIs
- You are not using Node.js or Deno-specific APIs
QuickJS focuses on the core JavaScript language, so your code should be platform-agnostic.
Organization
For this simple Hello World app we’re putting our files into the quickjs
directory. In a business context, you should keep your code separate from
QuickJS. Otherwise, you can structure your project as you would any other
JavaScript project. You can even use modules.
Building QuickJS
Once you have the source code, the next step is to compile it. This will convert
the code into an executable you can run on your computer. Navigate to the
quickjs
directory with cd quickjs
.
Finally, compile the source with the make
command. QuickJS’s Makefile will
detect your OS and choose the appropriate compiler and flags. Run:
make
On macOS, Make will use Clang as the default compiler, whereas on Linux, GCC is
more common. Compilation will take a few moments. Once completed, several new
files will be added to the quickjs
directory. qjs
is the command-line tool
for executing JavaScript files. qjsc
is a tool for compiling JavaScript into
bytecode.
Testing QuickJS
You can run a simple JavaScript file to verify your installation. Create a file
named hello.js
with the following code:
console.log("Hello, QuickJS!");
Save the file into the quickjs
directory and then execute it using the qjs
binary:
./qjs hello.js
If QuickJS installed, you will see the message “Hello, QuickJS!” Now you have QuickJS set up and ready, it’s time to package an application.
Packaging JavaScript
To create a standalone executable, use the qjsc
executable in the quickjs
directory. You can execute it with these commands:
qjsc -o hello hello.js
./hello
If it works, you will see “Hello, QuickJS!” on your terminal. This is the file that we will distribute.
There are many flags that you can pass to qjsc.
Try experimenting
with each. For example, output bytecode instead of an executable. Or disable
regular expressions to decrease binary size.
Package Size
The executable for this “Hello, World!” example is 4.6 MB. It may seem large for
such a simple program. However, consider the alternatives. Using deno compile
I get an executable that is 76 MB. Compiling the hello.js file using Node 21
produces a 98 MB file. So, In perspective 4.6 MB seems pretty good.
Distribution
You don’t need anything special to distribute a QuickJS-packaged application. Using the standalone executables, you have a range of distribution options.
QuickJS is portable. That means that it can run in many environments. When preparing for distribution, consider the target platforms. If you want your application to work on Windows, macOS, and Linux, you must build the app on each system.
Closing Thoughts
Whether you’re developing IoT devices or building server-side tools QuickJS is a stellar option. It’s easy to use, fast, and produces relatively tiny executables.
Try experimenting with it, push its boundaries, and see how it can be used in your projects. What has your experience with QuickJS been? I’d love to hear your stories, successes, and lessons learned.