If you're familiar with Django (or Python to any extent), you've probably accepted the concept of what Python guys refer to as "static assets." Let's take a moment to consider this: at some point in Django's conception a design decision was made: all JavaScript, CSS, or any assets pertaining to the presentation layer belong tucked away into an isolated corner of the framework. Alas, it was decreed: henceforward, only two types of code exist in the universe: Python, and not-Python. Anything that isn't Python is simply a "static" asset to be dealt with either locally or via a CDN somewhere.

The implied mindset is both somewhat absurd yet also unsurprising. It's easy to imagine a couple of backend nerds nonchalantly writing off frontend development as a concept to be stuck in a single folder. You know, all those things which make an app an app? Like, anything visible to a user? These are simply things-to-be-dealt-with when the frontend guys are ready with their "make it pretty" nonsense. Screw it, throw it all in the /static folder. We accept this distinction to this day with little thought. Flask has carried over the same terminology and concept of a "static" folder, so this amusing cultural relic is here to stay.

Anyhow, we're here today to help you serve those beautiful assets of yours, as static as they may be. We'll do so by exploring the top-two libraries available to us: Flask-Static-Compress, and Flask-Assets.

Application Structure For Flask Apps with Frontend

Best practices aside, we'll start with the most straightforward project structure for a Flask web app. Let's work with this structure:

├─ /static
│  └─ /js
│  └─ /less
│  └─ /img
│  └─ /build
├─ /templates
└─ app.py

All the Python we need is going to sit in a plump little app.py file.

Templates hold the same concept as they do to equivalent frameworks such as Express: this is where we contain pages, partials, and layouts. Flask's default Jinja2 templating engine has personally served me well enough to never investigate an alternative.

/Static is where we'll store the raw source files for things like preprocessed stylesheets and JavaScript. At runtime, these sources files will be compressed and stored elsewhere in a production setting, such as that nifty /build folder we created (or feel free to call it /dist, whatever, it's your party man).

It's best practice to serve these from a CDN at runtime, but whatever.

In contrast to NodeJS apps, things like images and fonts are stored in the /static folder as well. This distinguishes Python's concept of /static from, say, another framework's concept of /src: instead of being a folder of only source code, we pretty much have a folder of all that shit which isn't Python. And we've come full circle.

Creating The Flask App Object

As always, we start by creating our Flask app object:

from flask import Flask, url_for, render_template, request, Response

app = Flask(__name__, static_folder="static", template_folder="templates")

We initiate our app on line 4 with app = Flask().  We create our app with three parameters/attributes:

  • __name__: Now that I think about it, I've never actually considered what this first parameter is doing. Just know that you need it,
  • static_folder: Specifies the name of the folder where static assets will be served. This is based on where your app believes the root folder lives. Because our app is a single directory, it knows that /static is in the current directory.
  • template_folder: Same as above, but contains Jinja2 templates, AKA the files which become HTML.

It's important to reiterate that these folders default to being relative to the current location of app.py. If we want to change this behavior, we can pass instance_path=/path/to/desired/dir/ to override this behavior.

Method 1: Flask-Static-Compress

There's more than one way to skin a cat, and there's certainly more than one library or philosophy for serving pages and styles in Flask. flask_static_compress is one such library, which we'll include in app.py:

from flask import Flask, url_for, render_template, request, Response
from flask_static_compress import FlaskStaticCompress

flask_static_compress is a cool library for compressing and joining assets together. If you've ever used Gulp, it achieves some of the common tasks Gulp might, but with a MUCH different philosophy. Some key differences are:

  • The ability to served compressed assets individually, as opposed to one giant site bundle.
  • Never needing to explicitly fun a build command to create files served at runtime.

Before serving any assets, we'll need a landing page for our app. While still in app.py, we need to set a route for anybody who visits our site:

from flask import Flask, url_for, render_template, request, Response
from flask_static_compress import FlaskStaticCompress

app = Flask(__name__, static_folder="static", template_folder="templates")

@app.route('/', methods=['GET'])
def home():
    """Landing page."""
    return render_template('/index.html', title="Lame Site")

Our route listens for traffic hitting "/" (our root directory) and kindly serves them the page index.html as you might expect. Because we set the value of templates_folder just now, Flask knows to serve a file living in /templates/index.html.

If you need some pointers on creating basic templates in Flask in which to load your frontend assets, I'd suggest taking a look back at the previous post.

Frontend YOLO Swag

Back to your project, make .less file and a .js file in the appropriate places:

├─ /build
├─ /static
│  └─ js
│  │  └─ main.js
│  └─ less
│  │  └─ style.js
│  └─ img
├─ /templates
│  └─ layout.html
│  └─ index.html
└─ app.py

Use these files to brand your site accordingly. Add some images, memes, propaganda, viruses, etc. Whatever your heart desires.

Back in app.py we need to finish configuring our library:

from flask import Flask, url_for, render_template, request, Response
from flask_static_compress import FlaskStaticCompress

app = Flask(__name__)
app.config['COMPRESSOR_DEBUG'] = app.config.get('DEBUG')
app.config['COMPRESSOR_STATIC_PREFIX'] = 'static'
app.config['COMPRESSOR_OUTPUT_DIR'] = 'build'
app.static_folder = 'static'
compress = FlaskStaticCompress(app)

@app.route('/', methods=['GET'])
def home():
    """Landing page."""
    return render_template('/index.html', title="Lame Site")

Congrats! the main part of your application is pretty much done, just note a few things:

  • Notice this time we set our folder paths via app.config[] as opposed to inline, in the earlier example. This is simply a matter of preference.
  • compress = FlaskStaticCompress(app) initializes our library, so definitely do that.
  • Fun tidbit: app.static_folder = 'static' is a snippet which can live within any route to override the default app settings for where your folders are located.

Compressed 2 deff

Let's wrap this bad boy up. Back in layout.html, let's add those static files we created.

<!doctype html>
  <title>{% block title %}{% endblock %} - My Lame Site</title>
  <!-- Meta Data -->
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
  <!-- CSS -->
  {% compress 'css' %}
     <link href="{{ url_for('static', filename='less/home.less') }}" type="text/less">
  {% endcompress %}
  <!-- JS -->
    {% compress 'js' %}
      <script src="{{ url_for('static', filename='js/previews.js') }}"></script>
	{% endcompress %}
  {% block content %}{% endblock %}

Let's analyze this real quick:

{% compress 'css' %}
   <link href="{{ url_for('static', filename='less/home.less') }}" type="text/less">
{% endcompress %}

All files within the {% compress 'css' %} block will be minified and joined into a single file, and then moved to the build folder we specified earlier. It's that easy- no jobs to run beforehand, etc.

What might also catch your eye is how we define the path:

{{ url_for('static', filename='less/home.less') }}

This is a Jinja path dynamically locating our source file from our specified static doc. Well, I'll be darned.

Method 2: Flask-Assets

FlaskStaticCompress is great, but Flask_assets might even be better. If you're familiar with Webpack, consider the philosophy behind bundling. Creating bundles of frontend assets makes sense when we can draw clear distinctions of alike-screens in our app: this way, users don't download the entirety of our assets for screens they might not visit upfront, while pre-loading assets for pages they'll probably visit.

The Flask-Assets library goes hand-in-hand with the concept of Flask Blueprints. If you aren't familiar with Blueprints just yet, I encourage you to become familiar here.

To get started, we'll install the necessary libraries:

$ pip3 install flask-assets lesscpy cssmin jsmin

This is working under the assumption that we're be writing styles in LESS. If you prefer sass, libsass can take the place of lesscpy.

The other two libraries, cssmin and jsmin, are for minifying CSS and JS assets respectively.

Let's see how we pull this off:

from flask_assets import Environment, Bundle

# Flask-Assets Configuration
less_bundle = Bundle('src/less/*.less',
                     extra={'rel': 'stylesheet/less'})
js_bundle = Bundle('src/js/*.js',
assets.register('less_all', less_bundle)
assets.register('js_all', js_bundle)

Notice how we only import flask_assets of all the libraries we installed- this is intentional. The other libraries do not need to be imported anywhere.

less_bundle and js_bundle represent groupings of LESS and JS files to be stitched into one single file. Any positional arguments to be passed in to Bundle() will be taken as paths of sources files to bundle: we can bundle as many or as few files as we want (or in our case , just import *).

The filters argument is where we tell our Bundle() how to treat the files we've passed. In the case of less_bundle, passing less,cssmin indicates that the incoming files will be LESS files, to be compiled into CSS files, and then outputted as minified CSS files. the output destination, of course, is handled by the argument output.

Final Thoughts

The great thing about Flask (and Python in general) is that you're provided the luxury to write code in a manner which you find enjoyable. Considering Flask is a relatively young framework, we're in a bit of a golden age where there are just enough libraries to suit your tastes, but not enough to get lost in an NPM level hell.

In my completely biased and untrustworthy opinion, it's hard to imagine getting involved with a Framework in a sweeter spot than where Flask is right now.