score.webassets

The aim of this module is to provide an infrastructure for handling assets in a web project. An asset is a supplementary resource required by the web application, like a javascript file, or css definitions.

Quickstart

Configure a folder, where this module should store all created bundles and the provide a list of modules, to provide as web assets:

[score.init]
modules =
    score.ctx
    score.http
    score.css
    score.js
    score.webassets

[webassets]
rootdir = ${here}/_bundles
modules =
    css
    js

You can now include your css and javascript assets in your html templates. The next fragment is in jinja2 format:

<html>
    <head>

        <style>
            {# Will render the content of the given css file #}
            {{ webassets_content('css', 'above-the-fold.css') }}
        </style>

        {# Will load *all* css files  with a <link> tag #}
        {{ webassets_link('css') }}

        {# Will load a bundle consiting of two javascript files #}
        {{ webassets_link('js', 'file1.js', 'file2.js') }}

    </head>
    ...

Configuration

score.webassets.init(confdict, http=None, tpl=None)[source]

Initializes this module acoording to our module initialization guidelines with the following configuration keys:

rootdir None
The folder where this module will store the bundled assets.
modules []
A list of configured score modules to retrieve proxy objects from. The configured modules listed here must all expose a score_webassets_proxy() method, that returns a WebassetsProxy object.
freeze False

Option for speeding up asset hash calculations.

See Asset Freezing for valid values.

tpl.autobundle False
Whether the webassets_* functions registered with score.tpl should provide bundles instead of separate files. This should be set to True on deployment systems to speed up web page rendering.

Details

Asset Versioning

Most of a web applications assets rarely change. At least they stay exactly the same for long periods of time. A common technique for helping browsers cache these assets more efficiently is the exposure of versioning information of assets within their URLs.

Let us assume we have a css asset called colors.css, which has the following content at its first deployment:

h1.article-heading {
    background-color: #E3D9C6;
}

The URL of this resource might be http://example.com/css/colors.css. If we would use just this URL, the browser would need to check back every once in a while to see if this resource has changed. It would thus request the resource much more often than necessary.

If we instead add a “version string” to the URL, we can tell the browser to cache this resource forever. When the resource changes, we change the URL and point to the new URL in our HTML.

The initial “version” of the resource might now have the URL http://example.com/css/colors.css?version=1. When a browser requests this URL, we send all required HTTP headers that tell the browser that the resource found in this URL will never change.

If we add a definition to our css asset at a later point …

h1.article-heading {
    background-color: #E3D9C6;
}
h2.article-heading {
    background-color: #DDC49A;
}

… we immediately change the URL of that resource to http://example.com/css/colors.css?version=2. The browser sees a new URL and assumes that it must be a different asset (which it technically is) and requests its contents. We, again, tell it to keep this file forever and to never ask the web server again for this exact URL.

This feature is automatically enabled, although it does not use incremental values as “version strings”, as in the examples above. Instead, it operates using hashes of the asset contents. That’s why they are referred to as asset hashes throughout the documentation.

Asset Freezing

Normally, the webassets module will determine the asset hash based on the asset’s content. Unfortunately, this method is very slow: whenever we need the hash of an asset, we will need to render the content of the asset.

To avoid such expensive operations during deployment, the module has two different modes for freezing the asset hashes. You can configure score.webassets to remember the hash of each asset by passing True in the module’s freeze configuration. This ensures that hashes are calculated at most once.

[webassets]
freeze = True

If you have a deployment script, it is even better to pre-calculate the hash and to provide that value in the module configuration:

$ score webassets freeze
b18ed2b601ab3850
[webassets]
freeze = b18ed2b601ab3850

Proxy

Note

The intended audience of this section is module developers. This is the reason the next few suggestions may seem a bit too abstract for day to day usage.

Every module that wants to provide web assets through this module must return a sub-class of WebassetsProxy from the configured module’s score_webassets_proxy() function.

As an example, we will assume that we want to build a module called “myobjects”, that can grant access to javascript objects stored in JSON files. We will be accessing the objects one by one, but may occasionally need to retrieve multiple objects at once.

To provide these objects as web assets, we need to create a proxy object. We will use the simpler TemplateWebassetsProxy to keep the example code short:

from score.webassets import TemplateWebassetsProxy

class MyobjectsWebassets(TemplateWebassetsProxy):

    def __init__(self, tpl):
        super().__init__(tpl, 'application/json')

    def render_url(self, url):
        return '''
            <script>
                (function() {
                    var url = JSON.decode(%s);
                    fetch(url).then(function(response) {
                        return response.json();
                    }).then(function(result) {
                        myGlobalObjectStorage.add(result);
                    });
                })();
            </script>
        ''' % (json.dumps(url),)

    def create_bundle(self, paths):
        return ''.join(map(self.render, paths))

Your configured module must now return an instance of this class in its score_webassets_proxy() method:

from score.init import ConfiguredModule

class ConfiguredMyobjectsModule(ConfiguredModule)

    def __init__(self, tpl):
        self.tpl = tpl  # a score.tpl dependency
        super().__init__('myobjects')

    def score_webassets_proxy(self):
        return MyobjectsWebassets(self.tpl)

After configuring score.webassets to include this module, you can make use of your new module inside html templates. The next fragment is in jinja2 format:

<html>
    <head>

        <script>
            // some code initializing myGlobalObjectStorage
        </script>

        {# lazy-loading all assets #}
        {{ webassets_link('myobjects') }}

        {# embedding asset content directly #}
        <script>
            (function() {
                myGlobalObjectStorage.add(JSON.decode(
                    {{ webassets_content('myobjects') }}
                ));
            })();
        </script>

    </head>
    ...

API

Configuration

score.webassets.init(confdict, http=None, tpl=None)[source]

Initializes this module acoording to our module initialization guidelines with the following configuration keys:

rootdir None
The folder where this module will store the bundled assets.
modules []
A list of configured score modules to retrieve proxy objects from. The configured modules listed here must all expose a score_webassets_proxy() method, that returns a WebassetsProxy object.
freeze False

Option for speeding up asset hash calculations.

See Asset Freezing for valid values.

tpl.autobundle False
Whether the webassets_* functions registered with score.tpl should provide bundles instead of separate files. This should be set to True on deployment systems to speed up web page rendering.
class score.webassets.ConfiguredWebassetsModule[source]

This module’s configuration class.

get_asset_content(module, path)[source]

Returns the content of the asset identified my its module and path.

get_asset_mimetype(module, path)[source]

Returns the mime type of the asset identified my its module and path.

get_asset_hash(module, path)[source]

Provides the hash of the asset identified my its module and path.

get_asset_url(module, path)[source]

Returns the relative URL to the asset identified by its module and path, that this module can resolve via get_request_response().

You won’t need this function, if you’re using score.http. But if your means of deployment is different, you will want to create URLs to your assets using this function. It will look something like this:

/css/reset.css?_v=0b2931cc6255c72e

This should be rewritten to something you can detect in your application:

/_score_webassets/css/reset.css?_v=0b2931cc6255c72e

Whenever a URL starting with your custom prefix is requested, you can pass the modified Request with the original URL to get_request_response():

response = webassets.get_request_response(Request(
    '/css/reset.css',
    {'_v': '0b2931cc6255c72e'},
    {'Accept-Encoding': 'gzip,deflate',
     'Referer': ... }
))
get_bundle_name(module, paths=None)[source]

Provides a unique name for a bundle consisting of assets found in given module and given paths. Will use the module’s default paths, if the latter is omitted.

This feature is used internally for storing different bundles inside the same folder, for example.

get_bundle_hash(module, paths=None)[source]

Provides the hash of a bundle consisting of assets found in given module and given paths. Will use the module’s default paths, if the latter is omitted.

get_bundle_content(module, paths=None)[source]

Returns the content of requested bundle. The module name is required and will create a bundle with module’s default paths. It is also possible to create a bundle with a specific list of asset paths.

get_bundle_url(module, paths=None)[source]

Returns the relative URL to given bundle, that this module can resolve via get_request_response(). The module name is required and will create a bundle with module’s default paths. It is also possible to create a bundle with a specific list of asset paths.

See get_asset_url() for example usage.

get_request_response(request)[source]

Provides the most efficient response to an HTTP Request to obtain an asset. The return value is either a single int, denoting an HTTP status code (like 404 or 304), or a 2-tuple (headers, body). The headers list in the latter case is a dict mapping header names to their values, whereas the body is just a string. Note that none of the return values are formatted in any way. They will need to be properly encoded (which should happen automatically in most frameworks).

Helpers

class score.webassets.Request

A collections.namedtuple() describing the parts of HTTP request, that are required for ConfiguredWebassetsModule.get_request_response() to work. It consists of these 3 values:

  • path: The path section of the requested URL.
  • GET: Parsed dict of the query part.
  • headers: Another dict containing all headers, the client provided.
class score.webassets.WebassetsProxy[source]

A proxy object defining the behaviour of a type of web asset.

iter_default_paths()[source]

Provide a generator iterating over the paths, that should be used if no explicit path list was given.

validate_path(path)[source]

Test if a path is valid, i.e. if it can be passed to render().

hash(path)[source]

Returns a hash for path, that will change whenever the rendered content of the asset changes.

render(path)[source]

Provides the content of the asset with given path.

mimetype(path)[source]

Returns the mime type of the asset with given path.

render_url(url)[source]

Returns the string to embed in an HTML document to load given url. This might be a <link> tag for css assets, or a <script> tag for javascript assets.

create_bundle(paths)[source]

Returns a string containing the contents of multiple assets identified by their given paths.

bundle_hash(paths)[source]

Provides the hash of the bundle with given paths.

bundle_mimetype(paths)[source]

Returns the mime type of the bundle consisting of given paths.

class score.webassets.TemplateWebassetsProxy(tpl, mimetype)[source]

A type of WebassetsProxy that treats templates like assets. It accepts a configured score.tpl module and a mime type string and will provide almost all templates, that the tpl module knows of, as assets. If the tpl module knows of css files, for example, this class can be used to provide these css files as assets.

The default path list–as returned by iter_default_paths–will omit all files starting with underscore and all files inside folders that start with an underscore. Assuming the tpl module lists the following template paths …

banana.css
_orange.css
fresh/
    banana.css
    _apple.css
_old/
    pear.css
    passion-fruit.css

… only banana.css and fresh/banana.css will be returned by iter_default_paths.

exception score.webassets.AssetNotFound[source]

Thrown when an asset was requested, but not found. Web applications might want to return the HTTP status code 404 in this case.

Assets are uniquely identified by the combination of their module name and a path.