# Joint 11/20-11/27 [TOC] ## 打包 reflex 成 windows exe ### 1. compile reflex as exec file collect necessary files into `/dist` dir ```shell #!/bin/bash -v poetry shell # Build the executable #!/bin/bash -v poetry shell # Build the executable pyinstaller "$(which reflex)" \ --hidden-import reflex \ --hidden-import uvicorn \ --collect-all reflex \ --collect-all uvicorn \ # pyinstaller reflex.spec # Copy the config and assets cp -r chatroom ./dist/reflex/chatroom cp -r assets ./dist/reflex/assets cp -r .venv ./dist/reflex/.venv cp rxconfig.py ./dist/reflex/ # if is in poetry env, exit shell # if [[ "$VIRTUAL_ENV" != "" ]]; then # exit # fi mv ./dist/reflex ./dist/chatroom cd ./dist/chatroom || exit ./reflex run --env prod ``` ```bash ./build-exec.sh ``` result: ![image](https://hackmd.io/_uploads/BJbgyO-H6.png) ![image](https://hackmd.io/_uploads/rJxiRvWra.png) ### 2. directly compile what reflex do in `reflex run` appname.py ```python from reflex.utils import build, console, exec, prerequisites, processes import atexit from pathlib import Path """Run the app in the current directory.""" # Set the log level. # console.set_log_level(loglevel) # Show system info # exec.output_system_info() # If no --frontend-only and no --backend-only, then turn on frontend and backend both frontend = True backend = True frontend_port = 3000 backend_host: str = "0.0.0.0" backend_port = 8000 env = "prod" # if not frontend and backend: # _skip_compile() # Check that the app is initialized. prerequisites.check_initialized(frontend=frontend) # If something is running on the ports, ask the user if they want to kill or change it. if frontend and processes.is_process_on_port(frontend_port): frontend_port = processes.change_or_terminate_port( frontend_port, "frontend") if backend and processes.is_process_on_port(backend_port): backend_port = processes.change_or_terminate_port(backend_port, "backend") console.rule("[bold]Starting Reflex App") if frontend: # Get the app module. prerequisites.get_app() # Warn if schema is not up to date. prerequisites.check_schema_up_to_date() # Get the frontend and backend commands, based on the environment. setup_frontend = frontend_cmd = backend_cmd = None # if env == constants.Env.DEV: # setup_frontend, frontend_cmd, backend_cmd = ( # build.setup_frontend, # exec.run_frontend, # exec.run_backend, # ) # if env == constants.Env.PROD: # setup_frontend, frontend_cmd, backend_cmd = ( # build.setup_frontend_prod, # exec.run_frontend_prod, # exec.run_backend_prod, # ) setup_frontend, frontend_cmd, backend_cmd = ( build.setup_frontend_prod, exec.run_frontend_prod, exec.run_backend_prod, ) assert setup_frontend and frontend_cmd and backend_cmd, "Invalid env" # Post a telemetry event. # telemetry.send(f"run-{env.value}", config.telemetry_enabled) # Display custom message when there is a keyboard interrupt. atexit.register(processes.atexit_handler) # Run the frontend and backend together. commands = [] # Run the frontend on a separate thread. if frontend: setup_frontend(Path.cwd()) commands.append((frontend_cmd, Path.cwd(), frontend_port)) # In prod mode, run the backend on a separate thread. if backend and env == "prod": commands.append((backend_cmd, backend_host, backend_port)) # Start the frontend and backend. with processes.run_concurrently_context(*commands): # In dev mode, run the backend on the main thread. if backend and env == "dev": backend_cmd(backend_host, int(backend_port)) ``` pyinstaller spec (can use `pyinstaller appname.spec` to compile) ```python # -*- mode: python ; coding: utf-8 -*- from PyInstaller.utils.hooks import collect_all datas = [] binaries = [] hiddenimports = ['reflex', 'reflex.utils.*', 'pathlib', 'atexit'] tmp_ret = collect_all('reflex') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('reflex.utils') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('reflex.utils.build') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('reflex.utils.console') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('reflex.utils.exec') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('reflex.utils.prerequisites') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('reflex.utils.processes') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('pathlib') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all('atexit') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] a = Analysis( ['chatroom.py'], pathex=[], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], noarchive=False, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='chatroom', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) coll = COLLECT( exe, a.binaries, a.datas, strip=False, upx=True, upx_exclude=[], name='chatroom', ) ``` then compile ```bash $ pyinstaller apppname.spec ``` ![image](https://hackmd.io/_uploads/SJ3NguZHa.png) then run ```bash ./dist/chatroom/chatroom ``` ![image](https://hackmd.io/_uploads/S1JxxObB6.png) ### 3. static-file-only desktop APP (frontend only) ```bash reflex export ``` - [electron static file](https://github.com/thmsbfft/electron-wrap) ### 4. possible comb 1. all docker (need browser to show frontend) 2. electron desktop APP(frontend) + docker(backend) 3. electron desktop APP(frontend) + native python(backend) #### venv usage 1. run `.venv/bin/activate`, then your shell would have those 2. then ### 5. nsis https://zh.wikipedia.org/zh-tw/Nullsoft%E8%85%B3%E6%9C%AC%E5%AE%89%E8%A3%9D%E7%B3%BB%E7%B5%B1 ## Pack reflex => electron + python ### Poetry on windows #### Install pipx ```shell py -m pip install --user pipx py -m pipx ensurepath exit ``` #### Install Microsoft C++ Build Tools (greater than v14) go to: https://visualstudio.microsoft.com/visual-cpp-build-tools/ and install `Microsoft C++ Build Tools` or `wget https://aka.ms/vs/17/release/vs_BuildTools.exe` int the vs installer install these individual component: ![image](https://hackmd.io/_uploads/ByL9q89Sp.png) #### Install poetry ```shell! pipx install poetry ``` re-login or reopen a shell, then you can access `poetry` ``` poetry config virtualenvs.in-project true ``` if you encounter some error on executing `poetry shell`: ![image](https://hackmd.io/_uploads/HyPMyvqra.png) then checkout this answer: [allow all local scripts to execute on the VM](https://stackoverflow.com/a/54776674) #### Reflex setup ``` python -m venv ./.venv pip reflex ./.venv/Script/activate reflex init reflex db init reflex db migrate ``` ![image](https://hackmd.io/_uploads/r13lkC9ST.png) ![image](https://hackmd.io/_uploads/SkI4JCcHp.png) ``` reflex run ``` ![image](https://hackmd.io/_uploads/HJiCkRqB6.png) ``` reflex export ``` ``` mkdir frontend tar -xf .\frontend.zip -C frontend cd frontend ``` ### Install NodeJS ```shell wget https://github.com/coreybutler/nvm-windows/releases/download/1.1.12/nvm-setup.exe ./nvm-setup.exe nvm install lts nvm use lts ``` ### Start to setup electron 1. Enter the Frontend Static File Directory ```bash cd frontend ``` 2. Initialize a new Node.js project: ```bash npm init -y ``` 3. Install Electron locally as a development dependency: ```bash npm install electron --save-dev ``` 4. Modify `package.json` to define the entry point and add a start script and those necessary packages: ```json { "name": "frontend", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "start": "electron .", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "concurrently": "^8.2.2", "electron": "^27.1.3", "electron-builder": "^24.9.1", "electron-packager": "^17.1.2" }, "dependencies": { "electron-serve": "^1.2.0", "express": "^4.18.2" } } ``` 5. Set Up `main.js`: In `main.js`, you'll need to create a browser window and load `index.html` file: ```javascript const { app, BrowserWindow } = require('electron'); const express = require('express'); const path = require('path'); const nextApp = express(); const port = 3000; nextApp.use(express.static(path.join(__dirname, './frontend'))); nextApp.listen(port, () => { console.log(`Next.js server running on http://localhost:${port}`); }); function createWindow() { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, contextIsolation: false } }); win.loadURL(`http://localhost:${port}`); // win.webContents.openDevTools(); } app.whenReady().then(createWindow); ``` 5. **Start the Application**: Run the application using npm. ```bash npm start ``` ![image](https://hackmd.io/_uploads/rJcC1JjBp.png) 6. Install Electron Packager as a dev dependency ```bash npm install electron-packager --save-dev ``` 7. Use Electron Packager to create a Windows executable ```bash npx electron-packager . reflex-electron-app --platform=win32 --arch=x64 ``` 8. execute the electron windows executable ```bash cd .\reflex-electron-app-win32-x64\ ls ./reflex-electron-app.exe ``` ![image](https://hackmd.io/_uploads/BJeezkoH6.png) ### Start to Setup backend (require to apply on deployee) ``` cd backend cp ..\.venv\ . -R # copy venv file which generate by poetry .\.venv\Scripts\activate reflex init reflex db init reflex db migrate reflex db makemigrations reflex run ``` ## on a new x64 win11 machine ### frontend open the executable file directly ### backend ```shell Set-ExecutionPolicy Unrestricted -Scope CurrentUser .\.venv\Scripts\activate ``` cant use poetry ![image](https://hackmd.io/_uploads/r1e-1it2Ua.png) ![image](https://hackmd.io/_uploads/r1fsrq3IT.png) 1. pack portable python+pip - remember uncomment last line in `pythonxx.__pth` ```shell # Define the URL and the destination file name $url = "https://www.python.org/ftp/python/3.11.7/python-3.11.7-embed-amd64.zip" $output = "python-3.11.7-embed-amd64.zip" # Download Python 3.11.7 Invoke-WebRequest -Uri $url -OutFile $output # Unzip the downloaded file Expand-Archive -LiteralPath $output -DestinationPath "Python" -Force Remove-Item -Path $output # Install pip curl https://bootstrap.pypa.io/get-pip.py -O get-pip.py ./Python/python.exe get-pip.py ``` 3. install reflex ``` ./Python/Script/pip install reflex==0.3.4 ``` 4. run reflex ``` .\Python\Scripts\reflex.exe run --backend-only --env dev ``` 5. solve python exec path issue after transfer ![image](https://hackmd.io/_uploads/ryUVbpn8p.png) ## TODO - [x] electron desktop APP(frontend) + native python(backend) - [x] electron - [x] python run reflex backend - [ ] create a new clean windows env <!-- - [ ] write a script to automate all things -->