Local Components

Introduction

Now we have a way to publish and use Bower components. But you probably also develop your own front-end code: we call these “local components”. BowerStatic also helps with that. For this it is important to understand that locally developed code has special caching requirements:

  • When you release a local component, you want it to be cached infinitely just like for Bower components.

    When later a new release is made, you want that cache to be invalidated, and not force end-users to do a shift-reload to get their browser to load the new version of the code.

    We can accomplish this behavior by using a version number in the URL, just like for Bower components.

    XXX one way to release a local component would be to release it as a bower component at this point. But this may be cumbersome for code maintained as part of Python package.

  • When you develop a local component, you want the cache to be invalidated as soon as you make any changes to the code, so you aren’t forced to do shift-reload during development. A simple reload should refresh all static resources.

    A way to look at this is that you want the system to make a new version number for each and every edit to the local component.

Usage

To have local components, you first have to create a special local components registry:

local = bower.local_components('local', components)

You can have more than one local components registry, but typically you only need one per project.

The first argument is the name of the local components registry. It is used in the URL.

The second argument is a components object for a bower_components directory, created earlier with bower.components(). This makes all those bower components available in the local component registry, so that the local components can depend on them.

Note that the local components registry does not point to a bower_components directory itself. Instead we register directories for individual local components manually.

Here’s how we add a local component:

local.component('/path/to/directory/mycode', version='1.1.0')

The /path/to/directory/mycode directory should have a bower.json file. BowerStatic uses name and main for local components like it uses them for third party Bower components. The name of the component should be unique within the local registry, as well as not conflict with any component in the Bower components registry.

As with bower.components, you can use bowerstatic.module_relative_path to create a path relative to the calling module:

components = local.component(
    bowerstatic.module_relative_path('path/relative/to/calling/module'),
    version='1.1.0')

dependencies is also picked up from bower.json, but unlike for third party components these dependencies are not automatically installed.

The version number is not picked up from bower.json. Instead it is passed through to the local component. This will make it possible to support the right caching behavior. We go into detail about this later.

If you have a local component called mycode, and there is a file app.js in its directory, it is published under this URL:

/bowerstatic/local/mycode/1.1.0/app.js

To be able to include it, we can create an includer for the local registry:

include = local.includer(environ)

This includer can be used to include local components, but also the third-party components from the registry that the local components registry was initialized with.

You can now include app.js in mycode like this:

include('mycode/app.js')

Versioning

Let’s consider versioning in more detail.

The version number is passed in when registering the local component. We want it to do the right thing with caching:

  • When the application is deployed, we want the version number to be the version number of that application (or sub-package of that application), so that infinite caching can be used but the cache is automatically busted with an application upgrade.
  • When the application is under development, we want the version number to change each time you edit the local component’s code, so that the cache is busted each time.

Versioning deployed applications

You can use the version of a Python application easily, as long as it is packaged using setuptools ( pip, easy_install, buildout, etc). You can retrieve its version number like this:

import pkg_resources

version = pkg_resources.get_distribution('myproject').version

This picks up the version given in setup.py of myproject.

Using this to obtain the version and passing it into local.component() is enough to make sure the cache is busted when you make a release of your application.

Versioning during development

We have to make sure the cache is busted automatically during development as well. For that we have to turn on BowerStatic’s development mode. You can do this by passing None as the version into local.component.

This causes the version to be automatically determined from the code in the package, and be different each time you edit the code. Since the version is included in the URL to the package, this allows you to get the latest version of the code as soon as you reload after editing a file. No shift-reloads needed to reload the code!

Putting it all together

Development mode is relatively expensive, as BowerStatic has to monitor the local directory for any changes so it can update the version number automatically. You should therefore make sure it is only enabled during development, not during deployment. When your application is deployed you need to pass in a real version number, for instance the one you pick up using pkg_resources as described before.

If your application has a notion of a development mode that you can somehow inspect during run-time, you can write a version function that automatically returns None in development mode and otherwise returns the application’s version number. This ensures optimal caching behavior during development and deployment both. Here’s what this function could look like:

def get_version():
    if is_devmode_enabled():  # app specific API
        return None
    return pkg_resources.get_distribution('myproject').version

You can then register the local component like this:

local.component('/path/to/directory/mycode', version=get_version())