diff --git a/README.md b/README.md index a445cc84..e751e62f 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ As of Eel v0.12.0, the following options are available to `start()`: - **close_callback**, a lambda or function that is called when a websocket to a window closes (i.e. when the user closes the window). It should take two arguments; a string which is the relative path of the page that just closed, and a list of other websockets that are still open. *Default: `None`* - **app**, an instance of Bottle which will be used rather than creating a fresh one. This can be used to install middleware on the instance before starting eel, e.g. for session management, authentication, etc. + - **reload_python_on_change**, a boolean that enables Bottle server reloading when Python file changes are detected. Using this option may make local development of your application easier. An explicit port must be set when using this option to ensure that the Eel can effectively reconnect to the updated server. diff --git a/eel/__init__.py b/eel/__init__.py index 2f28ce89..570075eb 100644 --- a/eel/__init__.py +++ b/eel/__init__.py @@ -50,6 +50,7 @@ 'disable_cache': True, # Sets the no-store response header when serving assets 'default_path': 'index.html', # The default file to retrieve for the root URL 'app': btl.default_app(), # Allows passing in a custom Bottle instance, e.g. with middleware + 'reload_python_on_change': False, # Start bottle server in reloader mode for easier development } # == Temporary (suppressable) error message to inform users of breaking API change for v1.0.0 === @@ -128,6 +129,12 @@ def start(*start_urls, **kwargs): else: raise RuntimeError(api_error_message) + if _start_args['reload_python_on_change'] and _start_args['port'] == 0: + raise ValueError( + "Eel must be started on a fixed port in order to reload Python code on file changes. " + "For example, to start on port 8000, add `port=8000` to the `eel.start` call." + ) + if _start_args['port'] == 0: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost', 0)) @@ -140,9 +147,9 @@ def start(*start_urls, **kwargs): _start_args['jinja_env'] = Environment(loader=FileSystemLoader(templates_path), autoescape=select_autoescape(['html', 'xml'])) - # Launch the browser to the starting URLs - show(*start_urls) + if not _start_args['reload_python_on_change'] or not os.environ.get('BOTTLE_CHILD'): + show(*start_urls) def run_lambda(): if _start_args['all_interfaces'] == True: @@ -160,7 +167,8 @@ def run_lambda(): port=_start_args['port'], server=wbs.GeventWebSocketServer, quiet=True, - app=app) + app=app, + reloader=_start_args['reload_python_on_change']) # Start the webserver if _start_args['block']: diff --git a/eel/eel.js b/eel/eel.js index cc824206..f40385ed 100644 --- a/eel/eel.js +++ b/eel/eel.js @@ -103,61 +103,85 @@ eel = { } }, - _init: function() { - eel._mock_py_functions(); + _connect: function() { + let page = window.location.pathname.substring(1); + eel._position_window(page); - document.addEventListener("DOMContentLoaded", function(event) { - let page = window.location.pathname.substring(1); - eel._position_window(page); + let websocket_addr = (eel._host + '/eel').replace('http', 'ws'); + websocket_addr += ('?page=' + page); - let websocket_addr = (eel._host + '/eel').replace('http', 'ws'); - websocket_addr += ('?page=' + page); - eel._websocket = new WebSocket(websocket_addr); + eel._websocket = new WebSocket(websocket_addr); - eel._websocket.onopen = function() { - for(let i = 0; i < eel._py_functions.length; i++){ - let py_function = eel._py_functions[i]; - eel._import_py_function(py_function); - } + eel._websocket.onopen = function() { + for(let i = 0; i < eel._py_functions.length; i++){ + let py_function = eel._py_functions[i]; + eel._import_py_function(py_function); + } - while(eel._mock_queue.length > 0) { - let call = eel._mock_queue.shift(); - eel._websocket.send(eel._toJSON(call)); + while(eel._mock_queue.length > 0) { + let call = eel._mock_queue.shift(); + eel._websocket.send(eel._toJSON(call)); + } + }; + + eel._websocket.onmessage = function (e) { + let message = JSON.parse(e.data); + if(message.hasOwnProperty('call') ) { + // Python making a function call into us + if(message.name in eel._exposed_functions) { + let return_val = eel._exposed_functions[message.name](...message.args); + eel._websocket.send(eel._toJSON({'return': message.call, 'value': return_val})); } }; eel._websocket.onmessage = function (e) { let message = JSON.parse(e.data); - if(message.hasOwnProperty('call') ) { + if (message.hasOwnProperty('call')) { // Python making a function call into us - if(message.name in eel._exposed_functions) { + if (message.name in eel._exposed_functions) { try { let return_val = eel._exposed_functions[message.name](...message.args); - eel._websocket.send(eel._toJSON({'return': message.call, 'status':'ok', 'value': return_val})); - } catch(err) { + eel._websocket.send(eel._toJSON({ + 'return': message.call, + 'status': 'ok', + 'value': return_val + })); + } catch (err) { debugger eel._websocket.send(eel._toJSON( - {'return': message.call, - 'status':'error', - 'error': err.message, - 'stack': err.stack})); + { + 'return': message.call, + 'status': 'error', + 'error': err.message, + 'stack': err.stack + })); } } - } else if(message.hasOwnProperty('return')) { + } else if (message.hasOwnProperty('return')) { // Python returning a value to us - if(message['return'] in eel._call_return_callbacks) { - if(message['status']==='ok'){ + if (message['return'] in eel._call_return_callbacks) { + if (message['status'] === 'ok') { eel._call_return_callbacks[message['return']].resolve(message.value); - } - else if(message['status']==='error' && eel._call_return_callbacks[message['return']].reject) { - eel._call_return_callbacks[message['return']].reject(message['error']); + } else if (message['status'] === 'error' && eel._call_return_callbacks[message['return']].reject) { + eel._call_return_callbacks[message['return']].reject(message['error']); } } } else { throw 'Invalid message ' + message; } + } + }; - }; + eel._websocket.onclose = function (e) { + setTimeout(eel._connect, 200) + }; + }, + + _init: function() { + eel._mock_py_functions(); + + document.addEventListener("DOMContentLoaded", function(event) { + eel._connect(); }); } }; diff --git a/examples/10 - reload_code/reloader.py b/examples/10 - reload_code/reloader.py new file mode 100644 index 00000000..06f32c86 --- /dev/null +++ b/examples/10 - reload_code/reloader.py @@ -0,0 +1,11 @@ +import eel + +eel.init("web") + + +@eel.expose +def updating_message(): + return "Change this message in `reloader.py` and see it available in the browser after a few seconds/clicks." + + +eel.start("reloader.html", size=(320, 120), reload_python_on_change=True) diff --git a/examples/10 - reload_code/web/favicon.ico b/examples/10 - reload_code/web/favicon.ico new file mode 100644 index 00000000..c9efc584 Binary files /dev/null and b/examples/10 - reload_code/web/favicon.ico differ diff --git a/examples/10 - reload_code/web/reloader.html b/examples/10 - reload_code/web/reloader.html new file mode 100644 index 00000000..bd36758b --- /dev/null +++ b/examples/10 - reload_code/web/reloader.html @@ -0,0 +1,25 @@ + + +
+