Skip to main content

High-performance Python JavaScript execution engine based on Deno Core (V8) with full Promise/async support and official Deno Web API integration

Project description

never_jscore

基于 Deno Core (V8) 的高性能 Python JavaScript 执行引擎

PyPI Python License

专为 JavaScript 逆向工程设计 • 完整 Promise/async 支持 • Hook 拦截 • 确定性调试 • 极致性能


一句话介绍

never_jscore 是目前 最快、功能最完整 的 Python JavaScript 引擎,提供:

  • 🚀 极致性能 - 简单任务 255,000+ ops/s,复杂任务 20,000+ ops/s
  • 🎣 双模式 Hook 拦截$return + $terminate),专为逆向设计
  • v3.0.0 GIL 释放优化 - 多线程性能显著提升
  • 🌐 完整 Web/Node.js API,零配置补环境
  • 🎲 确定性随机数,可复现的调试体验
# ❌ 错误方式:每次都要重新加载 JS(慢 ~50 ops/s)
for data in data_list:
    ctx = Context()
    ctx.compile(js_code)
    result = ctx.call("encrypt", [data])

# ✅ 推荐方式:Context 复用,性能极致 (~255,000 ops/s)
ctx = Context()
ctx.compile(js_code)
for data in data_list:
    result = ctx.call("encrypt", [data])  # 快 5000 倍!
del ctx

快速开始

安装

pip install never-jscore

支持:Windows、Linux、macOS | Python 3.8+

推荐:使用 Python 3.14 以获得最佳性能和稳定性

30 秒上手

import never_jscore

# 方式 1:Context(适合探索、调试)
ctx = never_jscore.Context()
ctx.compile("function add(a, b) { return a + b; }")
print(ctx.call("add", [1, 2]))  # 3

# 方式 2:JSEngine(适合批量处理,v3.0.0+)
engine = never_jscore.JSEngine("""
    function encrypt(data) {
        return btoa(data);  // Base64 编码
    }
""", workers=4)

# 批量处理(性能提升 10-100 倍)
results = [engine.call("encrypt", [f"data_{i}"]) for i in range(1000)]
print(f"处理完成:{len(results)} 条数据")

核心特性

🚀 双模式架构(v3.0.0 重大升级)

never_jscore 提供两种执行模式,适应不同场景:

模式对比 (基于实测数据)

特性 Context(推荐 99% 场景) JSEngine(Worker Pool)
JS 代码加载 复用模式:加载一次,反复调用 预加载一次,workers 复用
简单任务性能 255,969 ops/s 743 ops/s
复杂任务性能 23,675 ops/s 550 ops/s
冷启动性能 50 ops/s 607 ops/s
多线程安全 ThreadLocal 模式 ✅ 内置线程安全队列
Hook 数据隔离 全局存储 ✅ Worker 级别隔离
GIL 释放 ✅ v3.0.0 优化 ✅ 自动释放
实现复杂度 ✅ 简单 (3 行代码) ⚠️ 适中
任务调度开销 ~0.004ms ~1-2ms (MPSC channel)
适用场景 大多数场景 (99%) 无法复用 Context 的场景 (1%)

性能结论:Context 复用模式快 50-340 倍

JSEngine 优势场景

Context 模式示例

import never_jscore

# 适合:探索性脚本、调试、灵活修改代码
ctx = never_jscore.Context(enable_extensions=True)

# 定义函数
ctx.compile("""
    async function fetchUserData(userId) {
        const response = await fetch(`https://api.example.com/users/${userId}`);
        return await response.json();
    }
""")

# 调用函数(自动等待 Promise)
user = ctx.call("fetchUserData", [12345])
print(user)  # {'id': 12345, 'name': 'John', ...}

# 一次性求值(不污染全局作用域)
result = ctx.evaluate("Math.random() * 100")
print(result)  # 随机数

del ctx  # 清理资源

JSEngine 模式示例

import never_jscore
from concurrent.futures import ThreadPoolExecutor

# 适合:批量处理、高并发、生产环境
engine = never_jscore.JSEngine("""
    const CryptoJS = require('crypto-js');

    function encrypt(data, key) {
        return CryptoJS.AES.encrypt(data, key).toString();
    }

    function decrypt(encrypted, key) {
        const bytes = CryptoJS.AES.decrypt(encrypted, key);
        return bytes.toString(CryptoJS.enc.Utf8);
    }
""", workers=4, enable_node_compat=True)

# 单线程批量处理
data_list = [f"data_{i}" for i in range(1000)]
results = []
for data in data_list:
    encrypted = engine.call("encrypt", [data, "secret_key"])
    results.append(encrypted)

# 多线程并发处理(自动释放 GIL)
def process(data):
    return engine.call("encrypt", [data, "secret_key"])

with ThreadPoolExecutor(max_workers=10) as executor:
    results = list(executor.map(process, data_list))

print(f"✓ 处理完成:{len(results)} 条数据")
del engine

🎣 双模式 Hook 拦截

专为 JavaScript 逆向工程设计,提供两种 Hook 模式:

模式 1:$return() - 快速拦截

ctx = never_jscore.Context()

# 拦截函数执行
result = ctx.evaluate("""
    function encrypt(data) {
        const step1 = processData(data);

        // Hook:提前返回中间结果
        $return({
            intercepted: true,
            step1: step1,
            timestamp: Date.now()
        });

        // 后续代码不会执行
        const step2 = furtherProcess(step1);
        return step2;
    }

    encrypt("sensitive_data")
""")

print(result)  # {'intercepted': True, 'step1': ..., 'timestamp': ...}

模式 2:$terminate() - 强制终止(无法被 try-catch 捕获)

import json
ctx = never_jscore.Context()

# 即使有 try-catch 也会被终止
try:
    ctx.evaluate("""
        try {
            XMLHttpRequest.prototype.send = function(data) {
                // 强制终止,无法被捕获
                $terminate({
                    url: this._url,
                    method: this._method,
                    encrypted: data
                });
            };

            const xhr = new XMLHttpRequest();
            xhr.open('POST', 'https://api.example.com/login');
            xhr.send(encryptedPayload);
        } catch (e) {
            console.log("不会执行");  // ❌ try-catch 无法捕获
        }
    """)
except Exception as e:
    print("✓ JS 被强制终止")

# 获取拦截的数据
hook_data = json.loads(ctx.get_hook_data())
print(f"拦截到:{hook_data}")

JSEngine + Hook 并发隔离(v3.0.0 新特性)

engine = never_jscore.JSEngine("""
    function processWithHook(id) {
        $terminate({
            taskId: id,
            result: compute(id),
            timestamp: Date.now()
        });
    }
""", workers=4)

# 并发调用(每个 worker 的 hook 数据独立)
from concurrent.futures import ThreadPoolExecutor

def process(task_id):
    result = engine.call("processWithHook", [task_id])

    # v3.0.0: Hook 数据直接在结果中返回
    if isinstance(result, dict) and result.get("__hook__"):
        return result["data"]  # 直接获取
    return result

with ThreadPoolExecutor(max_workers=10) as executor:
    results = list(executor.map(process, range(100)))

print(f"✓ 处理完成:{len(results)} 个任务,每个任务的 hook 数据独立")

🎲 确定性随机数调试

固定随机数种子,让加密算法的执行结果可复现:

# 使用固定种子
ctx = never_jscore.Context(random_seed=12345)

# 每次运行结果完全相同
r1 = ctx.evaluate("Math.random()")  # 0.8831156266...
r2 = ctx.evaluate("Math.random()")  # 0.5465919174...

# 新 Context 使用相同种子,结果也相同
ctx2 = never_jscore.Context(random_seed=12345)
r3 = ctx2.evaluate("Math.random()")  # 0.8831156266... (与 r1 相同)

# 适用于所有随机 API
uuid = ctx.evaluate("crypto.randomUUID()")  # 固定的 UUID
random_bytes = ctx.evaluate("crypto.getRandomValues(new Uint8Array(4))")

影响的 API

  • Math.random()
  • crypto.randomUUID()
  • crypto.getRandomValues()

🌐 完整 Web/Node.js API(零配置补环境)

Web API

ctx = never_jscore.Context()

# Fetch API
result = ctx.evaluate("""
    (async () => {
        const response = await fetch('https://api.github.com/users/github');
        const data = await response.json();
        return data.login;
    })()
""")
print(result)  # 'github'

# localStorage / sessionStorage
ctx.eval("localStorage.setItem('token', 'abc123')")
token = ctx.evaluate("localStorage.getItem('token')")
print(token)  # 'abc123'

# URL / URLSearchParams
url_info = ctx.evaluate("""
    const url = new URL('https://example.com/path?foo=bar');
    ({
        origin: url.origin,
        pathname: url.pathname,
        search: url.search
    })
""")
print(url_info)  # {'origin': 'https://example.com', 'pathname': '/path', ...}

# TextEncoder / TextDecoder
encoded = ctx.evaluate("""
    const encoder = new TextEncoder();
    Array.from(encoder.encode('Hello'))
""")
print(encoded)  # [72, 101, 108, 108, 111]

# ReadableStream
chunks = ctx.evaluate("""
    const stream = new ReadableStream({
        start(controller) {
            controller.enqueue('chunk1');
            controller.enqueue('chunk2');
            controller.close();
        }
    });

    const reader = stream.getReader();
    async function read() {
        const chunks = [];
        while (true) {
            const {done, value} = await reader.read();
            if (done) break;
            chunks.push(value);
        }
        return chunks;
    }
    read()
""")
print(chunks)  # ['chunk1', 'chunk2']

支持的 Web API

  • ✅ URL / URLSearchParams / URLPattern
  • ✅ TextEncoder / TextDecoder / TextEncoderStream / TextDecoderStream
  • ✅ fetch / XMLHttpRequest
  • ✅ localStorage / sessionStorage
  • ✅ atob / btoa (Base64)
  • ✅ console (log/info/warn/error/debug)
  • ✅ Event / EventTarget / CustomEvent
  • ✅ AbortController / AbortSignal
  • ✅ crypto.randomUUID() / crypto.getRandomValues()
  • ✅ setTimeout / setInterval / clearTimeout / clearInterval
  • ✅ performance.now() / mark / measure
  • ✅ ReadableStream / WritableStream / TransformStream
  • ✅ structuredClone

Node.js 兼容模式

# 启用 Node.js 兼容
ctx = never_jscore.Context(enable_node_compat=True)

# 使用 Node.js 内置模块
result = ctx.evaluate("""
    const path = require('path');
    const crypto = require('crypto');

    ({
        joined: path.join('a', 'b', 'c'),
        hash: crypto.createHash('md5').update('hello').digest('hex')
    })
""")
print(result)
# {'joined': 'a\\b\\c', 'hash': '5d41402abc4b2a76b9719d911017c592'}

# 加载 npm 包(需要先 npm install)
ctx2 = never_jscore.Context(enable_node_compat=True)
result = ctx2.evaluate("""
    const { JSDOM } = require('jsdom');
    const dom = new JSDOM('<html><body><h1>Hello</h1></body></html>');
    const document = dom.window.document;

    ({
        tagName: document.querySelector('h1').tagName,
        text: document.querySelector('h1').textContent
    })
""")
print(result)  # {'tagName': 'H1', 'text': 'Hello'}

支持的 Node.js 模块

  • require() 函数
  • ✅ 内置模块:path, fs, crypto, buffer, stream, url, util, events 等
  • ✅ npm 包:jsdom, lodash, crypto-js 等
  • __dirname / __filename
  • process.env / process.cwd()
  • Buffer 全局对象

详见:Node.js API 对比文档

🎨 Canvas 2D API(纯 Rust 实现)

ctx = never_jscore.Context()

# 创建 Canvas 并绘图
ctx.evaluate("""
    const canvas = createCanvas(200, 200);
    const ctx = canvas.getContext('2d');

    // 绘制矩形
    ctx.fillStyle = 'red';
    ctx.fillRect(10, 10, 50, 50);

    // 绘制文字
    ctx.font = '20px Arial';
    ctx.fillText('Hello', 70, 35);

    // 保存为 PNG
    canvas.toDataURL();  // 返回 base64
""")

详见:Canvas API 参考文档

🔬 V8 堆内存分析

ctx = never_jscore.Context()

# 获取堆统计信息
stats = ctx.get_heap_statistics()
print(f"总堆大小: {stats['total_heap_size'] / 1024 / 1024:.2f} MB")
print(f"已使用堆: {stats['used_heap_size'] / 1024 / 1024:.2f} MB")
print(f"使用率: {stats['used_heap_size'] / stats['total_heap_size'] * 100:.1f}%")

# 导出 Chrome DevTools 堆快照
ctx.take_heap_snapshot("heap_snapshot.heapsnapshot")
# 然后在 Chrome DevTools -> Memory -> Load 加载快照进行分析

# 手动触发 GC
ctx.gc()

性能基准测试

与其他库对比

测试项目 never_jscore PyMiniRacer PyExecJS
简单计算 0.007ms 0.005ms 2.3ms
字符串操作 0.004ms 0.008ms 2.3ms
数组操作 0.004ms 0.006ms 2.3ms
复杂算法(1000次) 11ms 38ms 69473ms
Promise/async ✅ 3ms ❌ 不支持 ❌ 不支持

Context vs JSEngine 性能真相 ⚠️

重要发现:Context 复用模式在 99% 场景下性能更好!

测试场景 Context (复用) JSEngine (Pool) 性能差距
简单 JS (btoa) 255,969 ops/s 743 ops/s Context 快 344x 🔥
复杂计算 (循环) 23,675 ops/s 550 ops/s Context 快 43x 🔥
冷启动 (每次重建) 50 ops/s 607 ops/s JSEngine 快 12x
import time
import never_jscore

js_code = "function test() { return btoa('hello'); }"

# 方案 1:Context 冷启动(不推荐)
start = time.time()
for i in range(1000):
    ctx = never_jscore.Context()
    ctx.compile(js_code)
    result = ctx.call("test", [])
    del ctx
t1 = time.time() - start

# 方案 2:Context 复用(⭐ 推荐大多数场景)
start = time.time()
ctx = never_jscore.Context()
ctx.compile(js_code)
for i in range(1000):
    result = ctx.call("test", [])
del ctx
t2 = time.time() - start

# 方案 3:JSEngine Worker Pool(⚠️ 仅冷启动场景有优势)
start = time.time()
engine = never_jscore.JSEngine(js_code, workers=4)
for i in range(1000):
    result = engine.call("test", [])
del engine
t3 = time.time() - start

print(f"Context(冷启动): {t1:.2f}s  (~{1000/t1:.0f} ops/s)")
print(f"Context(复用):   {t2:.3f}s  (~{1000/t2:.0f} ops/s) ⭐")
print(f"JSEngine(Pool):  {t3:.2f}s  (~{1000/t3:.0f} ops/s)")

实测结果(1000 次调用):

  • Context(冷启动):~20s (50 ops/s)
  • Context(复用):0.004s (255,969 ops/s)
  • JSEngine(Pool):~1.3s (743 ops/s)

结论:Context 复用比 JSEngine 快 344 倍

原因:JSEngine 的任务调度开销 (MPSC channel) 约 1-2ms/次,远大于简单 JS 执行时间。

📖 详细分析请参考:性能优化指南


API 参考

Context 类

never_jscore.Context(
    enable_extensions: bool = True,      # 启用 Web API 扩展
    enable_logging: bool = False,        # 打印 Rust 日志(调试用)
    random_seed: int | None = None,      # 随机数种子(None = 真随机)
    enable_node_compat: bool = False     # 启用 Node.js 兼容模式
)

方法

方法 说明 返回值
compile(code) 编译代码到全局作用域 None
evaluate(code, auto_await=True) 求值(不污染全局) Any
eval(code, return_value=False, auto_await=True) 执行代码(可选返回值) Any
call(name, args, auto_await=True) 调用函数 Any
gc() 请求垃圾回收 None
get_heap_statistics() 获取 V8 堆统计 dict
take_heap_snapshot(path) 导出堆快照 None
get_hook_data() 获取 Hook 数据 str | None
clear_hook_data() 清空 Hook 数据 None

v3.0.0 性能优化:所有方法现在都会释放 GIL,多线程性能显著提升!

JSEngine 类(v3.0.0+)

never_jscore.JSEngine(
    js_code: str,                    # 预加载的 JavaScript 代码
    workers: int = 4,                # Worker 数量
    enable_extensions: bool = True,  # 启用 Web API 扩展
    enable_node_compat: bool = False # 启用 Node.js 兼容模式
)

workers 参数配置 ⚠️:

单线程顺序调用时,workers 数量对性能影响很小(差异 < 2%):

# 实测:复杂 JS 计算
engine_1 = JSEngine(js_code, workers=1)  # 702 ops/s
engine_4 = JSEngine(js_code, workers=4)  # 713 ops/s (几乎相同)

推荐配置

  • 单线程顺序调用 → workers=1 (或直接用 Context 复用,快 22 倍)
  • 多线程并发 → workers = CPU 核心数
  • FastAPI 等异步框架 → workers = CPU 核心数

方法

方法 说明 返回值
call(function_name, args) 调用预加载的函数 Any
execute(code) 执行一次性代码 Any

选择建议 (基于实测数据):

场景 推荐 原因
单线程批量处理 Context 复用 快 50-300 倍
FastAPI / Flask Context + ThreadLocal GIL 释放,性能极佳
多线程并发 Context + ThreadLocal 性能最好
每次不同 JS 代码 JSEngine 避免重复加载
大型 JS 库 (>1MB) JSEngine 预加载优势
简单 JS (btoa/hash) Context 复用 快 300+ 倍
复杂计算 Context 复用 快 40+ 倍

默认建议: 先用 Context 复用,除非遇到冷启动问题再考虑 JSEngine

📖 详细选择指南:性能优化指南 - 快速决策表

类型转换

Python JavaScript 示例
None null Nonenull
bool boolean Truetrue
int number 4242
float number 3.143.14
str string "hello""hello"
list Array [1, 2][1, 2]
dict Object {"a": 1}{a: 1}

嵌套结构自动转换

ctx = never_jscore.Context()

# Python → JavaScript
result = ctx.call("processData", [{
    "users": [
        {"id": 1, "name": "Alice", "active": True},
        {"id": 2, "name": "Bob", "active": False}
    ],
    "count": 2
}])

# JavaScript → Python
data = ctx.evaluate("({status: 'ok', items: [1, 2, 3]})")
print(type(data))  # <class 'dict'>
print(data['items'])  # [1, 2, 3]

最佳实践

✅ 推荐做法

1. Context 复用(提升性能)

# ✅ 推荐:复用 Context
ctx = never_jscore.Context()
ctx.compile(js_code)

for i in range(10000):
    result = ctx.call("encrypt", [data])

del ctx  # 清理资源

2. JSEngine 批量处理(v3.0.0)

# ✅ 推荐:批量处理使用 JSEngine
engine = never_jscore.JSEngine(js_code, workers=4)

for data in data_list:  # 可以处理任意数量
    result = engine.call("encrypt", [data])

del engine

3. 多线程 + ThreadLocal(Context 模式)

# ✅ 推荐:每个线程复用 Context
import threading
from concurrent.futures import ThreadPoolExecutor

thread_local = threading.local()

def get_context():
    if not hasattr(thread_local, 'ctx'):
        thread_local.ctx = never_jscore.Context()
        thread_local.ctx.compile(js_code)
    return thread_local.ctx

def worker(data):
    ctx = get_context()
    return ctx.call("process", [data])

with ThreadPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(worker, data_list))

4. 多线程 + JSEngine(v3.0.0,更简单)

# ✅ 推荐:JSEngine 自动处理线程安全
from concurrent.futures import ThreadPoolExecutor

engine = never_jscore.JSEngine(js_code, workers=4)

def worker(data):
    return engine.call("process", [data])

with ThreadPoolExecutor(max_workers=10) as executor:
    results = list(executor.map(worker, data_list))

del engine

5. 多进程 + 多线程并发(v3.1.0,生产级并发)

# ✅ 推荐:多进程套多线程,充分利用多核 CPU
import os
from multiprocessing import get_context
from concurrent.futures import ThreadPoolExecutor
import never_jscore

js_code = """
function encrypt(data, key) {
    return btoa(data + key);
}
"""

def process_worker(process_id):
    """每个进程的工作函数"""
    pid = os.getpid()
    print(f"进程 {process_id} 启动 (PID: {pid})")

    # ⚠️ 重要:每个进程必须创建独立的 JSEngine/Context
    # JSEngine/Context 不能跨进程共享!
    engine = never_jscore.JSEngine(js_code, workers=2, enable_node_compat=True)

    def thread_worker(task_id):
        """线程任务"""
        return engine.call("encrypt", [f"data_{task_id}", "secret"])

    # 每个进程内使用多线程
    with ThreadPoolExecutor(max_workers=2) as pool:
        futures = [pool.submit(thread_worker, i) for i in range(10)]
        results = [f.result() for f in futures]

    del engine  # 显式清理资源
    return len([r for r in results if r])

if __name__ == "__main__":
    # ⚠️ Windows 必须使用 'spawn' 方式
    ctx = get_context('spawn')

    with ctx.Pool(processes=4) as pool:
        results = pool.map(process_worker, range(4))

    print(f"总计完成: {sum(results)} 个任务")

多进程注意事项 ⚠️:

要点 说明
进程隔离 每个进程必须创建独立的 JSEngine/Context,不能跨进程共享
Windows 兼容 必须使用 get_context('spawn'),不能用 fork
__main__ 保护 多进程代码必须放在 if __name__ == "__main__":
资源清理 进程结束前用 del engine 显式清理,确保正常退出
线程数配置 每个进程的 workers 数 × 进程数 ≤ CPU 核心数 × 2

典型配置建议(8 核 CPU):

# 方案 1:4 进程 × 2 workers = 8 并发
ctx.Pool(processes=4)  # 4 进程
JSEngine(js_code, workers=2)  # 每进程 2 workers

# 方案 2:2 进程 × 4 workers = 8 并发
ctx.Pool(processes=2)  # 2 进程
JSEngine(js_code, workers=4)  # 每进程 4 workers

# 方案 3:Context + ThreadLocal(最高性能)
# 每个进程内用 ThreadLocal 复用 Context

❌ 错误做法

1. 循环中重复创建 Context

# ❌ 错误:性能极差
for i in range(1000):
    ctx = never_jscore.Context()
    ctx.compile(js_code)
    result = ctx.call("encrypt", [data])
    del ctx  # 每次都要重新初始化 V8

2. 循环中直接使用 with 语句

# ❌ 错误:会在第 10-20 次崩溃
for i in range(100):
    with never_jscore.Context() as ctx:
        result = ctx.call("encrypt", [data])

原因:Python 的 with 不保证对象立即销毁,导致 V8 Isolate 堆积。

解决方案:用函数包装

# ✅ 正确:函数作用域强制清理
def process(data):
    with never_jscore.Context() as ctx:
        ctx.compile(js_code)
        return ctx.call("encrypt", [data])

for i in range(10000):
    result = process(data)

3. 跨线程共享 Context

# ❌ 错误:会崩溃
ctx = never_jscore.Context()  # 全局 Context

def worker(data):
    return ctx.call("encrypt", [data])  # ❌ 多线程访问同一个 Context

with ThreadPoolExecutor(max_workers=4) as executor:
    results = executor.map(worker, data_list)  # 崩溃

原因:Context 不是线程安全的。

解决方案:使用 ThreadLocal 或 JSEngine。


常见问题

Q: 什么时候选择 never_jscore 而不是 PyMiniRacer?

选择 never_jscore

  • ✅ 需要 Promise/async 支持(现代 JS 库必须)
  • ✅ 需要浏览器/Node.js 环境(补环境)
  • ✅ 需要 Hook 拦截功能(逆向必备)
  • ✅ 需要确定性随机数(调试加密算法)
  • ✅ 需要批量处理(JSEngine 性能更强)

选择 PyMiniRacer

  • ✅ 只需要执行简单同步 JS
  • ✅ 不需要任何 Web API

Q: 为什么比 PyExecJS 快 100-300 倍?

PyExecJS 架构

Python → 启动进程 → 外部 JS 引擎 → JSON 序列化 → 进程通信 → Python

每次调用都有进程启动和 IPC 开销(~2ms)。

never_jscore 架构

Python → Rust FFI → V8 引擎 → Rust FFI → Python

直接内存通信,无进程开销(~0.004ms)。

Q: compile() 和 evaluate() 有什么区别?

compile() evaluate() / call()
用途 定义函数、加载库 执行代码、获取结果
全局作用域 ✅ 影响 ❌ 不影响
运行微任务 ✅ queueMicrotask ✅ queueMicrotask
运行宏任务 ❌ 不等待 setTimeout ✅ 等待 setTimeout
等待 Promise ❌ 不等待 ✅ 自动等待

典型用法

# 第一步:用 compile 加载 JS 库(快)
ctx.compile("""
    function encrypt(data) {
        return new Promise(resolve => {
            setTimeout(() => resolve(btoa(data)), 100);
        });
    }
""")

# 第二步:用 call 调用函数(自动等待 Promise)
result = ctx.call("encrypt", ["hello"])  # 会等待 100ms

Q: Context vs JSEngine,该用哪个?⚠️ 重要

真相:Context 复用模式在 99% 场景下性能更好(快 50-340 倍)!

快速判断

  • 默认选择 → Context 复用 ⭐
  • 无法复用 (每次不同 JS 代码) → JSEngine
  • 大型 JS 库 (>1MB) 冷启动 → JSEngine
  • FastAPI / Flask → Context + ThreadLocal ⭐
  • 单线程批量 → Context 复用 ⭐

性能对比 (实测):

# Context 复用:255,969 ops/s ⭐
ctx = Context()
ctx.compile(js_code)
for data in data_list:
    result = ctx.call("process", [data])

# JSEngine:743 ops/s (慢 344 倍)
engine = JSEngine(js_code, workers=4)
for data in data_list:
    result = engine.call("process", [data])

📖 详细分析:性能优化指南

Q: 如何处理大量数据(避免内存溢出)?

方法 1:批量处理 + 手动 GC

ctx = never_jscore.Context()
ctx.compile(js_code)

for batch in chunks(data, 1000):  # 每 1000 条一批
    results = [ctx.call("process", [item]) for item in batch]
    ctx.gc()  # 手动触发垃圾回收
    save_results(results)

方法 2:使用 JSEngine(推荐)

engine = never_jscore.JSEngine(js_code, workers=4)

for item in data:  # 可以处理任意数量
    result = engine.call("process", [item])
    save_result(result)

del engine

完整示例

查看 tests/ 目录获取更多示例:

测试文件 功能
test_async_promise.py Promise/async/await
test_terminate_hook.py Hook 拦截系统
test_random_seed.py 确定性随机数
test_multithreading.py 多线程使用
test_engine.py JSEngine Worker Pool
test_memory_and_performance.py 内存监控和性能
canvas_complete_example.py Canvas 2D API

运行所有测试:

python tests/run_all_tests.py

更新日志

v3.1.0 (2026-01-30) ⚡ 性能优化与稳定性修复

  • 🔧 进程退出修复

    • 修复 Python 进程执行完成后卡住不退出的问题
    • 每个 Context 现在拥有独立的 Tokio Runtime,随 Context 销毁自动清理
    • 解决 setTimeout 定时器阻塞事件循环的问题
  • JSEngine Runtime 复用优化

    • 使用 thread_local OnceCell 复用 Tokio Runtime
    • 避免每次调用都创建新 Runtime,性能提升约 200%
  • 🚀 类型转换性能优化

    • json_to_python 数组转换消除双重迭代
    • ResultStorage 使用 Cell<bool> 替代 RefCell<bool>,减少借用检查开销
  • 🔧 FastReturnMode 修复

    • op_store_result 仅在 FastReturnMode 启用时才终止执行
    • 修复非 fast_return 模式下 "execution terminated" 错误
  • 📖 多进程 + 多线程文档

    • 新增生产级多进程并发使用指南
    • 详细说明 Windows spawn 模式、进程隔离等注意事项

v3.0.0 (2026-01-01) 🎉 重大架构升级

  • 🚀 Worker Pool 架构 - JSEngine

    • 预加载 JS 代码到多个 workers,避免重复加载
    • 适用场景:冷启动优化(无法复用 Context 时)
    • 冷启动性能提升 10-100 倍(相比 Context 重复加载)
    • Worker 级别的 hook 数据隔离,无数据竞争
    • Hook 数据直接返回,消除竞态条件
    • 自动 Worker 池管理和任务调度
    • 多线程使用例子test_engine.py,test_multithreading.py,
    • 不同使用情况下测速test_performance_comparison.py
  • Context GIL 释放优化 ⭐ 最重要的性能提升

    • 所有方法(compile, call, eval, evaluate)现在都会释放 GIL
    • 使用 SendPtr 包装器实现安全的 GIL 释放
    • 性能飞跃:Context 复用模式达到 255,000 ops/s(简单任务)
    • 多线程 Python 程序性能显著提升
  • 🔧 Cargo.toml 依赖优化

    • 移除 7 个不必要的显式依赖
    • 依赖精简 14%,降低编译复杂度
  • 📖 性能真相揭秘

    • Context 复用 快 50-340 倍(相比 JSEngine)
    • UVICORN_WORKERS_EXPLAINED.md fastapi多进程测试报告
    • 更新最佳实践建议:默认使用 Context 复用

v2.5.2 (2025-12-26)

  • 🎯 Canvas 2D API(纯 Rust 实现,替代 node-canvas)
  • 🔧 编译参数优化,Linux whl 从 41MB 减小至 29MB
  • 🛡️ deno_core 升级至 0.376.0(V8 142.2.0)

v2.5.0 (2025-11-30)

  • 🏗️ 模块化扩展架构
  • 🔄 完整 Node.js 兼容层(require + npm 包)
  • 🛡️ API 保护增强(隐藏 Deno 特征)

详见完整更新日志(历史版本省略)。


文档和资源

📚 官方文档

🔗 相关项目

  • Deno - 现代 JavaScript/TypeScript 运行时
  • PyO3 - Rust ↔ Python 绑定库

技术交流

  • 技术交流群:加微信 xu970821582
  • 博客http://www.ma2e.top/
  • 提醒:推荐使用 Python 3.14 以获得最佳性能

许可证

MIT License - 详见 LICENSE

警告:仅供技术研究和学习,请勿用于违法用途,后果自负。


贡献和反馈

欢迎提交 Issue 和 Pull Request!


⭐ 如果这个项目对你有帮助,请给个 Star!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

never_jscore-3.1.0.tar.gz (245.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

never_jscore-3.1.0-cp38-abi3-win_amd64.whl (24.7 MB view details)

Uploaded CPython 3.8+Windows x86-64

never_jscore-3.1.0-cp38-abi3-manylinux_2_34_x86_64.whl (30.7 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.34+ x86-64

never_jscore-3.1.0-cp38-abi3-macosx_11_0_arm64.whl (23.8 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

never_jscore-3.1.0-cp38-abi3-macosx_10_12_x86_64.whl (25.0 MB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file never_jscore-3.1.0.tar.gz.

File metadata

  • Download URL: never_jscore-3.1.0.tar.gz
  • Upload date:
  • Size: 245.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for never_jscore-3.1.0.tar.gz
Algorithm Hash digest
SHA256 afee6438406ceff1478ad4d477e6949945bb23422e0cd77342390efca4af0477
MD5 7dd927a7638495289d1690749f8b65d0
BLAKE2b-256 b938cb0470430e5f97ac0fb2ba1cf617d5eb3fab09a07e8bf73bac2b281fd7c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for never_jscore-3.1.0.tar.gz:

Publisher: build-wheels.yml on neverl805/never-jscore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file never_jscore-3.1.0-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: never_jscore-3.1.0-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 24.7 MB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for never_jscore-3.1.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 fb7e87df714707ebc32acef3509ed97456aa5f2f1bba622d70e3e30fbc272a40
MD5 c8e2feff163ea530139f6429a29cad5e
BLAKE2b-256 97668c7b94ca434c1deff2e204961d76433bc636ee3292f1f9c19614f5339062

See more details on using hashes here.

File details

Details for the file never_jscore-3.1.0-cp38-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for never_jscore-3.1.0-cp38-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 cea0e0d2528562a92b1ed49debd1fdc1f1815779111ecd0310d4ac4cb9bba68e
MD5 1545ceb850771b4baf3656297538d1b2
BLAKE2b-256 470f7ff191c6cd8aa6a8d14d1aa5f3b977e97c46cd94b2ef6cd2aacd68e32d15

See more details on using hashes here.

File details

Details for the file never_jscore-3.1.0-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for never_jscore-3.1.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 bbbb4d649814d1c6dfbfb86cc811da797b4fc3eb56650745386db32faa21f070
MD5 ae0d574e8e67f48f930c1c2a1636d0d0
BLAKE2b-256 d92e47519cd528a791321db94bd8c7da9e77693f01bb7cdfce3f91a309604036

See more details on using hashes here.

Provenance

The following attestation bundles were made for never_jscore-3.1.0-cp38-abi3-macosx_11_0_arm64.whl:

Publisher: build-wheels.yml on neverl805/never-jscore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file never_jscore-3.1.0-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for never_jscore-3.1.0-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 9ace939ff4bb7385cb65a754d949999510779bfdd2a0e6cdcd2c11203480db2c
MD5 2bab2a0302369602bada24108610c52e
BLAKE2b-256 a53cfd55b4d19c74de833bd699e37b3a03d77bf5aa9eadda23ad12394515293d

See more details on using hashes here.

Provenance

The following attestation bundles were made for never_jscore-3.1.0-cp38-abi3-macosx_10_12_x86_64.whl:

Publisher: build-wheels.yml on neverl805/never-jscore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page