Skip to content

Instantly share code, notes, and snippets.

@ahopkins
Last active June 2, 2022 23:04
Show Gist options
  • Save ahopkins/b37a458cd37c500fceb62fd22ad6d1d3 to your computer and use it in GitHub Desktop.
Save ahopkins/b37a458cd37c500fceb62fd22ad6d1d3 to your computer and use it in GitHub Desktop.
Sanic as Svelte development server

Sanic as Svelte development server

The purpose of this gist is to show how Sanic can be used as a development server for a frontend JS framework. In this example we are running a Svelte app with rollup, but the same idea could be applied to any other frameworks JS build tools.

Run with:

sanic path.to:app -d -R ./path/to/public
import asyncio
from datetime import datetime
from sanic import Sanic, json, response
from asyncio.subprocess import create_subprocess_shell, PIPE
from pathlib import Path
from sanic.log import logger
import ujson
app = Sanic("MainApp")
app.config.FRONTEND_DIR = Path(__file__).parent / "my-svelte-project"
livereload = Sanic("livereload")
livereload.static("/livereload.js", app.config.FRONTEND_DIR / "livereload.js")
INDEX_HTML = app.config.FRONTEND_DIR / "public" / "index.html"
HELLO = {
"command": "hello",
"protocols": [
"http://livereload.com/protocols/official-7",
],
"serverName": app.name,
}
RELOAD = {"command": "reload", "path": str(INDEX_HTML)}
app.static("/", app.config.FRONTEND_DIR / "public")
@app.get("/")
async def index(_):
return await response.file(INDEX_HTML)
@app.get("/time")
async def time(_):
await asyncio.sleep(1)
return json({"now": datetime.now().isoformat()})
@app.signal("watchdog.file.reload")
async def file_reloaded():
print("...")
@app.before_server_start
async def start(app, _):
app.ctx.livereload_server = await livereload.create_server(
port=35729, return_asyncio_server=True
)
app.add_task(runner(livereload, app.ctx.livereload_server))
@app.before_server_stop
async def stop(app, _):
await app.ctx.livereload_server.close()
@app.before_server_start
async def check_reloads(app, _):
do_rebuild = False
if reloaded := app.config.get("RELOADED_FILES"):
reloaded = reloaded.split(",")
do_rebuild = any(
ext in ("svelte", "js")
for filename in reloaded
if (ext := filename.rsplit(".", 1)[-1])
)
if do_rebuild:
rebuild = await create_subprocess_shell(
"yarn run build",
stdout=PIPE,
stderr=PIPE,
cwd=app.config.FRONTEND_DIR,
)
while True:
message = await rebuild.stdout.readline()
if not message:
break
output = message.decode("ascii").rstrip()
logger.info(f"[reload] {output}")
await app.dispatch("watchdog.file.reload")
@livereload.websocket("/livereload")
async def livereload_handler(request, ws):
global app
logger.info("Connected")
msg = await ws.recv()
logger.info(msg)
await ws.send(ujson.dumps(HELLO))
while True:
await app.event("watchdog.file.reload")
await ws.send(ujson.dumps(RELOAD))
async def runner(app, app_server):
app.is_running = True
try:
app.signalize()
app.finalize()
await app_server.serve_forever()
finally:
app.is_running = False
app.is_stopping = True
@ahopkins
Copy link
Author

ahopkins commented Nov 9, 2021

DISCLOSURE: As of 2021-11-09, the app.config.get("RELOADED_FILES") is not available yet in released or main branch. It is still POC. To obtain this functionality in current Sanic, see what I did here to get livereload working: https://github.com/ahopkins/pywebconf2021-making-sanic-even-faster/blob/main/server.py

Something like this:

from watchgod import awatch

@app.before_server_start
async def start_watch(app, loop):
    app.add_task(watch(app))


async def watch(app):
    async for changes in awatch(app.config.FRONTEND_DIR):
        for change, filename in changes:
            await app.dispatch("watchdog.file.reload")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment