Skip to content

Instantly share code, notes, and snippets.

@xeioex
Created October 28, 2025 23:52
Show Gist options
  • Save xeioex/8a15755443c4f7ba478b1bc87c367c85 to your computer and use it in GitHub Desktop.
Save xeioex/8a15755443c4f7ba478b1bc87c367c85 to your computer and use it in GitHub Desktop.

NGINX JavaScript (njs) Development Guide

Project Overview

NGINX JavaScript (njs) is a JavaScript engine for NGINX that implements a subset of ECMAScript 5.1 (strict mode) with extensions from ES6 and later. It can be built as:

  • Standalone CLI - for testing JavaScript code independently
  • NGINX module - integrated into NGINX for HTTP/Stream processing
  • With QuickJS - alternative JavaScript engine backend

See README.md for detailed project information.


Building njs

Prerequisites

See README.md for complete dependency list. Common requirements:

sudo apt install gcc make libedit-dev libpcre3-dev zlib1g-dev libssl-dev

Build njs CLI Only

For standalone njs binary (no NGINX integration):

./configure
make njs

The binary will be at ./build/njs.

Build njs with QuickJS Engine

First, build QuickJS library:

cd <QUICKJS_SRC_DIR>
CFLAGS='-fPIC' make libquickjs.a

Then build njs with QuickJS support:

cd <NJS_SRC_DIR>
make clean
./configure \
    --cc-opt='-I<QUICKJS_SRC_DIR>' \
    --ld-opt='-L<QUICKJS_SRC_DIR>'
make njs

Build njs for NGINX Integration

Configure and build NGINX with njs as a static module.

Basic build:

cd <NGINX_SRC_DIR>
./auto/configure \
    --add-module=<NJS_SRC_DIR>/nginx \
    --with-stream \
    --with-debug
make -j4

Build with QuickJS engine:

cd <NGINX_SRC_DIR>
./auto/configure \
    --add-module=<NJS_SRC_DIR>/nginx \
    --with-stream \
    --with-debug \
    --with-cc-opt="-I<QUICKJS_SRC_DIR>" \
    --with-ld-opt="-L<QUICKJS_SRC_DIR>"
make -j4

Build with address sanitizer:

cd <NGINX_SRC_DIR>
./auto/configure \
    --add-module=<NJS_SRC_DIR>/nginx \
    --with-stream \
    --with-debug \
    --with-cc="clang" \
    --with-cc-opt="-O0 -fsanitize=address" \
    --with-ld-opt="-fsanitize=address"
make -j4

Common configure options:

  • --add-module=<path> - Build njs as static module
  • --with-stream - Enable stream module (required for ngx_stream_js_module)
  • --with-debug - Enable debug logging
  • --with-cc="<compiler>" - Specify compiler (gcc, clang, etc.)
  • --with-cc-opt="<flags>" - Additional C compiler flags
  • --with-ld-opt="<flags>" - Additional linker flags

Configure Options

./configure --help              # Show all options
./configure --debug=YES         # Enable runtime checks
./configure --debug-memory=YES  # Enable memory allocation debug
./configure --debug-opcode=YES  # Enable function tracing (see below)
./configure --debug-generator=YES  # Enable generator debug
./configure --debug=YES --debug-memory=YES --address-sanitizer=YES # Full debug build for memory issues

Testing

njs Unit Tests

make unit_test    # Run njs-specific tests
make test262      # Run ECMAScript test262 compliance tests

NGINX Integration Tests

Tests are located in <NGINX_TESTS_DIR> and use Perl's prove harness.

Basic test run:

cd <NJS_SRC_DIR>
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
    prove -I <NGINX_TESTS_LIB_DIR> nginx/t/

Run specific test with verbose output:

TEST_NGINX_VERBOSE=1 TEST_NGINX_LEAVE=1 \
    TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
    prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js.t -v

Common test environment variables:

  • TEST_NGINX_BINARY=<path> - Path to nginx binary
  • TEST_NGINX_VERBOSE=1 - Enable verbose test output
  • TEST_NGINX_LEAVE=1 - Keep test artifacts in /tmp/nginx-test-*
  • TEST_NGINX_CATLOG=1 - Print error.log after tests complete
  • TEST_NGINX_GLOBALS=<config> - Add global nginx config
  • TEST_NGINX_GLOBALS_HTTP=<config> - Add http-level config (e.g., js_engine qjs;)
  • TEST_NGINX_GLOBALS_STREAM=<config> - Add stream-level config

For complete variable documentation, see <NGINX_TESTS_LIB_DIR>/Test/Nginx.pm.

Test with QuickJS engine:

TEST_NGINX_GLOBALS_HTTP='js_engine qjs;' \
    TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
    prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js_headers.t -v

Debugging

Debug Test Failures

  1. Check test output - Add TEST_NGINX_VERBOSE=1 and -v flag to prove
  2. Check error logs - Look at /tmp/nginx-test-<random>/error.log
    • Important: Remove old test directories first to see latest logs
    • Use TEST_NGINX_LEAVE=1 to prevent cleanup
    • Use TEST_NGINX_CATLOG=1 to print logs automatically

Using njs CLI for Debugging

./build/njs -c "console.log('test')"  # Execute code
./build/njs -d                        # Interactive shell with disassembly
./build/njs -d script.js              # Show bytecode for script
./build/njs -h                        # Show all options

Debug with GDB

To debug specific JavaScript code paths:

  1. Add debugger statement in your JS code:

    function test() {
        if (someCondition) {
            debugger;  // Trigger breakpoint
        }
    }
  2. Set GDB breakpoint:

    gdb ./build/njs
    (gdb) break njs_vmcode_debugger
    (gdb) run test.js
  3. Inspect VM state in GDB:

    # First, dump bytecode to find the index you want to inspect
    ./build/njs -d test.js
    
    # In GDB, inspect value at specific index
    (gdb) p *njs_scope_value_get(vm, 0x0123)  # Use hex literal from bytecode

Understanding Opcode Tracing

With --debug-opcode=YES, njs prints execution trace:

./build/njs -o script.js

# Example output:
ENTER main
    0 | 00000 FUNCTION COPY     5250000212D8 0124
    0 | 00000 FUNCTION FRAME    0124 0
    0 | 00000 FUNCTION CALL     0223
CALL NATIVE unmapped ??() ??:0 [0xAE5EDD13E7A0]
ENTER f
    0 | 00000 AWAIT             0133
EXIT AWAIT f

njs VM Architecture

Register-Based VM

njs uses a register-based virtual machine:

  • Each instruction has operands (immediate values or indexes)
  • Indexes encode: index | level_type (4-bit) | var_type (4-bit)

Level Types (Storage Location)

NJS_LEVEL_LOCAL = 0    // Local variable in current function
NJS_LEVEL_CLOSURE = 1  // Closure variable from parent function
NJS_LEVEL_GLOBAL = 2   // Global variable
NJS_LEVEL_STATIC = 3   // Static/absolute scope variable

Variable Types

NJS_VARIABLE_CONST = 0
NJS_VARIABLE_LET = 1
NJS_VARIABLE_CATCH = 2
NJS_VARIABLE_VAR = 3
NJS_VARIABLE_FUNCTION = 4

Accessing Values

Variable values are stored in: vm->levels[NJS_LEVEL_*][index]

Bytecode Example

./build/njs -d
>> var a = 42; function f(v) { return v + 1}

shell:main
    1 | 00000 MOVE              0123 0133
    1 | 00024 STOP              0033

shell:f
    1 | 00000 ADD               0203 0103 0233
    1 | 00032 RETURN            0203

Breakdown:

  • MOVE 0123 0133 - Move value from index 0x0133 to 0x0123
  • ADD 0203 0103 0233 - Add values at 0x0103 and 0x0233, store in 0x0203
  • Indexes are in hex, encode level and variable type

Common Workflows

Modify njs → Test in NGINX

# 1. Make changes to njs source
cd <NJS_SRC_DIR>
# ... edit files ...

# 2. Rebuild nginx (njs is built as static module)
cd <NGINX_SRC_DIR>
make -j4

# 3. Run tests
cd <NJS_SRC_DIR>
rm -fr /tmp/nginx-test*  # Clean old test artifacts
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
    prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js*.t

Test JavaScript Code Standalone

# Quick test
./build/njs -c "var a = [1,2,3]; console.log(a.map(x => x*2))"

# Interactive testing
./build/njs
>> var obj = {a: 1, b: 2}
>> Object.keys(obj)
['a','b']

Compare njs vs QuickJS Behavior

# Build nginx with njs engine
cd <NGINX_SRC_DIR>
./auto/configure \
    --add-module=<NJS_SRC_DIR>/nginx \
    --with-stream \
    --with-debug
make -j4

# Test with njs engine
cd <NJS_SRC_DIR>
TEST_NGINX_GLOBALS_HTTP='js_engine njs;' \
    TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
    prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js_headers.t -v

# Build nginx with QuickJS engine
cd <NGINX_SRC_DIR>
./auto/configure \
    --add-module=<NJS_SRC_DIR>/nginx \
    --with-stream \
    --with-debug \
    --with-cc-opt="-I<QUICKJS_SRC_DIR>" \
    --with-ld-opt="-L<QUICKJS_SRC_DIR>"
make -j4

# Test with QuickJS engine
cd <NJS_SRC_DIR>
TEST_NGINX_GLOBALS_HTTP='js_engine qjs;' \
    TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
    prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js_headers.t -v

Additional Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment