浏览器插件制作流程
目标与效果
打开浏览器扩展图标 → 弹出页 执行
hello.py
→ 在页面内看到输出完全离线:不依赖 CDN;Pyodide 运行时随扩展打包
便于扩展:后续可把任何 Python 逻辑放进来
适用于以下浏览器
- Google Chrome 140
- Chromium Version 141
- Firefox
-
- 用于浏览器扩展或本地部署(例如前端中直接运行 Python),通常选择 “full distribution” 版本,即包含所有 vendored 包(第三方库预打包)的大包。通常文件比较大(300多M)
- 若你想减小体积,仅用基础功能,也可以选 “core” 版本(只含运行时和标准库)——然后手动引入其他包。
离线包解压后能看到
pyodide.js / pyodide.wasm / python_stdlib.zip
(或pyodide_py.tar
等)及若干.data/.json
文件 创建项目目录结构。在任意空文件夹(例:
my-explorer-extension/
)内创建如下结构:my-explorer-extension/
├─ manifest.json
├─ popup.html
├─ popup.js
├─ py/
│ └─ hello.py
└─ pyodide/ # ← 把 Pyodide 离线包整个丢进来
├─ pyodide.js
├─ pyodide.wasm
├─ python_stdlib.zip
├─ ...若干 .data/.json核心文件内容
manifest.json
(Chromium MV3){
"manifest_version": 3,
"name": "Python Hello (Pyodide)",
"version": "1.0.0",
"action": {
"default_title": "Python Hello",
"default_popup": "popup.html"
},
"icons": { "128": "icon128.png" },
"web_accessible_resources": [
{ "resources": ["pyodide/**", "py/hello.py"], "matches": ["<all_urls>"] }
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none';"
}
}
不得加载远程脚本(CDN)。Pyodide 必须随扩展打包。
WASM 权限:
'wasm-unsafe-eval'
让扩展页能加载 WebAssembly。web_accessible_resources
允许在扩展页面读取pyodide/**
和py/hello.py
。
popup.html
点击扩展后的弹出页面 HTML<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Python Hello</title>
<style>
body { font: 14px/1.4 system-ui, sans-serif; padding: 12px; width: 360px; }
button { padding: 8px 12px; border-radius: 8px; border: 1px solid #ddd; }
#status { margin: 8px 0; color: #666; }
pre { background: #f6f6f6; padding: 10px; border-radius: 8px; min-height: 80px; }
</style>
</head>
<body>
<h3>Run Python in a Chrome Extension</h3>
<div id="status">Loading Python runtime…</div>
<button id="run" disabled>Run hello.py</button>
<pre id="out">[output will appear here]</pre>
<script src="popup.js"></script>
</body>
</html>popup.js
let pyodide;
const statusEl = document.getElementById("status");
const outEl = document.getElementById("out");
const runBtn = document.getElementById("run");
(async () => {
try {
// 1) 从扩展内部动态导入 pyodide.js
const url = chrome.runtime.getURL("pyodide/pyodide.js");
await import(url);
// 2) 初始化 Pyodide;indexURL 指向扩展内的 pyodide 目录
pyodide = await loadPyodide({
indexURL: chrome.runtime.getURL("pyodide/")
});
statusEl.textContent = "Python ready ✅";
runBtn.disabled = false;
} catch (e) {
statusEl.textContent = "Failed to load Python runtime.";
console.error(e);
}
})();
runBtn.addEventListener("click", async () => {
runBtn.disabled = true;
outEl.textContent = "Running hello.py…";
try {
// 3) 读取扩展内的 hello.py
const helloUrl = chrome.runtime.getURL("py/hello.py");
const code = await (await fetch(helloUrl)).text();
// 4) 捕获 stdout
await pyodide.runPythonAsync(`
import sys
class _Cap:
def __init__(self): self.buf=[]
def write(self, s): self.buf.append(s)
def flush(self): pass
old_stdout = sys.stdout
cap = _Cap()
sys.stdout = cap
`);
// 5) 执行 python 源码
await pyodide.runPythonAsync(code);
// 6) 取回缓冲
const output = await pyodide.runPythonAsync("''.join(cap.buf)");
outEl.textContent = output;
// 7) 复位 stdout(可选)
await pyodide.runPythonAsync("sys.stdout = old_stdout");
} catch (e) {
outEl.textContent = "Error: " + e.message;
console.error(e);
} finally {
runBtn.disabled = false;
}
});py/hello.py
Python 文件,本示例以输出内容为例print("hello world from Python inside a browser extension!")
在本地加载与试跑(Chromium)
- 打开
chrome://extensions
- 右上角打开 开发者模式
- 选择 加载未打包的扩展程序 ,在弹出窗口中选择示例程序目录,本示例为
my-explorer-extension/
- 正常加载扩展程序后,会在 我的扩展程序 中看到
Python Hello (Pyodide) 1.0.0
(manifest.json
文件中定义的扩展程序名称和版本),点击浏览器的 扩展 ,下拉列表中也会看到新加的扩展程序。
- 在扩展中选择
Python Hello (Pyodide)
执行,会弹出popup.html
页面,并可以点击Run hello.py
运行 Python 程序。
- 打开
常见错误
Manifest is not valid JSON. Can’t read file. Could not load manifest.
在 Windows 系统上的 Chrome 中运行正常,Linux 中 Chromium Version 141.0.7390.54 (Official Build) snap (64-bit) 报错: Error Manifest is not valid JSON. Can't read file. Could not load manifest.
原因 : 不是描述所说的 JSON 格式错误,而是因为 snap 沙箱读不到文件 。snap 版 Chromium 对文件访问有严格沙箱:默认只能读 $HOME
里的内容(比如 ~/Downloads
, ~/Documents
)。如果扩展目录在 可移动磁盘 或 Windows 分区挂载(例如 /media/xxx/…
、 /mnt/d/…
) ,就会出现 “Can’t read file”
。 可以把扩展整个文件夹搬到家目录中尝试