This video is available to students only

Assets

With any website, static assets like CSS, Images, and Javascript files control a large part of how the application looks and behaves. In Yumroad, we have been relying on externally hosted CSS and Javascript files. In the prior chapter, to implement Stripe checkout, we added a raw <script> tag with a function we wrote to support checkout. Those kinds of scripts and general styling should ideally not live within our templates, but instead in dedicated CSS and JS files.

In this chapter we will organize the static assets for our application into a scalable system and build a landing page for Yumroad.

Flask's default static folder.

Flask comes built in with a way to serve static files by creating a folder called static within our application and serving all of the files in that folder.

Try it out by creating a static folder within the yumroad folder and then creating a file called test.txt. If you save some content there and have the Flask server running, you will be able to see the file being served at localhost:5000/static/test.txt.

Static Text File
Static Text File

The static folder also works for other types of files, like images. Here we've made a logo for Yumroad and saved it as yumroad/static/yumroad.png.

Static Image File
Static Image File

In order to use these static files in our templates, we'll need to be able to reference the URLs. Similar to any other route, we can use url_for to generate the full path of the asset with the filename argument.

In order to reference our logo in our templates, we would call url_for('static', filename='yumroad.png'), which would return /static/yumroad.png. If we had placed yumroad.png within a subfolder of static called img, we would want to change the filename parameter to be url_for('static', filename='img/yumroad.png')`.

Now that we can serve static files, you might be tempted to write CSS and JS files within the static folder and serve them directly, which would work but would quickly grow untenable as the number of CSS files increased, especially if you want to run your CSS/JS files through a pre-processor.

Flask-Assets & webassets

webassets is a Python library to manage static assets like CSS, Javascript, SCSS, SASS, allowing us to create bundles, merge files, and minify (reduce in size & compress) them for our eventual production build. Flask-Assets is an extension that integrates webassets into Flask so that we can easily specify and use asset bundles that we create within our templates.

This install will be more involved than usual since there's some additional configuration steps.

To install this library, you'll want to add flask-assets to your requirements.txt, which will also install webassets. We want to eventually be able to minify CSS and JS files, so we'll also want to install the cssmin and jsmin packages

Once you add the three packages to your requirements.txt file, run pip install -r requirements.txt.

flask-assets
cssmin
jsmin

Before we integrate the webassets library into our application, we will first create a Bundle. A Bundle is a collection of static resources that are grouped together into an output file. A Bundle can contain references to externally hosted static files, files within our application, or even other Bundles.

Once a bundle is made, we should specify a folder for the output file to live.

An asset bundle that would function like our current CSS configuration, would look like this.

from flask_assets import Bundle

common_css = Bundle(
    'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/litera/bootstrap.css',
    filters='cssmin',
    output='public/css/common.css'
)

If we wanted to add our own custom css file from the static folder, we would add the file path to the bundle. If our custom css file was located at yumroad/static/css/basic.css, we would add the path relative to the static folder to the Bundle.

from flask_assets import Bundle

common_css = Bundle(
    'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/litera/bootstrap.css',
    'css/basic.css',
    filters='cssmin',
    output='public/css/common.css'
)

In a production environment, instead of going to bootstrapcdn.com, the contents would be stored on our own server and bundled together and served from static/public/css/common.css with any other CSS files we decide to add to common_css. This kind of bundling can save HTTP requests for browsers, but more importantly you get control of how you serve your assets.

In a development environment, to save time we can configure webassets to not bundle together the assets into a single file, and instead include each file individually when used in templates. This helps to make it easier to debug and avoid unnecessary pre-processing. To do that, add ASSET_DEBUG = True to your DevConfig and TestConfig in config.py.

Adding our own assets

To better organize our Bundle objects, we'll create a separate file call assets.py within yumroad to define the assets we want to used.

Within yumroad/assets.py, start of a Bundle that only contains a basic reference to the external bootstrap.css file and has an output of public/css/common.css

from flask_assets import Bundle

common_css = Bundle(
    'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/litera/bootstrap.css',
    filters='cssmin',
    output='public/css/common.css'
)

Once we've defined the asset in asset.py, we'll need to tell Flask Assets that we want to be able to use them within templates. First, we will need to initialize Flask-Extensions in extensions.py.

from flask_assets import Environment
...
assets_env = Environment()

Then in order to setup Flask Assets to see the bundles we've created, we have to import the assets.py module we created. In addition, we need to load in the asset's we've created. webassets comes with a built in PythonLoader that reads the bundle configuration from a Python module (which in our case will be assets.py). Once we've read the asset configurations, we need to tell Flask-Assets about each bundle, which is what the following code in yumroad/__init__.py does.

from webassets.loaders import PythonLoader as PythonAssetsLoader

from yumroad import assets

from yumroad.extensions import (..., assets_env)

def create_app(environment_name='dev'):
    ...
    assets_env.init_app(app)
    assets_loader = PythonAssetsLoader(assets)
    for name, bundle in assets_loader.load_bundles().items():
        assets_env.register(name, bundle)

Next, we want to remove any references to this stylesheet in our templates and replace it with a command to use the assets defined in common_css.

In yumroad/templates/base_layout.html, replace our direct link to the stylesheet from

        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">

to this:

        {% assets "common_css" %}
            <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}" />
        {% endassets %}

This block tells webassets to use the common_css bundle, and for each file that is being output, substitute in the actual path (ASSET_URL) to that file.

If you reload the page, you should not see anything different. To start customizing this, we can start creating our own static assets.

We are going to have three main types of assets in Yumroad, CSS files, Javascript Files, and images. To organize these we will create a folder for each within the static folder.

Create folders named img, css, js within the static folder.

Next, we can start by creating a Javascript and CSS file that will be shared across the application. Within the css folder, create an empty file called common.css within the css folder and within the js folder, create a Javascript file called common.js.

For the purposes of testing this out, let's make some changes that will be obvious to notice. Within yumroad/static/css/common.css, lets set the background color of the page to green.

body {
    background-color: green;
}

Within common.js, add an obnoxious alert pop up.

window.alert('This is an example of Javascript and CSS')

To load Javascript, we'll need to create a new bundle in yumroad/assets.py.

common_js = Bundle(
    'js/shared.css',
    filters='jsmin',
    output='public/js/common.js'
)

Then in yumroad/templates/base_layout.html, we add a new block to render a script tag with the correct src attribute within the head section of the template.

{% assets "common_js" %}
    <script type="text/javascript" src="{{ ASSET_URL }}"> </script>
{% endassets %}

Now when you load Yumroad, your page will look like this:

CSS Styling
CSS Styling

Now that page doesn't look very good, so let's remove our example style and Javascript for now. What we can think about is how to remove the Javascript from the checkout page template and into a dedicated Javascript file.

First, let's define a new Bundle in yumroad/static/assets.py.

checkout_js = Bundle(
    'js/purchase.js',
    filters='jsmin'
    output='public/js/purchase.js'
)

Then we will create yumroad/static/js/purchase.js. In the template, we directly template in the checkout_session_id into the function, but because this purchase.js is a static file, we don't have access to Jinja templating or even to Python here. Instead what we'll do is to change the function signature to take in the session ID, and have our function call to purchase pass in the session ID.

In addition, we don't have stripe defined or loaded yet. Unlike other scripts, we cannot host Stripe.js ourselves by putting it into a bundle since Stripe requires developers to pull it directly from https://js.stripe.com/v3/.

What we can do instead is define a dedicated block for templates to be able to into scripts the page

In templates/base_layout.html add a block called custom_assets

<head>
    ..
    {% block custom_assets %}

    {% endblock %}
</head>

Then within templates/products/details.html, we can add the content we need to be defined, along with a reference to add checkout_js.

Start a new discussion. All notification go to the author.