quickjs-emscripten

repository·main·Indexed 23 days ago

https://github.com/justjake/quickjs-emscripten

TypeScript bindings for a WebAssembly-compiled version of the QuickJS interpreter, providing high-performance, sandboxed JavaScript execution. It offers multiple variants based on the QuickJS or quickjs-ng libraries, with configurations for release or debug modes, synchronous or ASYNCIFY sync modes, and single-file or separate .wasm file structures. Supported environments include Node.js (CommonJS and ESM), Browsers, Web Workers, and Cloudflare Workers.

Tokens
107.3K
Snippets
93
Records
730
Agent score
81%

What's inside quickjs-emscripten

  1. Roadmap and future features

    main

    The project is currently pre-1.0.0 and expects occasional breaking API changes. Planned improvements include:

    • Module Loading: Creating modules via JavaScript and scanning source text for imports.
    • High-level Value Reading: Type guard functions (e.g., context.isArray(handle), context.isPromise(handle)) and iteration utilities (e.g., context.getIterable(handle), context.iterateObjectEntries(handle)).
    • High-level Value Creation: Improved environment setup and automatic translation (potentially via quickjs-emscripten-sync).
    • SQLite Integration.

    Security Note: While the project strives for security, it has not been audited. Use with care in production.

  2. Use QuickJSWASMModule to interface with QuickJS

    main

    The QuickJSWASMModule class provides a JavaScript interface to the QuickJS interpreter (which supports ES2020). It wraps a WebAssembly module containing the QuickJS library and helper C code. Because WebAssembly modules are isolated by the host runtime, using separate QuickJSWASMModule instances provides the highest level of isolation possible.

    To interact with the interpreter, you can use three main approaches:

    1. One-off evaluation: Use evalCode() for simple, quick tasks.
    2. Context-based control: Use newContext() to create a QuickJSContext (and an associated runtime) for more granular control.
    3. Runtime-based control: Use newRuntime() to create a QuickJSRuntime, allowing you to set CPU/memory limits and configure module loading for multiple contexts.
  3. Understand the @jitl/quickjs-ng-wasmfile-release-sync variant

    main

    This package is a specialized variant of the quickjs-emscripten library with the following characteristics:

    • Library: Uses quickjs-ng (v0.12.1), a fork of QuickJS that includes additional features.
    • Release Mode: Optimized for performance; intended for production use.
    • Sync Mode: Uses a synchronous build (no extra async magic).
    • WASM Delivery: Uses a separate .wasm file rather than embedding it in the JS bundle. This is better for caching and bundle size, but if you encounter loading issues, consider a singlefile variant.
    • Compatibility: Supports NodeJS (CommonJS and ESM), Browsers, and Cloudflare Workers (workerd).
  4. Core capabilities of quickjs-emscripten

    main

    The quickjs-emscripten library provides TypeScript bindings for QuickJS (a modern JavaScript interpreter) compiled to WebAssembly. It is designed for:

    • Sandboxing: Safely evaluating untrusted JavaScript (supports most of ES2023).
    • Runtime Interfacing: Creating and manipulating values inside the QuickJS runtime.
    • API Exposure: Exposing host functions to the QuickJS runtime.
    • Async Support: Executing synchronous code that uses asynchronous functions via asyncify.
    • Cross-Platform Compatibility: Works in browsers, NodeJS, Deno, Bun, Cloudflare Workers, and even QuickJS itself (via quickjs-for-quickjs).
  5. Use QuickJSWASMModule to run JavaScript

    main

    The QuickJSWASMModule class provides a JavaScript interface to the QuickJS interpreter (ES2020 support) by wrapping a WebAssembly module. Each module instance is isolated by the host's WebAssembly runtime, providing the highest level of isolation possible.

    For simple tasks, use evalCode() to evaluate JavaScript and receive a native JavaScript value. For more complex environments, use newContext() or newRuntime() to gain control over execution, memory, and resource limits.

  6. What is a QuickJSAsyncRuntime?

    main

    A QuickJSAsyncRuntime represents a JavaScript runtime corresponding to a specific object heap.

    Key characteristics:

    • Isolation: Multiple runtimes can exist simultaneously, but they cannot exchange objects. You should create separate runtime instances to isolate untrusted code from different sources.
    • No Multi-threading: Within a single runtime, multi-threading is not supported.
    • Analogy: Think of separate runtimes as different browser domains, and the contexts within a runtime as different windows open to the same domain.
    • Stronger Isolation: For even stronger isolation (at the cost of higher memory usage), you can create separate WebAssembly modules using newQuickJSWASMModule.

    Create a runtime using QuickJSWASMModule.newRuntime.

  7. What is a QuickJS variant?

    main

    A variant is a configuration object that defines how to load a QuickJS WebAssembly build and how to access the low-level C API functions. It allows quickjs-emscripten-core to remain pure JavaScript while delegating the heavy WebAssembly loading to specific environment-optimized packages.

    A variant object must implement the following structure:

    • type: Either "sync" or "async". Use "async" if the variant was built with ASYNCIFY to allow WebAssembly execution to be suspended.
    • importFFI: A function that returns a promise resolving to a QuickJSFFI class.
    • importModuleLoader: A function that returns a promise resolving to an Emscripten-shaped WASM module factory.
    const variant = {
      // This should be `async` if the variant is built with ASYNCIFY
      // so that the WebAssembly module execution can be suspended.
      //
      // Otherwise, this should be `sync`.
      type: "sync",
      // This should be a function that resolves to a QuickJSFFI class.
      importFFI: () => import("something/ffi.ts").then((mod) => mod.QuickJSFFI),
      // This should be a function that resolves to a Emscripten-shaped WASM module factory.
      importModuleLoader: () => import("something/emscripten-module.ts"),
    }
  8. What is WeakLifetime and when to use it

    main

    A WeakLifetime is a specialized version of a Lifetime that does not own its underlying value.

    Key characteristics:

    • No Disposal: It never calls its disposer function.
    • Use Case: It is primarily used for function arguments where the lifetime of the object is managed by an external owner, rather than the lifetime object itself.
    • Conversion: While it doesn't own the value, it can be duped to produce regular Lifetime objects that do own the value and call a disposer.
  9. What is a WeakLifetime and when to use it

    main

    A WeakLifetime is a specialized type of Lifetime that does not own its underlying value. Unlike a standard Lifetime, a WeakLifetime will never call its disposer function.

    Key Characteristics:

    • Non-owning: It provides access to a value without being responsible for its destruction.
    • Use Case: It is primarily used for function arguments where the lifetime of the object is managed by a caller or another owner, rather than the function itself.
    • Conversion: You can call .dup() on a WeakLifetime to produce a regular Lifetime that does own the value and will call the disposer.
  10. What is a variant in quickjs-emscripten?

    main

    A variant is a configuration object that defines how to load a QuickJS WebAssembly build and how to access the low-level C API functions required by quickjs-emscripten-core.

    A variant object must include:

    • type: Either "sync" or "async". Use "async" if the variant is built with ASYNCIFY to allow WebAssembly execution to be suspended.
    • importFFI: A function that resolves to a QuickJSFFI class.
    • importModuleLoader: A function that resolves to an Emscripten-shaped WASM module factory.

    You can provide custom variants to control the loading mechanism of the WebAssembly object.

    const variant = {
      // This should be `async` if the variant is built with ASYNCIFY
      // so that the WebAssembly module execution can be suspended.
      //
      // Otherwise, this should be `sync`.
      type: "sync",
      // This should be a function that resolves to a QuickJSFFI class.
      importFFI: () => import("something/ffi.ts").then((mod) => mod.QuickJSFFI),
      // This should be a function that resolves to a Emscripten-shaped WASM module factory.
      importModuleLoader: () => import("something/emscripten-module.ts"),
    }
  11. What is a Lifetime and how does it work?

    main

    A Lifetime<T, TCopy, Owner> is an abstraction used to prevent access to a value (typically a C memory pointer) after it has been disposed. It ensures memory safety by tracking whether the underlying value is still valid.

    Key Concepts

    • Safety: Accessing the .value property after the lifetime has been disposed will throw an error. This prevents use-after-free bugs.
    • Disposal: When dispose() is called, the lifetime executes a disposer function provided during construction to perform necessary cleanup (e.g., freeing memory).
    • Ownership: The Owner type parameter is optional metadata used to track who created the lifetime, but it is not used for logic by the lifetime itself.

    Usage Pattern

    Instead of holding onto raw values, you should hold onto the Lifetime object. Use .map() to transform the value or .dup() to create a new handle to the same value.