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:
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:
- 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:
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:
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:
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:
Serving Bundles via Page Templates
We can serve our newly created assets in our page templates as we would any other static asset:
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.
{% 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:
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-Assets
into the Flask application factory:
Let's check out what's happening in 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:
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:
...And the app is running here: