Here's an idea and initial proof of concept - uWSGI and Flask as an automatic build tool, using uWSGI's filemon
decorator to watch files for changes.
Any file (or directory) modifications trigger a function, minifying CSS, JavaScript, compiling SCSS to CSS, linting, code formatting etc..
The uWSGI server is started in a new terminal and runs in the background, functions are triggered when modifications are made to the desired files.
Scenario
- You're already using Flask and uWSGI in your project
- You want to minify assets, such as CSS & JavaScript
- You want to use a CSS pre-processor
- You want to automate linting & code formatting on the fly
- You want to automate anything on a file/directory modification
Runner
In this initial proof of concept, tuples containing source
and destination
files are each accompanied by a function, using the source
as the decorator argument.
The argument passed to the @filemon
decorator is the file or directory to watch for changes.
Here's the quick and dirty prototype:
from flask import Flask, render_template, make_response
from uwsgidecorators import filemon
import click
import rcssmin
import rjsmin
import sass
from datetime import datetime
"""
To start thr runner:
uwsgi runner.ini
"""
app = Flask(__name__)
# compile SCSS to CSS: source > destination
sass_map = ("app/static/scss/style.scss", "app/static/css/style.css")
# Minify JavaScript: source > destination
js_map = ("app/static/js/app.js", "app/static/js/app.min.js")
# Minify CSS: source > destination
css_map = ("app/static/css/style.css", "app/static/css/style.min.css")
task_history = list()
@filemon(sass_map[0])
def scss_watcher(file):
""" Compiles SCSS to CSS on file modification """
click.secho(f"\nDetected change in {sass_map[0]}", fg="green")
print("--------------------")
print("Compiling SCSS to CSS:")
source, dest = sass_map
with open(dest, "w") as outfile:
outfile.write(sass.compile(filename=source))
print(f"{source} > {dest}", end="\n")
task_history.append(
{
"type": "Compiled SCSS to CSS",
"src": source,
"dest": dest,
"time": datetime.utcnow(),
}
)
@filemon(js_map[0])
def js_watcher(file):
""" Minifies JavaScript on file modification """
click.secho(f"\nDetected change in {js_map[0]}", fg="yellow")
print("--------------------")
print("Minifying Javascript files:")
source, dest = js_map
with open(source, "r") as infile:
with open(dest, "w") as outfile:
outfile.write(rjsmin.jsmin(infile.read()))
print(f"{source} > {dest}", end="\n")
task_history.append(
{
"type": "Minified JavaScript file",
"src": source,
"dest": dest,
"time": datetime.utcnow(),
}
)
@filemon(css_map[0])
def css_watcher(file):
""" Minifies CSS on file modification """
click.secho(f"\nDetected change in {css_map[0]}", fg="cyan")
print("--------------------")
print("Minifying CSS files:")
source, dest = css_map
with open(source, "r") as infile:
with open(dest, "w") as outfile:
outfile.write(rcssmin.cssmin(infile.read()))
print(f"{source} > {dest}", end="\n")
task_history.append(
{"type": "Minified CSS", "src": source, "dest": dest, "time": datetime.utcnow()}
)
@app.route("/")
def runner_web():
""" A simple route that displays tasks run since reload """
if len(task_history) == 0:
res = "No tasks run"
else:
res = "Tasks run since reload:\n"
for t in task_history:
res += "[ runner.py ]"
res += f" task: {t['type']}"
res += f" src: {t['src']}"
res += f" dest: {t['dest']}"
res += f" time: {str(t['time'])}\n"
response = make_response(res)
response.headers["Content-Type"] = "text"
return response
uWSGI config
Here's the uWSGI config file:
[uwsgi]
strict = true
http = :8080
wsgi-file = runner.py
callable = app
master = true
processes = 1
py-autoreload = 2
Starting the runner
uwsgi runner.ini
Testing
Initial testing works as expected. SCSS is compiled and CSS/JS files are minified. Here's the text logged to the browser after a few file modifications:
Tasks run since reload:
[ runner.py ] task: Compiled SCSS to CSS src: app/static/scss/style.scss dest: app/static/css/style.css time: 2019-04-18 21:30:36.448970
[ runner.py ] task: Compiled SCSS to CSS src: app/static/scss/style.scss dest: app/static/css/style.css time: 2019-04-18 21:30:36.619307
[ runner.py ] task: Minified CSS src: app/static/css/style.css dest: app/static/css/style.min.css time: 2019-04-18 21:30:36.625491
[ runner.py ] task: Minified CSS src: app/static/css/style.css dest: app/static/css/style.min.css time: 2019-04-18 21:30:36.629975
[ runner.py ] task: Minified CSS src: app/static/css/style.css dest: app/static/css/style.min.css time: 2019-04-18 21:30:36.635917
[ runner.py ] task: Minified CSS src: app/static/css/style.css dest: app/static/css/style.min.css time: 2019-04-18 21:30:36.641563
Wrapping up
I know there's lot's of very good task runners/build tools out there, this was a curiosity and some fun. After experimenting with some of the uWSGI decorators in a previous article, I thought this kind of project might have some potential for projects already using Flask and uWSGI (without Node/NPM).
:TODO - Revisit this.