denox

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Denox — Embed Deno JS/TS Runtime in Elixir

Denox — 在Elixir中嵌入Deno JS/TS运行时

Overview

概述

Denox embeds a Deno V8 runtime into Elixir via a Rustler NIF. Evaluate JS/TS, load ES modules, import from CDNs/npm/jsr, and call functions across the boundary — all in-process.
Denox通过Rustler NIF将Deno V8运行时嵌入到Elixir中。您可以执行JS/TS代码、加载ES模块、从CDN/npm/jsr导入资源,以及跨边界调用函数——所有操作都在进程内完成。

When to Use

适用场景

  • Evaluating JavaScript or TypeScript from Elixir
  • Loading ES modules with
    import
    /
    export
  • Using npm/jsr packages in Elixir applications
  • Running async JS (Promises, dynamic
    import()
    )
  • Calling Elixir functions from JavaScript (callbacks)
  • Pre-initializing V8 state with snapshots
  • 从Elixir中执行JavaScript或TypeScript代码
  • 加载支持
    import
    /
    export
    的ES模块
  • 在Elixir应用中使用npm/jsr包
  • 运行异步JS代码(Promises、动态
    import()
  • 从JavaScript中调用Elixir函数(回调)
  • 使用快照预初始化V8状态

Quick Reference

快速参考

Installation

安装

elixir
undefined
elixir
undefined

mix.exs

mix.exs

{:denox, "~> 0.2.0"}

Requires Rust (stable). First compile ~20-30 min (V8 builds from source).
{:denox, "~> 0.2.0"}

需要Rust(稳定版)。首次编译约20-30分钟(V8从源码构建)。

Core API

核心API

FunctionPurposeEvent Loop
Denox.runtime(opts)
Create V8 isolate
Denox.eval(rt, js)
Eval JS, return JSON stringNo
Denox.eval_ts(rt, ts)
Transpile+eval TS (no type-check)No
Denox.eval_async(rt, js)
Eval with Promises/
import()
Yes
Denox.eval_ts_async(rt, ts)
Async TS evalYes
Denox.exec(rt, code)
Eval, discard result (
:ok
)
No
Denox.eval_module(rt, path)
Load ES module fileYes
Denox.eval_file(rt, path)
Read+eval file (no import/export)No
Denox.call(rt, name, args)
Call named JS functionNo
Denox.call_async(rt, name, args)
Call async JS functionYes
Denox.eval_decode(rt, code)
Eval +
Jason.decode
result
No
Denox.eval_ts_decode(rt, code)
TS eval + decodeNo
Denox.call_decode(rt, name, args)
Call + decodeNo
Denox.call_async_decode(rt, name, args)
Async call + decodeYes
All functions return
{:ok, result} | {:error, message}
. The
exec
variants return
:ok | {:error, message}
.
函数用途事件循环
Denox.runtime(opts)
创建V8隔离环境
Denox.eval(rt, js)
执行JS代码,返回JSON字符串
Denox.eval_ts(rt, ts)
转译并执行TS代码(无类型检查)
Denox.eval_async(rt, js)
执行包含Promises/
import()
的代码
Denox.eval_ts_async(rt, ts)
异步执行TS代码
Denox.exec(rt, code)
执行代码,丢弃结果(返回
:ok
Denox.eval_module(rt, path)
加载ES模块文件
Denox.eval_file(rt, path)
读取并执行文件(不支持import/export)
Denox.call(rt, name, args)
调用命名JS函数
Denox.call_async(rt, name, args)
调用异步JS函数
Denox.eval_decode(rt, code)
执行代码并通过
Jason.decode
解析结果
Denox.eval_ts_decode(rt, code)
执行TS代码并解析结果
Denox.call_decode(rt, name, args)
调用函数并解析结果
Denox.call_async_decode(rt, name, args)
异步调用函数并解析结果
所有函数返回
{:ok, result} | {:error, message}
exec
系列函数返回
:ok | {:error, message}

Runtime Options

运行时选项

elixir
Denox.runtime(
  base_dir: "lib/js",           # resolve relative module imports
  sandbox: true,                # disable fs/net extensions
  cache_dir: "_denox/cache",    # disk cache for remote modules
  import_map: %{"utils" => "file:///path/to/utils.js"},
  callback_pid: pid,            # enable JS→Elixir callbacks
  snapshot: snapshot_bytes       # V8 snapshot for fast cold start
)
elixir
Denox.runtime(
  base_dir: "lib/js",           # 解析相对模块导入路径
  sandbox: true,                # 禁用文件系统/网络扩展
  cache_dir: "_denox/cache",    # 远程模块的磁盘缓存目录
  import_map: %{"utils" => "file:///path/to/utils.js"},
  callback_pid: pid,            # 启用JS→Elixir回调
  snapshot: snapshot_bytes       # 用于快速冷启动的V8快照
)

Sync vs Async

同步 vs 异步

Use
eval
/
call
for simple expressions. Use
eval_async
/
call_async
when code contains:
  • await
    or Promises
  • Dynamic
    import()
  • setTimeout
    /event-loop-dependent code
elixir
undefined
简单表达式使用
eval
/
call
。当代码包含以下内容时,使用
eval_async
/
call_async
  • await
    或Promises
  • 动态
    import()
  • setTimeout
    或依赖事件循环的代码
elixir
undefined

Sync — fast, no event loop

同步 — 速度快,无事件循环

{:ok, "3"} = Denox.eval(rt, "1 + 2")
{:ok, "3"} = Denox.eval(rt, "1 + 2")

Async — pumps event loop

异步 — 驱动事件循环

{:ok, "42"} = Denox.eval_async(rt, "return await Promise.resolve(42)")
undefined
{:ok, "42"} = Denox.eval_async(rt, "return await Promise.resolve(42)")
undefined

TypeScript

TypeScript支持

Transpile-only via deno_ast/swc. No type-checking (same as
deno run
without
--check
). Type errors like
const x: string = 42
transpile without error.
elixir
{:ok, "42"} = Denox.eval_ts(rt, "const x: number = 42; x")
通过deno_ast/swc仅进行转译,不做类型检查(与不带
--check
参数的
deno run
行为一致)。类似
const x: string = 42
的类型错误会被直接转译,不会报错。
elixir
{:ok, "42"} = Denox.eval_ts(rt, "const x: number = 42; x")

Function Calls

函数调用

Define functions in JS, call from Elixir with JSON-serializable args:
elixir
Denox.exec(rt, "globalThis.add = (a, b) => a + b")
{:ok, "5"} = Denox.call(rt, "add", [2, 3])
{:ok, 5} = Denox.call_decode(rt, "add", [2, 3])
在JS中定义函数,从Elixir传入可JSON序列化的参数进行调用:
elixir
Denox.exec(rt, "globalThis.add = (a, b) => a + b")
{:ok, "5"} = Denox.call(rt, "add", [2, 3])
{:ok, 5} = Denox.call_decode(rt, "add", [2, 3])

ES Modules

ES模块

elixir
undefined
elixir
undefined

Load module with import/export support

加载支持import/export的模块

{:ok, rt} = Denox.runtime(base_dir: "/path/to/project") {:ok, _} = Denox.eval_module(rt, "/path/to/project/main.ts")
undefined
{:ok, rt} = Denox.runtime(base_dir: "/path/to/project") {:ok, _} = Denox.eval_module(rt, "/path/to/project/main.ts")
undefined

CDN Imports

CDN导入

elixir
{:ok, rt} = Denox.runtime(cache_dir: "_denox/cache")
{:ok, result} = Denox.eval_async(rt, """
  const { z } = await import("https://esm.sh/zod@3.22");
  return z.string().parse("hello");
""")
Must use
eval_async
— dynamic
import()
returns a Promise.
elixir
{:ok, rt} = Denox.runtime(cache_dir: "_denox/cache")
{:ok, result} = Denox.eval_async(rt, """
  const { z } = await import("https://esm.sh/zod@3.22");
  return z.string().parse("hello");
""")
必须使用
eval_async
——动态
import()
会返回Promise。

Runtime Pool

运行时池

For concurrent workloads (V8 isolates are single-threaded):
elixir
undefined
针对并发工作负载(V8隔离环境是单线程的):
elixir
undefined

Supervision tree

监督树

children = [{Denox.Pool, name: :js_pool, size: 4}]
children = [{Denox.Pool, name: :js_pool, size: 4}]

Usage (round-robin)

使用(轮询调度)

{:ok, result} = Denox.Pool.eval(:js_pool, "1 + 2") Denox.Pool.load_npm(:js_pool, "priv/bundles/zod.js") # load into all

Pool options: `:name` (required), `:size` (default: schedulers count), plus all runtime options.
{:ok, result} = Denox.Pool.eval(:js_pool, "1 + 2") Denox.Pool.load_npm(:js_pool, "priv/bundles/zod.js") # 加载到所有实例

池选项:`:name`(必填)、`:size`(默认:调度器数量),以及所有运行时选项。

JS → Elixir Callbacks

JS → Elixir回调

elixir
{:ok, rt, handler} = Denox.CallbackHandler.runtime(
  callbacks: %{
    "greet" => fn [name] -> "Hello, #{name}!" end,
    "add" => fn [a, b] -> a + b end
  }
)

{:ok, _} = Denox.eval(rt, ~s[Denox.callback("greet", "Alice")])
Callback functions receive a list of decoded JSON arguments.
elixir
{:ok, rt, handler} = Denox.CallbackHandler.runtime(
  callbacks: %{
    "greet" => fn [name] -> "Hello, #{name}!" end,
    "add" => fn [a, b] -> a + b end
  }
)

{:ok, _} = Denox.eval(rt, ~s[Denox.callback("greet", "Alice")])
回调函数接收已解码的JSON参数列表。

V8 Snapshots

V8快照

Pre-initialize global state for instant startup:
elixir
{:ok, snap} = Denox.create_snapshot("globalThis.helper = (x) => x * 2")
{:ok, rt} = Denox.runtime(snapshot: snap)
{:ok, "10"} = Denox.call(rt, "helper", [5])
预初始化全局状态以实现即时启动:
elixir
{:ok, snap} = Denox.create_snapshot("globalThis.helper = (x) => x * 2")
{:ok, rt} = Denox.runtime(snapshot: snap)
{:ok, "10"} = Denox.call(rt, "helper", [5])

TypeScript snapshots

TypeScript快照

{:ok, snap} = Denox.create_snapshot("globalThis.add = (a: number, b: number) => a + b", transpile: true)
undefined
{:ok, snap} = Denox.create_snapshot("globalThis.add = (a: number, b: number) => a + b", transpile: true)
undefined

Dependency Management (
Denox.Deps
)

依赖管理(
Denox.Deps

Requires
deno
CLI at build-time only.
elixir
undefined
仅在构建时需要
deno
CLI。
elixir
undefined

Install from deno.json

从deno.json安装依赖

Denox.Deps.install()
Denox.Deps.install()

Add/remove deps

添加/移除依赖

Denox.Deps.add("zod", "npm:zod@^3.22") Denox.Deps.remove("zod")
Denox.Deps.add("zod", "npm:zod@^3.22") Denox.Deps.remove("zod")

List deps

列出依赖

{:ok, imports} = Denox.Deps.list()
{:ok, imports} = Denox.Deps.list()

Create runtime with installed deps

使用已安装的依赖创建运行时

{:ok, rt} = Denox.Deps.runtime()

Mix tasks: `mix denox.install`, `mix denox.add <name> <spec>`, `mix denox.remove <name>`.
{:ok, rt} = Denox.Deps.runtime()

Mix任务:`mix denox.install`、`mix denox.add <name> <spec>`、`mix denox.remove <name>`。

Pre-Bundling (
Denox.Npm
)

预打包(
Denox.Npm

Bundle npm packages into self-contained JS files (requires
deno
CLI):
elixir
Denox.Npm.bundle!("npm:zod@3.22", "priv/bundles/zod.js")
:ok = Denox.Npm.load(rt, "priv/bundles/zod.js")
将npm包打包为独立的JS文件(需要
deno
CLI):
elixir
Denox.Npm.bundle!("npm:zod@3.22", "priv/bundles/zod.js")
:ok = Denox.Npm.load(rt, "priv/bundles/zod.js")

Telemetry

遥测

Events:
[:denox, :eval, :start | :stop | :exception]
Types:
:eval
,
:eval_ts
,
:eval_async
,
:eval_ts_async
,
:eval_module
,
:eval_file
事件:
[:denox, :eval, :start | :stop | :exception]
类型:
:eval
:eval_ts
:eval_async
:eval_ts_async
:eval_module
:eval_file

Common Mistakes

常见错误

MistakeFix
Using
eval
with
await
/
import()
Use
eval_async
eval
doesn't pump the event loop
Expecting type errors from
eval_ts
Denox is transpile-only, no type-checking
Sharing one runtime across concurrent tasksUse
Denox.Pool
— V8 isolates are single-threaded
Forgetting
return
in
eval_async
Async wraps code in IIFE; use
return
for the result
CDN import without
cache_dir
Set
cache_dir
to avoid re-fetching on every runtime
Running untrusted code without sandboxUse
sandbox: true
to disable fs/net extensions
错误修复方案
使用
eval
执行包含
await
/
import()
的代码
使用
eval_async
——
eval
不会驱动事件循环
期望
eval_ts
返回类型错误
Denox仅做转译,不进行类型检查
在并发任务中共享单个运行时使用
Denox.Pool
——V8隔离环境是单线程的
eval_async
中忘记写
return
异步代码会被包裹在IIFE中;需要用
return
指定返回结果
CDN导入未设置
cache_dir
设置
cache_dir
避免每次创建运行时都重新拉取资源
运行不受信任的代码未启用沙箱使用
sandbox: true
禁用文件系统/网络扩展