Fork of the fantastic QuickJS engine by Fabrice Bellard, with many changes.
- APIs from 'std' and 'os' modules were changed to be more idiomatic-to-JS; notably, they throw Errors on failure instead of returning null or errno numbers.
- TypeScript-format type interface files (
.d.tsfiles) were added for everything. - Hooks have been added to the module loader that make its functionality more customizable.
- Some ES2022 features were added, and some non-standard ECMAScript proposals and extensions were added.
- The way the project is organized and built was changed dramatically.
- Several new JS bindings for C APIs have been added, such as
strftime,access,fsync,setvbuf,getuid... - Scripts were added that cross-compile binaries for several operating systems and architectures
- FreeBSD support added
- A TypeScript
.d.tsfile is provided for all QuickJS-specific APIs (operator overloading APIs, BigInt extensions, BigFloat, BigDecimal, etc). - Non-standard
Object.toPrimitiveadded (static method that invokes ToPrimitive on the given value, using the optionally-provided hint). - Non-standard
Object.isPrimitiveadded (static method that returns a boolean indicating whether the given value is a primitive). - Non-standard
Symbol.typeofValuehas been added which can be used to override the result of using thetypeofoperator on an object. However, you can only use it to return a different one of the builtin valuestypeofwould normally return:"object","boolean","number", etc. - Added support for Error constructor "cause" option (from ES2022).
- Added support for relative indexing method
.at()(from ES2022). String.cookedadded (no-op template tag, like the proposed String.cooked).- Added function
JS_EvalThis_Privileged, which allows C code to run eval in Contexts that have eval disabled. With this, you can disable eval in a context for security purposes, but can still execute trusted code within it. - Additional functions are exposed that allow importing modules synchronously or asynchronously:
JS_DynamicImportAsyncJS_DynamicImportSyncJS_DynamicImportSync2(additional arg for specifying basename that the import specifier is relative to)
- Additional error-related functions added:
JS_ThrowErrorJS_AddPropertyToException
- Additional utility functions added:
JS_FreezeObjectValue(performs Object.freeze)JS_IsPrimitive
- ModuleDefs now have an optional "user_data" property (pointer to void) which can be accessed during module initialization (via
JS_GetModuleUserDataandJS_SetModuleUserData) - Added
JS_SetContextOpaqueValueandJS_GetContextOpaqueValue, which let you associate a JSValue with a JSContext, which will be garbage-collected when that JSContext is garbage-collected.
stdandosbuiltin modules are now namespaced under "quickjs:". In other words, you have to import them as "quickjs:std" and "quickjs:os".- APIs in
stdandosno longer return errno anywhere; instead, Error objects are thrown.errnois available as a property on the thrown Error objects. - In places where APIs in
stdoroswould return null on failure, now an error will be thrown instead. - Error messages from
stdandosinclude information in the message such as the path to the file that couldn't be loaded. This info is also available as properties on the Error object. - A TypeScript
.d.tsfile is now provided for all APIs (globals as well as stuff fromstd/os). - A builtin global function
inspectis added, which pretty-prints any JS value as a string. os.accessfunction added (wrapper for libcaccess).FILE.prototype.syncmethod added (wrapper forfsync).FILE.prototype.setvbufmethod added (wrapper forsetvbuf).FILE.prototype.writeTomethod added (pipe data from one FILE to another, given a buffer size and limit).std.isFILEfunction added (returns whether the provided object is aFILE(viajs_std_file_class_id)).os.{WUNTRACED,WEXITSTATUS,WTERMSIG,WSTOPSIG,WIFEXITED,WIFSIGNALED,WIFSTOPPED,WIFCONTINUED}added, for working withos.waitpid.os.{S_IRWXU,S_IRUSR,S_IWUSR,S_IXUSR,S_IRWXG,S_IRGRP,S_IWGRP,S_IXGRP,S_IRWXO,S_IROTH,S_IWOTH,S_IXOTH}added, for working with file modes."b"mode flag is now allowed instd.fdopen.std.strftimeadded (wrapper for libcstrftime).- Added
std.getuid,std.geteuid,std.getgid,std.getegid, andstd.getpwuid(wrappers for the libc/posix functions of the same names). os.gethostnameadded (wrapper for POSIXgethostname).- Most module-loading-related code was moved into
quickjs-modulesys. setTimeoutandclearTimeoutare now available as globals (previously they were only available as exports).setIntervalandclearIntervalare added, available as globals.- Several C-side helper functions were moved out of quickjs-libc and into quickjs-utils.
- Most module-related code (setting import.meta, etc) was moved into quickjs-modulesys.
- Added
std.setExitCode,std.getExitCode, and madestd.exit's parameter optional. The value passed tostd.setExitCodewill be used when the process exits normally, or whenstd.exitis called without any arguments.std.setExitCode,std.getExitCode, andstd.exitthrow if called from a thread other than the main thread (ie. a Worker). - The manual garbage collection function
std.gc()was moved to"quickjs:engine".
- Blocking the main thread is allowed
-m(module mode) now affects eval strings passed to-e- Changes to the repl:
- Ctrl+W deletes words backwards from the cursor position
- The last error is accessible via the
_errorglobal - If a thrown error has additional properties added onto it, those are printed along with the thrown error
- The new
inspectglobal is used to print results
Appending some JS code to the end of this binary changes it into a binary that executes that JS code:
$ cp qjsbootstrap my-program
$ echo 'console.log("hello!")' >> my-program
$ ./my-program
hello!You can use this to create distributable binaries that run JS code without needing to use qjsc or a C compiler. Instructions here.
Note: On FreeBSD,
qjsbootstraprequires procfs. You can mount it withmount -t procfs proc /proc. I started some work to use libprocstat instead, but haven't completed it yet.
Barebones binary for running files, without any of the arg parsing logic from qjs. Good for testing some unusual cases, or writing programs with custom argv parsing logic.
A Module that exposes QuickJS's value <-> bytecode (de)serialization APIs to JavaScript code. Generated bytecode can be combined with qjsbootstrap-bytecode in the same way that source code strings can be combined with qjsbootstrap.
A Module that allows JS code to create new JS Contexts (Realms). You can create new Contexts and run code inside them. Contexts can have certain features disabled (like eval) for security purposes. You can share values between Contexts. Contexts are destroyed when they get garbage-collected.
A barebones Module that exports a JS class which can be used to represent an opaque pointer. C modules can use the js_new_pointer function provided by this module to pass opaque pointer handles to users without needing to make their own wrapper class for stuff. This is mostly just useful in order to have a codified convention for how FFI libraries and such should represent foreign pointers.
Text encoding/decoding functions. Exports functions for converting between utf-8 and ArrayBuffer.
Helper structs, functions, and macros that make it easier to work with QuickJS in C code.
- APIs for looping over every property in a JSValue
- Helper function for loading a file from disk into char a buffer
- Helper functions for printing JS errors to stderr
This module contains APIs related to engine internals like script execution, module loading, code eval, filename reflection, and garbage collection. Several parts of quickjs-libc were moved here so that quickjs-libc could be focused on "C standard library" bindings.
.jsextensions can now be omitted from import specifiers; they're optional.- If your import specifier points to a folder, it will attempt to load
index.jsfrom that folder. - Synchronous import function added (
importModule), which provides the same module record object you would get via dynamic (async) import. - JS api for using the engine's configured module name normalization function was added (
resolveModule). - Adds the global
require, a CommonJS-like synchronous module loading function.- The
requirefunction is not fully CommonJS-compliant; for instance,require.mainis not present.require.resolveis, though.
- The
- Adds
import.meta.require- It's the same as the global
require; it's just added to import.meta for compatibility with bundlers that outputimport.meta.require, likebun.
- It's the same as the global
- Adds
import.meta.resolve- Similar to the one in the browser, but it's actually just
require.resolveexposed viaimport.meta.
- Similar to the one in the browser, but it's actually just
- Module and eval helpers have been moved from "quickjs:std" to the new module "quickjs:engine".
- Makes the module loader's resolution and loading behavior configurable
- The module "quickjs:engine" exports an object called
ModuleDelegate. - You can specify additional implicit import specifier extensions by adding to the
ModuleDelegate.searchExtensionsarray. - You can transform any file prior to evaluating it as a module by adding a function to the
ModuleDelegate.compilersobject. Useful for compile-to-js languages like TypeScript, Coffeescript, etc. - You can override module name normalization (aka module resolution) by replacing the
ModuleDelegate.resolvefunction.- Note that you must handle
ModuleDelegate.searchExtensionsyourself in your replacement implementation.
- Note that you must handle
- You can override the method used to load modules by replacing the
ModuleDelegate.readfunction.- Note that you must handle
ModuleDelegate.compilersyourself in your replacement implementation.
- Note that you must handle
- The module "quickjs:engine" exports an object called
- Makes
import.meta.mainconfigurable- The module "quickjs:engine" exports two functions named
setMainModuleandisMainModule. - You can use
setMainModuleto makeimport.meta.maintrue within that module's code. Note, however, that it does not work retroactively; only modules loaded after thesetMainModulecall will be affected. To defer module load, useimport(),importModulefrom "quickjs:engine", orrequire. - You can use
isMainModuleto check if a given module would be the main module without loading it.
- The module "quickjs:engine" exports two functions named
- New
isModuleNamespacefunction lets users identify module namespace objects - New
defineBuiltinModulefunction lets users add their own builtin modules - When using
requireto load a module which contains an export named__cjsExports, the value of the__cjsExportsproperty will be returned fromrequireinstead of the usual module namespace object. This can be leveraged by users configuring the module loader to add some CommonJS <-> ESM interop. Note, however, that dynamic import and"quickjs:engine"'simportModulealways receive the usual module namespace object.
- Stuff is reorganized into separate folders under
src. - Ninja is used instead of make. Ninja build config is generated via
.ninja.jsfiles which get loaded into @suchipi/shinobi. - Line endings have been made consistent and trailing whitespace has been removed
- The tests are authored in a new format which leverages jest snapshot testing.
- Some parts of
quickjs-libcwere moved intoquickjs-modulesysandquickjs-engine. - The
eval_*functions that were duplicated in each of the programs (eval_buf,eval_file, andeval_binary) were deduplicated and moved intoquickjs-modulesys.
We now include support for more platforms, and cross-compilation scripts to build most of those platforms from any platform where Docker is available.
See the meta/ninja/envs folder to see all the supported platforms. The host folder represents the machine where compilation is taking place, and the target folder represents the target platform for the output binaries.
We create .a files containing all of quickjs as part of the build.
There are also probably some other miscellaneous changes I forgot to write down in the README.
The repo has stuff set up to compile quickjs binaries for:
- Linux (amd64 and aarch64)
- glibc
- musl
- statically-linked
- macOS/iOS (x86_64 and arm64)
- Windows (x86_64)
- FreeBSD (cross-compilation not supported; you need to compile from a FreeBSD host)
- Cosmopolitan Libc (cross-platform
*.combinaries). You need the cosmo toolchain installed for this one to work. - any arbitrary unix-like OS, if you set env vars for CC, CFLAGS, etc.
QuickJS itself has no external dependencies outside this repo except pthreads, and all of the code is C99. As such, it shouldn't be too difficult to get it compiling on other Unix-like OSes.
Linux, macOS, iOS, and Windows binaries can be compiled using Docker. Or, you can compile binaries for just your own unix system, without using Docker.
If you're not gonna use Docker, you'll need to install Ninja and Node.js in order to compile. I use Ninja 1.10.1 and Node.js 18.18.0, but it should work with most versions of both of those.
To compile binaries for Linux, macOS, iOS, and Windows (using Docker):
- Make sure you have docker installed.
- Clone the repo and cd to its folder
- Run
meta/docker/build-all.sh - Build artifacts will be placed in the
buildfolder, organized by OS.
Or, to compile binaries for just your own unix system:
-
Make sure you have both Ninja and Node.js installed. I use Ninja 1.10.1 and Node.js 18.18.0, but it should work with most versions of both of those.
-
Make sure you have bash 4 or higher.
Almost all modern Linux systems have bash 5, but macOS still has bash 3. To get a newer version of bash on macOS, install either Homebrew or MacPorts (your preference), then run
brew install bashorsudo port install bash, respectively. You may also need to ensure your shell's PATH environment variable is set correctly; runningwhich bashshould print the location of the newer bash binary, installed by Homebrew/MacPorts. -
Clone the repo and cd to its folder
-
Run
meta/build.sh -
Build artifacts will be placed in the
buildfolder. You're probably most interested in stuff in thebuild/binandbuild/libfolders.
If you are targeting an unsupported OS or would like to use a different compiler, set the environment variables HOST and TARGET both to "other", and then set the environment variables CC, AR, CFLAGS, and LDFLAGS as you see fit. There are some other variables you can set as well; see meta/ninja/envs/target/other.ninja.js and meta/ninja/envs/host/other.ninja.js for a list.