前端开发笔记[3]-electron嵌入jupyterlite

发布时间 2023-11-04 16:26:17作者: qsBye

摘要

在electron应用中嵌入jupyterlite,实现python程序和运行环境的跨平台打包.

jupyterlite简介

[https://jupyter.org/try-jupyter/lab/]
[https://zhuanlan.zhihu.com/p/578412030]
[https://jupyterlite.readthedocs.io/en/latest/_static/lab/index.html]
JupyterLite的用户界面和JupyterLab完全相同,都是在浏览器中运行。与JupyterLab不同的是,在JupyterLite中,Python程序也是在浏览器中运行的。因此只需要静态的Web服务器,就可以搭建JupyterLite的开发环境,Python的解释器以及所有的扩展库都是从网络上下载到浏览器中运行。

emscripten/webassembly简介

[https://emscripten.org/docs/compiling/WebAssembly.html]
WebAssembly is a binary format for executing code on the web, allowing fast start times (smaller download and much faster parsing in browsers when compared to JS or asm.js). Emscripten compiles to WebAssembly by default, but you can also compile to JS for older browsers.
Emscripten是一个工具集,它可以将C/C++代码编译成WebAssembly字节码。WebAssembly是一种新的低级二进制格式,可以在现代浏览器中运行。与JavaScript相比,WebAssembly具有更高的性能和更低的内存占用。Emscripten使用LLVM编译器将C/C++代码转换为WebAssembly字节码,并生成与Web技术交互的JavaScript胶水代码。通过Emscripten,开发人员可以使用C/C++等语言来开发Web应用,利用WebAssembly的优势来提升性能和用户体验[3][5]。

WebAssembly可以在所有主流浏览器中运行,包括Chrome、Firefox、Edge和Safari。它可以将C/C++、Rust等语言编写的代码编译成字节码格式,然后在浏览器中运行。WebAssembly可以通过JavaScript的API调用,也可以在WebAssembly模块之间进行通信。它可以在需要大量CPU密集运算的场景下弥补JavaScript的性能不足,为Web开发生态打开了新的一扇窗[1][3]。

Emscripten是WebAssembly工具链的重要组成部分。它可以将C/C++代码编译为ASM.js和WebAssembly代码,并帮助生成所需的JavaScript胶水代码。Emscripten与LLVM工具链相当接近,它包含了各种开发所需的C/C++头文件、宏参数和相关命令行工具。通过这些头文件和宏参数,Emscripten可以为源代码提供合适的编译流程,并完成数据转换[5]。

总结一下,Emscripten是一个将C/C++代码转换为WebAssembly的工具集,它可以帮助开发人员在Web上运行高性能的C/C++代码,提升Web应用的性能和用户体验[3][5]。

pyodide简介

[https://pyodide.org/]
Pyodide is a Python distribution for the browser and Node.js based on WebAssembly.
Pyodide is a port of CPython to WebAssembly/Emscripten.

Pyodide makes it possible to install and run Python packages in the browser with micropip. Any pure Python package with a wheel available on PyPI is supported. Many packages with C extensions have also been ported for use with Pyodide. These include many general-purpose packages such as regex, pyyaml, lxml and scientific Python packages including numpy, pandas, scipy, matplotlib, and scikit-learn.

Pyodide comes with a robust Javascript ⟺ Python foreign function interface so that you can freely mix these two languages in your code with minimal friction. This includes full support for error handling (throw an error in one language, catch it in the other), async/await, and much more.

When used inside a browser, Python has full access to the Web APIs.

Pyodide是一个基于WebAssembly和Emscripten的项目,它将完整的Python数据科学堆栈引入到浏览器中运行。Pyodide的目标是在浏览器中提供完整的Python环境,包括Python解释器、NumPy、Pandas、Matplotlib、SciPy等常用的Python科学计算库。

Pyodide使用WebAssembly将CPython解释器编译为浏览器可运行的格式,并提供了一个JavaScript胶水库,使得在浏览器中可以直接调用Python代码。它可以在任何需要在Web浏览器中运行Python并具有对Web API的完全访问权限的上下文中使用。

Pyodide的应用场景包括在Web前端中调用Python库文件或方法,进行数据科学计算和可视化,以及在浏览器中进行科学实验和交互式计算。通过Pyodide,开发人员可以在浏览器中运行Python代码,而无需依赖服务器或本地Python环境。

总结一下,Pyodide是一个将完整的Python数据科学堆栈引入到浏览器中运行的项目,它使用WebAssembly和Emscripten技术,提供了一个完整的Python环境,可以在浏览器中进行数据科学计算和可视化。

ws/wss:websocket简介

[https://www.cnblogs.com/orange-CC/p/14736364.html]
jupyterlite需要用到websocket
WebSocket是一种在Web应用程序中实现双向通信的协议。它允许浏览器和服务器之间建立持久性的连接,实现实时数据传输和交互。

WebSocket协议与传统的HTTP协议不同,HTTP协议是一种无状态的请求-响应协议,每次请求都需要建立新的连接。而WebSocket协议通过在客户端和服务器之间建立一条持久性的双向通信通道,可以实现服务器主动向客户端推送数据,而不需要客户端不断发送请求。

WebSocket协议使用ws://或wss://作为统一资源标识符(URI),其中ws://表示非安全的WebSocket连接,而wss://表示在TLS(Transport Layer Security)之上的安全WebSocket连接。类似于HTTP和HTTPS之间的关系,非安全的WebSocket连接不需要SSL证书,而安全的WebSocket连接需要使用SSL证书来保证通信的安全性。

WebSocket协议的特点包括:

  • 建立在TCP协议之上,实现容易;
  • 与HTTP协议兼容,可以通过HTTP代理服务器;
  • 支持双向通信,实现实时数据传输;
  • 可以发送文本和二进制数据;
  • 不限制同源策略,可以跨域通信。

总结一下,WebSocket是一种在Web应用程序中实现双向通信的协议,它通过建立持久性的连接,实现实时数据传输和交互。ws://表示非安全的WebSocket连接,wss://表示在TLS之上的安全WebSocket连接。WebSocket协议具有与HTTP协议兼容、双向通信、实时数据传输等特点。

npm:express包简介

  • 使用express内嵌一个服务器到electron应用中以运行jupyterlite
    express是一个流行的Node.js Web应用程序框架,它可以帮助开发者快速构建可靠和可扩展的Web应用程序。它是通过npm(Node.js包管理器)进行安装和管理的。

以下是对express包的一些简要介绍:

  • express提供了一组简单而强大的功能,包括路由、中间件、模板引擎等,使开发Web应用程序变得更加容易。
  • 使用express可以轻松处理HTTP请求和响应,创建路由和处理程序,以及管理会话和身份验证等功能。
  • express还支持各种插件和中间件,可以扩展其功能,例如处理表单数据、处理JSON数据、处理文件上传等。

实现

  1. 安装express和jupyterlite
npm install express
# jupyterlite的打包仍然需要利用宿主机的python环境
pip install rye
rye init jupyterlite-python
cd jupyterlite-python
rye add jupyterlite
rye add jupyterlab
rye add jupyterlite-pyodide-kernel
rye add jupyterlite-core
rye sync
# 激活虚拟环境
conda deactivate
. .venv/bin/activate
# 测试
jupyter lite serve
# 打包
jupyter lite build --pyodide https://github.com/pyodide/pyodide/releases/download/0.22.1/pyodide-0.22.1.tar.bz2

将ipynb文件放置到{工程目录}/files,可以被jupyterlite打包到_output/files目录,可以在网页中访问到这些文件;

# 打包jupyterlite
jupyter lite build
  1. 运行express网页服务器,嵌入iframe
    将上一步的output文件夹复制到electron工程的static文件夹,然后在启动electron时顺便运行express服务器:
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron');
const path = require('path');

console.log('欢迎来到 Electron ?');
function createWindow () {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 渲染时的操作
      preload: path.join(__dirname, 'preload.js'),
      // 允许加载nodejs模块
      nodeInteration: true, //允许渲染进程使用node.js
      contextIsolation:false,  //允许渲染进程使用node.js
    },

  })

  // and load the index.html of the app.
  mainWindow.loadFile('index.html')
  // Open the DevTools.
   //mainWindow.webContents.openDevTools();
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
  // 启动界面
  createWindow();

  app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
  //if (process.platform !== 'darwin') app.quit();
  app.quit();
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
/* start 网页服务器相关 */
const { spawn } = require('child_process');
const express = require('express');
//const express = require(path.join(__dirname, './static/express/index.js'));
const express_app = express();
const port = 8888;
const rootDir = path.join(__dirname, 'jupyterlite-python/_output');

express_app.use(express.static(rootDir));

// 启动express网页服务器:jupyterlite
express_app.listen(port, () => {
  console.log(`Web server running at http://localhost:${port}`);
});
/* end 网页服务器相关 */

将jupyterlite页面嵌入electron页面:

<!-- lab页面功能最多 -->
<iframe
src="http://localhost:8888/lab/index.html?kernel=python&toolbar=1&file=/losslessCompression.ipynb"
height="600px"
class="mdui-center mdui-col-sm-12"
></iframe>

jupyterlite中的测试程序:

# 导入包
import pyodide_js
await pyodide_js.loadPackage('numpy')

import numpy
print(numpy.__version__)
  1. 打包
# node_modules文件夹不能忽略,否则electron将会找不到express库
electron-packager . DuckDuckImagination --win --out ./output --arch=arm64 --electron-version=26.0.0 --icon=./static/duck.icns --app-version=0.2 --overwrite --ignore=yarn --platform=darwin

效果

附录:pyodide的wheel包下载地址

  • none-any架构适用于浏览器环境
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/micropip-0.4.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/packaging-23.1-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyodide.js
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyodide.asm.wasm
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/python_stdlib.zip
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyodide.asm.js
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyodide-lock.json
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/ssl-1.0.0.zip
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/openssl-1.1.1n.zip
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/distutils-1.0.0.zip
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/setuptools-68.1.2-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyparsing-3.1.1-py3-none-any.whl
https://files.pythonhosted.org/packages/bf/25/2d88e8feee8e055d015343f9b86e370a1ccbec546f2865c98397aaef24af/markdown_it_py-2.2.0-py3-none-any.whl
https://files.pythonhosted.org/packages/1f/1a/16b0d2f66601ba3081f1d4177087c79fd1f11d17706ee01d373e4ba8e00d/linkify_it_py-2.0.2-py3-none-any.whl
https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl
https://files.pythonhosted.org/packages/d1/1c/5aeb94aa980da111e4fd0c0fbe5ad95ed5bf9bd957f8e2a6178b85ff4da8/uc_micro_py-1.0.2-py3-none-any.whl
https://files.pythonhosted.org/packages/19/24/44299477fe7dcc9cb58d0a57d5a7588d6af2ff403fdd2d47a246c91a3246/anyio-3.7.1-py3-none-any.whl
https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/idna-3.4-py3-none-any.whl
https://files.pythonhosted.org/packages/4d/d2/3ad038a2365fefbac19d9a046cab7ce45f4c7bfa81d877cbece9707de9ce/fastapi-0.103.2-py3-none-any.whl
https://files.pythonhosted.org/packages/58/f8/e2cca22387965584a409795913b774235752be4176d276714e15e1a58884/starlette-0.27.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/typing_extensions-4.7.1-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pydantic-1.10.7-py3-none-any.whl
http://127.0.0.1:5501/exp10/exp10-1-5/static/gradio_lite/dist/assets/gradio-3.50.2-py3-none-any.whl
http://127.0.0.1:5501/exp10/exp10-1-5/static/gradio_lite/dist/assets/gradio_client-0.6.1-py3-none-any.whl
https://files.pythonhosted.org/packages/b4/ff/b1e11d8bffb5e0e1b6d27f402eeedbeb9be6df2cdbc09356a1ae49806dbf/python_multipart-0.0.6-py3-none-any.whl
https://files.pythonhosted.org/packages/c5/19/5af6804c4cc0fed83f47bff6e413a98a36618e7d40185cd36e69737f3b0e/aiofiles-23.2.1-py3-none-any.whl
https://files.pythonhosted.org/packages/65/6e/09d8816b5cb7a4006ef8ad1717a2703ad9f331dae9717d9f22488a2d6469/importlib_resources-6.1.0-py3-none-any.whl
https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl
https://files.pythonhosted.org/packages/82/61/a5fca4a1e88e40969bbd0cf0d981f3aa76d5057db160b94f49603fc18740/httpx-0.25.1-py3-none-any.whl
https://files.pythonhosted.org/packages/47/96/9d5749106ff57629b54360664ae7eb9afd8302fad1680ead385383e33746/websockets-11.0.3-py3-none-any.whl
https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl
https://files.pythonhosted.org/packages/ef/b5/b6107bd65fa4c96fdf00e4733e2fe5729bb9e5e09997f63074bb43d3ab28/huggingface_hub-0.18.0-py3-none-any.whl
https://files.pythonhosted.org/packages/17/16/b12fca347ff9d062e3c44ad9641d2ec50364570a059f3078ada3a5119d7a/altair-5.1.2-py3-none-any.whl
https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl
https://files.pythonhosted.org/packages/79/96/b0882a1c3f7ef3dd86879e041212ae5b62b4bd352320889231cc735a8e8f/uvicorn-0.23.2-py3-none-any.whl
https://files.pythonhosted.org/packages/7c/bd/8f4e676af570d8990e02e3f4cefba7c0c506f2b2ce63f086e0cb939b6e1e/httpcore-1.0.1-py3-none-any.whl
https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl
https://files.pythonhosted.org/packages/d2/b2/b157855192a68541a91ba7b2bbcb91f1b4faa51f8bae38d8005c034be524/urllib3-2.0.7-py3-none-any.whl
https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl
https://files.pythonhosted.org/packages/81/54/84d42a0bee35edba99dee7b59a8d4970eccdd44b99fe728ed912106fc781/filelock-3.13.1-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/numpy-1.25.2-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pandas-1.5.3-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/python_dateutil-2.8.2-py2.py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/six-1.16.0-py2.py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pytz-2023.3-py2.py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/MarkupSafe-2.1.3-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/PyYAML-6.0.1-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/Pillow-10.0.0-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/matplotlib-3.5.2-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/cycler-0.11.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/fonttools-4.42.1-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/kiwisolver-1.4.4-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/matplotlib_pyodide-0.2.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/orjson-3.9.2-cp311-cp311-emscripten_3_1_45_wasm32.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/Jinja2-3.1.2-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/fsspec-2023.6.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/certifi-2023.7.22-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/click-8.1.7-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/tqdm-4.66.1-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/toolz-0.12.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/jsonschema-4.17.3-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/attrs-23.1.0-py3-none-any.whl
https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyrsistent-0.19.3-cp311-cp311-emscripten_3_1_45_wasm32.whl