You’re probably not a frontend developer, and I’m guessing you have no desire to become one. I’m fairly confident of this because you’re currently engaged in a Flask tutorial instead of complaining about the number of frontend frameworks you’ve been forced to learn in the last decade (sorry frontend fam, you know I love you 😄).

Every backend dev needs to come to terms with a stark reality: meaningful apps can’t be built without eventually digging into writing some styles or frontend JavaScript. Since the early days of Django, Python developers have opted to coin frontend code as static assets: a backhanded term that seemingly downplays the importance of JavaScript, CSS, or any presentation layer assets that make up a web application. It had been decreed that only two types of code exist in the universe: Python and Not-Python. Anything that isn't a .py file is considered a "static" asset, tucked into a single directory after presumably receiving a handoff from a frontend developer. This implied mindset is both somewhat absurd and unsurprising.

It’s time to face our fears of dealing with frontend code in Flask the right way: by writing styles in CSS preprocessors, minifying files, and serving them in “bundles” akin to the webpack 30-year-old equivalent. If you’d prefer to link to a CDN-hosted version of jQuery 1.11 or include a hideously generic Bootstrap library to style your app for you, please stop reading and go away. For those of us with higher aspirations than a full-time job maintaining 30-year old legacy code for CitiBank, we’ll be learning about Flask-Assets.

How it Works

In the simplest of terms, applications that utilize Flask-Assets handle two "states" of static files: the uncompressed "development" files (such as style preprocessors) and "compiled" files. Files in the "development" category include .LESS, .SASS, or some form of Javascript. "Compiled" files are compressed to be production-ready. This should sound familiar to readers who have encountered projects containing /src and /dist directories (hopefully, that includes all of us).

When configured to do so, Flask-Assets compiles static assets into bundles upon startup. This is accomplished via auxiliary Python libraries dedicated to building and compressing files:

  • lesscpy & pyscss: Compiles .LESS files or .SASS/SCSS into .CSS.
  • cssmin: Minifies the output of stylesheets, such as those generated by the above.
  • jsmin: Minifies JavaScript files.

We’ll need to install the appropriate Python packages depending on our stack choice. I plan on writing styles with LESS and adding some frontend JavaScript to my app:

$ pip install flask-assets lesscpy cssmin jsmin

Install dependencies

Flask-Assets Configuration

As with most Flask plugins, we can configure Flask-Assets by setting variables in our top-level Flask config. Whoever created this library made the interesting choice of not documenting any of this, so I'm going to save you some time:

"""Flask configuration."""

LESS_BIN = '/usr/local/bin/lessc'
ASSETS_DEBUG = False
ASSETS_AUTO_BUILD = True

config.py

  • LESS_BIN: LESS requires the NPM library called LESS (known to the system as lessc) to be installed on our system to compile LESS into CSS. Run which lessc in your terminal to find this path. The SASS equivalent to this is SASS_BIN.
  • ASSETS_DEBUG: We're bound to screw up a style or JS function every now and then, which sometimes doesn't become obvious until we build our bundles and try to use our app. If we set this variable to True, Flask-Assets won't bundle our static files while we're running Flask in debug mode. This is useful for troubleshooting styles or JavaScript gone wrong.
  • ASSETS_AUTO_BUILD: A flag to tell Flask to build our bundles of assets when Flask starts up automatically. We generally want to set this to be True, unless you'd prefer only to build assets with some external force (AKA: you're annoying).

Creating Asset Bundles

Before diving into code, let's establish our app's structure. Nothing out of the ordinary here:

/app
├── /static
│   ├── /img
│   ├── /dist
│   │   ├── /css
│   │   └── /js
│   └── /src
│       ├── /js
│       └── /less
├─ /templates
├─ config.py
└─ app.py

Project structure

The key takeaway is that we will set up Flask-Assets to look for raw files to compile in static/src/less and static/src/js. When the contents of these folders are compiled, we'll output the results to static/dist/less and static/dist/js where they'll be ready to serve.

Let's get this baby fired up in app.py:

from flask import Flask
from flask_assets import Environment


app = Flask(__name__, instance_relative_config=False)

assets = Environment(app)

app.py

We initialize Flask-Assets by creating an Environment and initializing it against Flask's app object. There's an alternative way of initializing the library via the Flask application factory, but we'll get there in a moment.

We'll create our first asset Bundle in the form of .LESS compile files found in our static/src/less directory. We're going to compile these files into CSS, minify the resulting CSS, and serve the files:

from flask import Flask
from flask_assets import Environment, Bundle


app = Flask(__name__, instance_relative_config=False)
assets = Environment(app)

style_bundle = Bundle(
    'src/less/*.less',
    filters='less,cssmin',
    output='dist/css/style.min.css',
    extra={'rel': 'stylesheet/css'}
)

# Register style bundle
assets.register('main_styles', style_bundle) 

# Build LESS styles
style_bundle.build()  

app.py

The Bundle object is where most of our magic happens. Bundle can accept any number of positional arguments, where each argument is the path of a file to include in our bundle. Our example creates a bundle from 'src/less/*.less', which means "all .less files in src/less." We could instead pass individual files (like 'src/less/main.less', 'src/less/variables.less', etc.) if we want to cherry-pick specific stylesheets. In this scenario, it's important to note any imports within these .LESS files (such as @import './other_styles.less') would also automatically be included in our bundle.

The next step in creating our Bundle is passing some necessary keyword arguments:

  • Filters: This is where we tell Flask-Assets which Python libraries to use to compile our assets. Passing 'less,cssmin' tells our Bundle that we will build a bundle from .LESS files using lesscpy, which we should then compress using cssmin.
  • Output: The directory where our production-ready bundle will be saved and served.
  • Extra: Appends additional HTML attributes to the bundle output in our Jinja template (this usually isn't necessary).

After creating a bundle of styles called style_bundle, we "register" the bundle with our environment with assets.register('main_styles', style_bundle) ( 'main_styles' is the arbitrary name we assign to our bundle, and style_bundle is the variable itself).

We finally create our bundle with style_bundle.build(). This outputs a single CSS file to dist/css/style.css.

Creating a JavaScript Bundle

Creating a bundle for frontend JavaScript is almost identical to the bundle we created for styles. The main difference is using the jsmin filter to minimize the JS files.

Here's an example of creating bundles for both in unison:

...

style_bundle = Bundle(
    'src/less/*.less',
    filters='less,cssmin',
    output='dist/css/style.min.css',
    extra={'rel': 'stylesheet/css'}
)
js_bundle = Bundle(
    'src/js/main.js',
    filters='jsmin',
    output='dist/js/main.min.js'
)
assets.register('main_styles', style_bundle)
assets.register('main_js', js_bundle)
style_bundle.build()
js_bundle.build()

CSS & JS bundles

Serving Bundles via Page Templates

We can serve our newly created assets in our page templates as we would any other static asset:

{% extends "layout.jinja2" %}

<link
   href="{{ url_for('static', filename='dist/css/style.min.css') }}"    
   rel="stylesheet"
   type="text/css"
/>
<script
   src="{{ url_for('static', filename='dist/js/main.min.js') }}">
</script>

index.jinja2

Launching your Flask app should now result in styles and JavaScript being built to src/dist. If all goes well, your page templates will pick these up, and your app will look snazzy with some sexy, performance-optimized frontend assets.

There's apparently an alternative way to create bundles entirely in templates with zero Python code. I personally haven't tried this out, but the simplicity of the syntax is pretty f'in cool:

{% assets filters="jsmin", output="src/js/main.js", "src/js/jquery.js"  %}

	
    
{% endassets %}

Blueprint-specific Bundles

One of the most powerful features of Flask-assets is the ease with which we can create Blueprint-specific asset bundles. This allows us to "code-split" relevant frontend assets exclusive to Blueprints. We will build on the example project we created last time in our Flask Blueprints tutorial to create blueprint-specific bundles.

Here's a quick overview of a project with two registered blueprints (/main and /admin) which we'll create asset bundles for:

flask-assets-tutorial
├── /flask_assets_tutorial
│   ├── __init__.py
│   ├── assets.py
│   ├── /admin
│   │   ├── routes.py
│   │   ├── /static
│   │   └── /templates
│   ├── /main
│   │   ├── routes.py
│   │   ├── /static
│   │   └── /templates
│   ├── /static
│   └── /templates
├── config.py
└── wsgi.py

Flask project utilizing Blueprints

Each blueprint has its own /static folder containing blueprint-specific assets. Our top-level Flask app also retains its /static folder, containing assets to be shared across all blueprints. Before getting into the details of how that works, let's look at how we instantiate Flask-Assetsinto the Flask application factory:

"""Initialize Flask app."""
from flask import Flask
from flask_assets import Environment


def create_app():
    """Construct core Flask app."""
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object("config.Config")
    assets = Environment()

    # Initialize plugins
    assets.init_app(app)

    with app.app_context():
        # Import parts of our flask_assets_tutorial
        from .admin import routes as admin_routes
        from .assets import compile_js_assets, compile_stylesheet_bundles
        from .main import routes as main_routes

        # Register Blueprints
        app.register_blueprint(admin_routes.admin_blueprint)
        app.register_blueprint(main_routes.main_blueprint)

        # Compile static assets
        compile_stylesheet_bundles(assets)
        compile_js_assets(assets)

        return app

__init__.py

Let's check out what's happening in assets.py:

"""Compile static assets."""
from flask import current_app as app
from flask_assets import Bundle, Environment


def compile_stylesheet_bundles(assets: Environment) -> Environment:
    """
    Create minified CSS bundles from LESS styles.

    :param Environment assets: Flask `environment` for static assets.

    :returns: Environment
    """
    # Main Stylesheets Bundle
    main_style_bundle = Bundle(
        "src/less/*.less",
        "main_blueprint/homepage.less",
        filters="less,cssmin",
        output="dist/css/landing.css",
        extra={"rel": "stylesheet/css"},
    )
    # Admin Stylesheets Bundle
    admin_style_bundle = Bundle(
        "src/less/*.less",
        "admin_blueprint/admin.less",
        filters="less,cssmin",
        output="dist/css/account.css",
        extra={"rel": "stylesheet/css"},
    )
    assets.register("main_styles", main_style_bundle)
    assets.register("admin_styles", admin_style_bundle)
    if app.config["ENVIRONMENT"] == "development":
        main_style_bundle.build()
        admin_style_bundle.build()
    return assets


def compile_js_assets(assets: Environment) -> Environment:
    """
    Create minified JS bundles from raw Javascript files.

    :param Environment assets: Flask `environment` for static assets.

    :returns: Environment
    """
    main_js_bundle = Bundle("src/js/main.js", filters="jsmin", output="dist/js/main.min.js")  # Main JavaScript Bundle
    assets.register("main_js", main_js_bundle)
    if app.config["ENVIRONMENT"] == "development":
        main_js_bundle.build()
    return assets

assets.py

The magic comes into play in Bundle()'s positional arguments. In the case of main_style_bundle, we pass global styles with src/less/*.less and blueprint specific styles with main_blueprint/homepage.less. But wait, main_blueprint isn't a directory! It's the registered name of our main blueprint that we create in main_routes.py:

"""Routes for main pages."""
from flask import Blueprint


main_blueprint = Blueprint(
    "main_blueprint",
    __name__,
    template_folder="templates",
    static_folder="static"
)

main/routes.py

When setting static_folder='static' on our blueprint, we're telling Flask to check for assets in two places: first in flask_assets_tutorial/static, and then flask_assets_tutorial/main/static second. Bundle() Recognizes that main_blueprint is a blueprint name as opposed to a directory and thus resolves the asset correctly.

Setting up blueprint-specific assets isn't a walk in the park the first time. The official documentation for Flask-assets is simply god-awful. As a result, plenty of pitfalls and quirks can ruin somebody's day... especially mine.

Learning by Example

If you find yourself getting stuck, I've created a living, breathing demo of Flask-Assets.

The source is up on Github:

hackersandslackers/flask-assets-tutorial
Working example of a Flask application utilizing blueprint-specific assets. - hackersandslackers/flask-assets-tutorial

...And the app is running here:

Hackers and Slackers - Hackers and Slackers
Serve static assets (images, css, js) for your user-facing Flask app.