All Projects → justjake → Quickjs Emscripten

justjake / Quickjs Emscripten

Licence: other
Javascript/Typescript bindings for QuickJS, a modern Javascript interpreter written in C by Fabrice Bellard.

Programming Languages

javascript
184084 projects - #8 most used programming language
c
50402 projects - #5 most used programming language

Projects that are alternatives of or similar to Quickjs Emscripten

Iplug2
C++ Audio Plug-in Framework for desktop, mobile and web [PRE-RELEASE]
Stars: ✭ 875 (+605.65%)
Mutual labels:  plugins, wasm
Gbemu
WebAssembly based Gameboy Emulator
Stars: ✭ 120 (-3.23%)
Mutual labels:  wasm
Mattata
A powerful, plugin-based, multi-purpose Telegram bot designed to serve a wide variety of purposes
Stars: ✭ 107 (-13.71%)
Mutual labels:  plugins
Eqcss
EQCSS is a CSS Reprocessor that introduces Element Queries, Scoped CSS, a Parent selector, and responsive JavaScript to all browsers IE8 and up
Stars: ✭ 1,513 (+1120.16%)
Mutual labels:  eval
Deprecated rust wasm template
Deprecated in favor of rustwasm/wasm-pack-template or rustwasm/rust-webpack-template
Stars: ✭ 108 (-12.9%)
Mutual labels:  wasm
Blurhash Rust Wasm
A Rust and WASM implementation of the blurhash algorithm
Stars: ✭ 119 (-4.03%)
Mutual labels:  wasm
Wasmplay
WASM Web "Framework" Playground
Stars: ✭ 105 (-15.32%)
Mutual labels:  wasm
Rust Nes Emulator
NES Emulator written in Rust
Stars: ✭ 122 (-1.61%)
Mutual labels:  wasm
Pidgin Wechat
pidgin plugin for web wechat protocol
Stars: ✭ 119 (-4.03%)
Mutual labels:  plugins
Tinysearch
🔍 Tiny, full-text search engine for static websites built with Rust and Wasm
Stars: ✭ 1,705 (+1275%)
Mutual labels:  wasm
Csdwheels
一套基于原生JavaScript开发的插件,无依赖、体积小
Stars: ✭ 114 (-8.06%)
Mutual labels:  plugins
Kou
A minimal language compiled into wasm bytecode
Stars: ✭ 112 (-9.68%)
Mutual labels:  wasm
Aries Framework Go
Hyperledger Aries Framework Go provides packages for building Agent / DIDComm services.
Stars: ✭ 118 (-4.84%)
Mutual labels:  wasm
App Examples
Miro Apps Examples
Stars: ✭ 108 (-12.9%)
Mutual labels:  plugins
Blazorcrud
Demo application built with the Blazor client-side hosting model (WebAssembly) and .NET Core REST APIs secured by a JWT service.
Stars: ✭ 121 (-2.42%)
Mutual labels:  wasm
Update
Update is a new developer framework and CLI for automating updates of any kind in code projects. If you know how to use assemble, generate or verb, you'll know how to use update.
Stars: ✭ 106 (-14.52%)
Mutual labels:  plugins
Wasm Jseval
A safe eval library based on WebAssembly and Duktape/QuickJS.
Stars: ✭ 111 (-10.48%)
Mutual labels:  eval
Asteval
minimalistic evaluator of python expression using ast module
Stars: ✭ 116 (-6.45%)
Mutual labels:  eval
Wasm Snip
`wasm-snip` replaces a WebAssembly function's body with an `unreachable`
Stars: ✭ 123 (-0.81%)
Mutual labels:  wasm
Go Wasm
WebAssembly binary file parser written in go
Stars: ✭ 121 (-2.42%)
Mutual labels:  wasm

quickjs-emscripten

Javascript/Typescript bindings for QuickJS, a modern Javascript interpreter written in C by Fabrice Bellard compiled to WebAssembly.

  • 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.newString('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.

Once getQuickJS has been awaited at least once, you also can use the getQuickJSSync function to directly access the singleton engine in your synchronous code.

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),
    memoryLimitBytes: 1024 * 1024,
  })
  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.

Each QuickJSVm instance has its own environment, CPU limit, and memory limit. See the documentation for details.

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)

nextId.dispose()
vm.dispose()

Memory Management

Many methods in this library return handles to memory allocated inside the WebAssembly heap. These types cannot be garbage-collected as usual in Javascript. Instead, you must manually manage their memory by calling a .dispose() method to free the underlying resources. Once a handle has been disposed, it cannot be used anymore. Note that in the example above, we call .dispose() on each handle once it is no longer needed.

Calling QuickJSVm.dispose() will throw a RuntimeError if you've forgotten to dispose any handles associated with that VM, so it's good practice to create a new VM instance for each of your tests, and to call vm.dispose() at the end of every test.

const vm = QuickJS.createVm()
const numberHandle = vm.newNumber(42)
// Note: numberHandle not disposed, so it leaks memory.
vm.dispose()
// throws RuntimeError: abort(Assertion failed: list_empty(&rt->gc_obj_list), at: quickjs/quickjs.c,1963,JS_FreeRuntime)

Here are some strategies to reduce the toil of calling .dispose() on each handle you create:

Scope

A Scope instance manages a set of disposables and calls their .dispose() method in the reverse order in which they're added to the scope. Here's the "Interfacing with the interpreter" example re-written using Scope:

Scope.withScope(scope => {
  const vm = scope.manage(QuickJS.createVm())
  let state = 0

  const fnHandle = scope.manage(
    vm.newFunction('nextId', () => {
      return vm.newNumber(++state)
    })
  )

  vm.setProp(vm.global, 'nextId', fnHandle)

  const nextId = scope.manage(vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)))
  console.log('vm result:', vm.getNumber(nextId), 'native state:', state)

  // When the withScope block exits, it calls scope.dispose(), which in turn calls
  // the .dispose() methods of all the disposables managed by the scope.
})

You can also create Scope instances with new Scope() if you want to manage calling scope.dispose() yourself.

Lifetime.consume(fn)

Lifetime.consume is sugar for the common pattern of using a handle and then immediately disposing of it. Lifetime.consume takes a map function that produces a result of any type. The map fuction is called with the handle, then the handle is disposed, then the result is returned.

Here's the "Interfacing with interpreter" example re-written using .consume():

const vm = QuickJS.createVm()
let state = 0

vm.newFunction('nextId', () => {
  return vm.newNumber(++state)
}).consume(fnHandle => vm.setProp(vm.global, 'nextId', fnHandle))

vm.unwrapResult(vm.evalCode(`nextId(); nextId(); nextId()`)).consume(nextId =>
  console.log('vm result:', vm.getNumber(nextId), 'native state:', state)
)

vm.dispose()

Generally working with Scope leads to more straight-forward code, but Lifetime.consume can be handy sugar as part of a method call chain.

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.

Because the version number of this project is below 1.0.0, expect occasional breaking API changes.

Ideas for future work:

  • 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.
  • Remove the singleton limitations. Each QuickJS class instance could create its own copy of the emscripten module.
  • Run quickjs-emscripten inside quickjs-emscripten.
  • Remove the LowLevelJavascriptVm interface and definition. Those types provide no value, since there is no other implementations, and complicate the types and documentation for quickjs-emscripten.
  • Improve our testing strategy by running the tests with each of the Emscripten santizers, as well as with the SAFE_HEAP. This should catch more bugs in the C code. See the Emscripten docs for more details

Related

Developing

This library is implemented in two languages: C (compiled to WASM with Emscripten), and Typescript.

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

This project uses emscripten 1.39.19. The install should be handled automatically if you're working from Linux or OSX (if using Windows, the best is to use WSL to work on this repository). If everything is right, running yarn embin emcc -v should print something like this:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 1.39.18
clang version 11.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 613c4a87ba9bb39d1927402f4dd4c1ef1f9a02f7)

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.

Yarn updates

Just run yarn set version from sources to upgrade the Yarn release.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].