Using Electron as front end
I mainly use Eel to make prototypes or personal/internal tools, where polish is not that important, but if you want to make a more professional looking app with HTML/JS frontend, and Python backend, you can use Electron as the browser. This will give you total control over things like right click menus, menubars etc.
I have added a new custom mode which just runs whatever command you provide in args. In the future I might add more "first class" support for Electron.
The absolute smallest possible Electron app needs two files, a package.json file that looks like...
{
"main": "main.js",
"devDependencies": {
"electron": "^2.0.0"
}
}
and a main.js that looks something like...
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow.loadURL('http://localhost:8000/start_page.html');
mainWindow.on('closed', function () {
mainWindow = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', function () {
app.quit()
});
app.on('activate', function () {
if (mainWindow === null) {
createWindow()
}
})
Then, in your python script, you need to specify options something like...
options = {
'mode': 'custom',
'args': ['/usr/local/bin/electron', '.'],
(...)
}
eel.init('web')
eel.start('start_page.html', size=(800, 600), options=options)
Thanks for this work - I'll check it out.
My recent software product is now complete. It definately needed menus, a standalone build of chrome and other electron features so I had no choice but to jump ship and use https://github.com/fyears/electron-python-example. But I'll re-check out the feasibility of eel with electron for my next project.
@ChrisKnott So I went ahead and created my own electron app (without eel). Then, I put gui.py in that folder:
import eel
options = {
'mode': 'custom',
'args': ['C:/Users/x/PycharmProjects/adidas/gui']
}
eel.init('html')
eel.start('index.html', options=options)
Here is my main.js:
const {app, BrowserWindow} = require('electron')
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({width: 1206, height: 729, icon:'images/favicon-32x32.png', resizable: false, title: ""})
mainWindow.setMenu(null);
mainWindow.loadFile('http://localhost:8000/index.html')
mainWindow.on('closed', function () {
mainWindow = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if (mainWindow === null) {
createWindow()
}
})
The Electron app works fine when I run it via npm run. However, when I try and run gui.py I get the following error:
Traceback (most recent call last): File "C:/Users/x/PycharmProjects/adidas/gui.py", line 9, in
eel.start('index.html', options=options) File "C:\Users\x\AppData\Local\Programs\Python\Python36-32\lib\site-packages\eel_init_.py", line 112, in start brw.open(start_urls, options) File "C:\Users\x\AppData\Local\Programs\Python\Python36-32\lib\site-packages\eel\browsers.py", line 39, in open stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE) File "C:\Users\x\AppData\Local\Programs\Python\Python36-32\lib\site-packages\gevent\subprocess.py", line 604, in init restore_signals, start_new_session) File "C:\Users\x\AppData\Local\Programs\Python\Python36-32\lib\site-packages\gevent\subprocess.py", line 955, in _execute_child startupinfo) PermissionError: [WinError 5] Access is denied
This is my file structure within the gui folder:
css
global.css
html
index.html
node_modules gui.py main.js package.json package-lock.json renderer.js
Any ideas?
args should point to the electron executable. When you run npm start, what does it say in the command prompt as it starts up? Should be something like electron.exe .
@ChrisKnott OK so I found the electron exe file.
Now my gui.py file looks like this:
import eel
options = {
'mode': 'custom',
'args': ['C:/Users/x/PycharmProjects/adidas/gui/node_modules/electron/dist/electron.exe', '.']
}
eel.init('html')
eel.start('index.html', options=options)
When I run this, it opens an electron browser but it is just blank. When I go to localhost:8000 on my browser, I can see my page but there is no CSS being applied to it...
What am I missing here?
EDIT: When I view source on localhost:8000/index.html and click on the link to my css file (../css/global.css), I get a 404 error. Why is this not showing up?
EDIT2: Changed loadFile to loadURL in main.js. Now it opens the page but again without styling. How can I add all of my folders to localhost?
Also, closing the app doesn't end the python script.
Fixed. I put the html, css, js and images folders in a folder called web, then I did eel.init('web) and eel.start('html/index.html').
Works great now except for the issue with the python script still running.
To close the Python script just call into it from Electron (in the close handler) then call sys.exit() on the Python side
@ChrisKnott I'll test that in a bit bit right now I'm having another problem. I added a custom title bar and close/minimise buttons which works absolutely fine when I run it via npm start. However, when I run it via eel it shows an old version of my project and nothing will update. I even tried reverting it back in case my title bar code was causing an issue and just changing one letter of an element, but it still shows the old version and nothing will change. I tried restarting my PC but the issue persists.
What's going on here?
Hmmm not sure really, perhaps you have two copies of the file?
@ChrisKnott I tried it a few hours after and it just worked... Strange.
Also I got the closing to work properly, thanks.
I even made a custom title bar in electron which looks great.
It seems like the issue was caused by electron's caching which can be cleared by going to C:\Users<user>\AppData\Roaming<project>\Cache and deleting the files.
@ChrisKnott Just wanted to say thanks for adding this capability. It's working really well for me!
Thanks Neal I appreciate it
@ChrisKnott Hi Chris, thanks so much for this.
1 question - I'm trying to the above suggestion with Electron, however whenever I run my .py script, instead of launching a new window in Electron, it just adds a new tab to my chrome browser. Otherwise, it just opens a new instance of Chrome.
Seems like it's ignoring the fact that I want to launch it using Electron and uses Chrome instead.
@nba94 If it's opening a Chrome tab, then eel must be opening a new instance of Chrome instead of Electron. Do you have 'mode': 'custom' added to the options dictionary?
@ChrisKnott I do. I have the following as options:
options1 = { 'mode': 'custom', 'port': 8000, 'args': ['/usr/local/bin/electron','.'], }
and this at the end:
eel.start('index.html',options=options1)
Yet it still launches Chrome for some reason.
Maybe you are still running the old version of eel...?
@ChrisKnott Yes.. Now it recognizes it! Thanks and sorry...
@ChrisKnott Thanks for your help so far - this is an amazing addition to the module.
I have a question.. When I tried to compile the application using pyinstaller, what is the correct way to include Electron?
Do you include it in datas as a complete .app executable file to be stored within the .app itself? I have tried doing that, but that did not work.
Maybe there is something that I am missing out?
FYI I am on MacOS.
I think the best way is to use --add-data option to include Electron binaries. There is more info on pyinstaller's help https://media.readthedocs.org/pdf/pyinstaller/cross-compiling/pyinstaller.pdf
These will be extracted to a temp folder when the .app is run. You want to use the path of that temp folder in your args list
@ChrisKnott I finally worked it out..
Just in case anyone else will be wondering these are the following steps I took to compile into a single file on MacOS:
-
Download compiled electron from https://github.com/electron/electron/releases (for me it was electron-v2.0.11-darwin-x64.zip)
-
Right click -> open contents of the downloaded Electron, create 'app' folder in the Contents -> Resources and move my main.js, index.html and package.json inside.
-
As Chris mentioned above, --add-data has to be used to include Electron, however for some reason it does not include some of the libraries for Electron to run successfully when compiled, so including Electron and those files as follows:
-
--add-data [include Electron.app that you have downloaded previously]
-
--add-data /Users/[path to downloaded Electron]/Electron.app/Contents/Frameworks/Electron\ Framework.framework/Versions/A:Electron.app/Contents/Frameworks/Electron\ Framework.framework
-
(optional) instead of point 2 it is also possible to just write --add-data for those files required to include inside Electron, following this format: --add-data /Users/[path to your main.js]/main.js:Electron.app/Contents/Resources/app
After making sure these files are included everything ran as expected.
FYI In case anyone is planning to compile using --onefile - on MacOS you have to make sure all of the references to any paths in your script are using relative paths. The following function ensures that the path to any file you use in your script that is saved locally is turned into a relative path:
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
Even options has to be rewritten using the function in order for --onefile compilation to work correctly, as follows:
options1 = {
'mode': 'custom',
'port': 8000,
'args': [resource_path('Electron.app/Contents/MacOS/Electron'),'.'],
}
Sorry for the lengthy post, but maybe this will help someone spend way less time than I have on this!
@nba94 Thanks for the detailed writeup, I'm sure this will help lots of people
anyone can help on how to add eel.js while using electron ?
You now longer have to use 'custom' mode for this, you can say 'electron' in latest version (currently in beta)
You may also want to use;
import eel.browsers
eel.browsers.set_path('electron', 'node_modules/electron/dist/electron')
to force using binary from local folder (this should also help with packaging with pyinstaller).
I have added an example for a minimal electron app. I will improve it when beta is released fully on PyPI.
For now I am going to close this issue as there are too many open atm.
To close the Python script just call into it from Electron (in the close handler) then call sys.exit() on the Python side
Close handler is not in render process ,how to call into python script ? I'm confused
Yes I think it is not as easy as it seems, I will make an example with clean shutdown when I have time, hopefully in w/c 12th August as I have time off work then
Yes I think it is not as easy as it seems, I will make an example with clean shutdown when I have time, hopefully in w/c 12th August as I have time off work then
Any updates to this?
Hi @w6tsang - I've taken over primary maintenance of this project for now, and unfortunately I'm not super familiar with this side of the project. I'll try to look into it when I have time - but in the mean time if anyone else comes across this and has more experience and the willingness to share ways that might work, it would be really appreciated.
I have a project using Electron + Eel 1.0 so I will probably submit fix for this at some point
Any Updates on Electron + Eel ? Please include the integration of Electron with Eel in the Documentation