# 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:


### 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
```

then run
```bash
./dist/chatroom/chatroom
```

### 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:

#### 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`:

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
```


```
reflex run
```

```
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
```

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
```

### 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


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

## 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 -->