所有项目

2025.08 / 独立开发

纯浏览器 Python IDE

无需安装的纯浏览器 Python IDE,支持代码编辑、终端运行、互动输入与智能提示。

ReactTypeScriptPyodideMonacoXTerm.jsPyrightWeb Worker
纯浏览器 Python IDE 预览
打开项目

第一步:让 Python 跑起来

要让 Python 在浏览器跑起来,最直接的做法是把 Python 运行时带到前端。Pyodide 将 CPython 和常用科学计算库编译为 WebAssembly(WASM)。WASM 是浏览器可高效运行的二进制格式,因此前端可以直接加载 Pyodide,并在本机运行 Python。

这先解决了「代码在哪里运行」的问题,但不能直接放在主线程。Python 一旦运行较久,React 界面也会被阻塞,按钮、编辑器和页面更新都会停住。于是下一步是把 Pyodide 放进独立的 Web Worker;主线程只负责渲染界面,耗时运算不会卡住用户操作。

flowchart LR
  UI[React UI] <-->|Comlink| Worker[Python Web Worker]
  Worker --> Runtime[Pyodide / WASM]
  UI --> Editor[Monaco Editor]
  UI --> Terminal[XTerm.js]
  Worker -->|stdout / stderr| Terminal

第二步:接上编辑器与终端

Worker 跑起来后,主线程还需要向它发送加载、运行和停止指令,并接收输出。这里使用 Comlink 将 Worker 的消息通信封装成接近一般函数调用的 RPC 界面,避免手写大量 postMessage 协议。

接著加入 Monaco Editor 作为代码编辑器;它是 VS Code 编辑器的开源核心。终端则采用 XTerm.js,接收 Python 的 stdout/stderr 字符流。到这一步,用户已能在浏览器内编写并运行绝大部分 Python 代码,并在终端查看结果。

还差两件事

基本运行流程完成后,仍有两个能力缺口:

  1. input() 无法直接使用。
  2. 编辑器没有自动完成和错误提示。

互动式 input()

Pyodide 在 Python Worker 内同步调用 stdin 回调,input() 必须在回调结束前拿到字符串。另一边,XTerm.js 的 onData 在主线程中异步接收键盘输入。若 stdin 回调只用 postMessage() 通知主线程后立刻返回,Python 只会读到空字符串;Python Worker 也不能直接 await 主线程上、等待用户输入的 Promise。

项目以 SharedArrayBuffer 和 Atomics 创建同步桥接。stdin 回调先以 Atomics.wait() 阻塞 Python Worker;用户按 Enter 后,主线程把 XTerm.js 收到的 UTF-8 bytes 与长度写入共享 buffer,再以 Atomics.notify() 唤醒 Worker。等待只发生在 Python Worker,React 界面与终端事件仍可正常运行。

浏览器只有在 cross-origin isolation 下才允许使用 SharedArrayBuffer,因此部署端需要同时设置 Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp。写入前也会检查 UTF-8 byte length,避免输入超过 buffer 默认容量。

自动完成与错误提示

Monaco 只负责显示和编辑代码,并不理解 Python 语义。自动完成、属性检查和错误提示需要 Language Server Protocol(LSP):它是编辑器与语言服务之间的通信协议,让不同编辑器可以重用同一套分析能力。

Python LSP 中较成熟的是 Pyright。它已有可在浏览器中运行的 WASM npm 包,因此项目启动另一个 browser worker 加载 Pyright 与 typeshed 类型定义,再通过 monaco-languageclient 将它的 LSP 消息转发给 Monaco。这部分几乎没有现成的浏览器接入文档,需要自行处理 Worker 之间的 transport、文件系统和初始化顺序。

接入后,编辑器可在输入 math. 时显示成员自动完成,也会即时标记不存在的 math.sqrrt 属性:

Monaco 显示 math 的自动完成清单,并以红色波浪线提示不存在的 sqrrt 属性

完整架构

React 主线程只保留用户可见的界面:Monaco、XTerm、输入控制和操作按钮。Python 运行时与 Pyright 分别在自己的 Worker 运行,前者负责运行与终端 I/O,后者只提供代码分析。一般指令和输出走 Comlink/消息通道;只有 input() 需要保留同步语义,才使用 SharedArrayBuffer 与 Atomics。

flowchart TB
  subgraph Main[主线程:React UI]
    Editor[Monaco Editor]
    Terminal[XTerm.js]
    Input[输入控制]
    Controls[运行 / 停止 / 主题]
  end

  subgraph PythonWorker[Python Web Worker]
    Bridge[Comlink API]
    Pyodide[Pyodide / WASM]
    Stdin[stdin 回调]
  end

  subgraph LspWorker[Pyright Web Worker]
    Pyright[Pyright / WASM]
    Typeshed[typeshed 类型定义]
  end

  Controls <-->|运行、停止| Bridge
  Controls <-->|主题切换| Editor
  Bridge --> Pyodide
  Pyodide -->|stdout / stderr| Terminal
  Terminal -->|onData| Input
  Input <-->|SharedArrayBuffer + Atomics| Stdin
  Stdin -->|阻塞 / 唤醒| Pyodide
  Editor <-->|monaco-languageclient / LSP| Pyright
  Typeshed --> Pyright