Javascript/Typescript bindings for QuickJS, a modern Javascript interpreter written in C by Fabrice Bellard.
- Safely evaluate untrusted Javascript (up to ES2020).
- Create and manipulate values inside the QuickJS runtime.
- Expose host functions to the QuickJS runtime.
import { getQuickJS } from 'quickjs-emscripten'
async function main() {
const QuickJS = await getQuickJS()
const vm = QuickJS.createVm()
const world = vm.createString('world')
vm.setProp(vm.global, 'NAME', world)
world.dispose()
const result = vm.evalCode(`"Hello " + NAME + "!"`)
if (result.error) {
console.log('Execution failed:', vm.dump(result.error))
result.error.dispose()
} else {
console.log('Success:', vm.dump(result.value))
result.value.dispose()
}
vm.dispose()
}
main()
Install from npm
: npm install --save quickjs-emscripten
or yarn add quickjs-emscripten
.
The root entrypoint of this library is the getQuickJS
function, which returns
a promise that resolves to a QuickJS singleton when
the Emscripten WASM module is ready.
See QuickJS.evalCode
import { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten'
getQuickJS.then(QuickJS => {
const result = QuickJS.evalCode('1 + 1', {
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000)
})
console.log(result)
})
You can use QuickJSVm to build a scripting environment by modifying globals and exposing functions into the QuickJS interpreter.
const vm = QuickJS.createVm()
let state = 0
const fnHandle = vm.newFunction('nextId', () => {
return vm.newNumber(++state)
})
vm.setProp(vm.global, 'nextId', fnHandle)
fnHandle.dispose()
const nextId = vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`))
console.log('vm result:', vm.getNumber(nextId), 'native state:', state)
This was inspired by seeing https://github.com/maple3142/duktape-eval on Hacker News and Figma's blogposts about using building a Javascript plugin runtime:
- How Figma built the Figma plugin system: Describes the LowLevelJavascriptVm interface.
- An update on plugin security: Figma switches to QuickJS.
Both the original project quickjs and this project are still in the early stage of development. There are tests, but I haven't built anything on top of this. Please use this project carefully in a production environment.
Ideas for future work:
- Simplify memory management. Currently the user must call
handle.dispose()
on all handles they create to avoid leaking memory in C.- We chould use a Pool abstraction and do a Pool.freeAll() to free all handles and pointers in the pool.
- Pools, etc, should not pollute QuickJSVm interface. Composition!
- quickjs-emscripten only exposes a small subset of the QuickJS APIs. Add more QuickJS bindings!
- Expose tools for object and array iteration and creation.
- Stretch goals: class support, an event emitter bridge implementation
- Higher-level abstractions for translating values into (and out of) QuickJS.
These should be implemented in a way that works for any
LowLevelJavascriptVm
implementation. - Removing the singleton limitations. Each QuickJS class instance could create its own copy of the emscripten module, although we'd need to make all public methods async - so they wait for the module instance to be ready.
- Run quickjs-emscripten inside quickjs-emscripten.
- Duktape wrapped in Wasm: https://github.com/maple3142/duktape-eval/blob/master/src/Makefile
- QuickJS wrapped in C++: https://github.com/ftk/quickjspp
This library is implemented in two languages: C (compiled to WASM with Emscripten), and Typescript. Emscripten outputs are checked in, so you will only need the C compiler if you need to modify C code.
The ./c directory contains C code that wraps the QuickJS C library (in ./quickjs).
Public functions (those starting with QTS_
) in ./c/interface.c are
automatically exported to native code (via a generated header) and to
Typescript (via a generated FFI class). See ./generate.ts for how this works.
The C code builds as both with emscripten
(using emcc
), to produce WASM (or
ASM.js) and with clang
. Build outputs are checked in, so
Intermediate object files from QuickJS end up in ./build/quickjs/{wasm,native}.
You'll need to install emscripten
. Following the offical instructions here, using emsdk
:
https://emscripten.org/docs/getting_started/downloads.html#installation-instructions
Related NPM scripts:
yarn update-quickjs
will sync the ./quickjs folder with a github repo tracking the upstream QuickJS.yarn make-debug
will rebuild C outputs into ./build/wrapperyarn run-n
builds and runs ./c/test.c
The ./ts directory contains Typescript types and wraps the generated Emscripten FFI in a more usable interface.
You'll need node
and npm
or yarn
. Install dependencies with npm install
or yarn install
.
yarn build
produces ./dist.yarn test
runs the tests.yarn test --watch
watches for changes and re-runs the tests.