0.3.1 • Published 4 years ago

@expreva/quickjs-web v0.3.1

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

quickjs-emscripten

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()

Usage

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.

Safely evaluate Javascript code

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)
})

Interfacing with the interpreter

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)

More Documentation

Background

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:

Status & TODOs

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.

Related

Developing

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 parts

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/wrapper
  • yarn run-n builds and runs ./c/test.c

The Typescript parts

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.