Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
462 lines (323 sloc) 15.5 KB

Integrating BowerStatic

Introduction

WSGI?

WSGI is a Python standard for interoperability between web applications and web servers. It also allows you to plug in "middleware" that sit between web server and web application that adds extra functionality. BowerStatic provides such middleware, which we will see later.

Most Python web frameworks are WSGI based. This means that if you use such a web framework for your application, your application is a WSGI application. Where this documentation says "WSGI application" you can read "your application".

This tutorial explains how to use BowerStatic with a WSGI application. BowerStatic doesn't have a huge API, but your web framework may provide more integration, in which case you may only have to know even less.

The Bower object

To get started with BowerStatic you need a Bower instance. Typically you only have one global Bower instance in your application.

You create it like this:

import bowerstatic

bower = bowerstatic.Bower()

Integrating BowerStatic with a WSGI app

For BowerStatic to function, we need to wrap your WSGI application with BowerStatic's middleware. Here's to do this for our bower object:

app = bower.wrap(my_wsgi_app)

Your web framework may have special BowerStatic integration instead that does this for you.

Later on we will go into more details about what happens here (both an injector and publisher get installed).

Declaring Bower Directories

Bower manages a directory in which it installs components (jQuery, React, Ember, etc). This directory is called bower_components by default. Bower installs components into this directory as sub-directories. Bower makes sure that the components fit together according to their dependency requirements.

Each bower_components directory is an "isolated universe" of components. Components in a bower_components directory can depend on each other only -- they cannot depend on components in another bower_components directory.

You need to let BowerStatic know where a bower_components directory is by registering it with the bower object:

components = bower.components('components', '/path/to/bower_components')

Bowerstatic needs an absolute path to the components. With the help of module_relative_path you can use a path relative to the calling module:

components = bower.components('components',
   bowerstatic.module_relative_path('path/relative/to/calling/module'))

You can register multiple bower_components directories with the bower object. You need to give each a unique name; in the example it is components. This name is used in the URL used to serve components in this directory to the web.

The object returned we assign to a variable components that we use later.

Including Static Resources in a HTML page

Errors

If you try to include a component that was not installed, you get an bowerstatic.Error exception. The bower_components directory is read during startup, so if you just installed that component using bower install, you need to restart the server.

If you try to refer to a refer to a file in a component that does not exist you also get an bowerstatic.Error exception. If that file then gets added (through a bower upgrade or if it's in a local component) it will be found without the need for a restart.

Now that we have a components object we can start including static resources from these components in a HTML page. BowerStatic provides an easy, automatic way for you to do this from Python.

Using the components object we created earlier for a bower_components directory, you create a include function:

include = components.includer(environ)

You need to create the include function within your WSGI application, typically just before you want to use it. You need to pass in the WSGI environ object, as this is where the inclusions are stored. You can create the include function as many times as you like for a WSGI environ; the inclusions are shared.

Now that we have include, we can use it to include resources:

include('jquery/dist/jquery.js')

WSGI environ

BowerStatic's includer system needs to interact with the WSGI environ object. If your WSGI-based web framework has a request object, then a very good bet is to try request.environ to get it.

Your web framework may also have special integration with BowerStatic; in that case the integration can offer the include function directly and takes care of interacting with the environ for you.

This specifies you want to include the dist/jquery.js resource from within the installed jquery component. This refers to an actual file in the jQuery component; in bower_components there is a directory jquery with the sub-path dist/jquery.js inside. It is an error to refer to a non-existent file.

If you call include somewhere in code where also a HTML page is generated, BowerStatic adds the following <script> tag to that HTML page automatically:

<script
  type="text/javascript"
  src="/bowerstatic/components/jquery/2.1.1/dist/jquery.js">
</script>

Supporting additional types of resources

There are all kinds of resource types out there on the web, and BowerStatic does not know how to include all of them on a HTML page. Additional types can be added by making a renderer and register that renderer to an extension.

Renderers will take a resource and returns a html snippet which will be injected in the HTML head element. Renderers can be defined as a callable.

The callable will need to take the resource as the single argument. Based on the resource, the callable can create a html snippet. The following attributes of resource are useful for creating the html:

url
The url which can be used to load the resource
content
The content of the resource, which can used to make an inline resource. This is mainly useful for small resources as it reduces the numbers of http requests

An example:

def render_foo(resource):
     return "<foo>%s</foo>" % resource.url()

A renderer can be registered to resources types by:

bower.register_renderer('.foo', render_foo)

If you now include a resource like example.foo, that resource gets included on the web page as <foo>/path/to/example.foo</foo>.

Because most of the time, like above, the html can be constructed with a format string, it is also possible to supply a string. For example:

bower.register_renderer('.foo', "<foo>{url}</foo>")

You can use url and content as variables in the format string.

You can also use register_renderer() to override existing behavior of how a resource with a particular extension is to be included.

If you include a resource with an unrecognized extension, a bowerstatic.Error is raised.

Custom renderer

It's also possible to specify the renderer which will be used in an included resource, so the renderer of the resource type will be overriden just for the given resource. When you specify the renderer, you can again do that both as callable and format string:

include('static/favicon.ico', '<link rel="shortcut icon" type="image/x-icon" href="{url}"/>')

or:

include('static/favicon.ico', lambda resource: '<link rel="shortcut icon" type="image/x-icon" href="' + resource.url() + '"/>')

Rendering inline

In some cases, you may want to render the content of resource directly into the web page, instead of referring to it through a URL:

include('static/something.js', bowerstatic.render_inline_js)
include('static/something.css', bowerstatic.render_inline_css)

URL structure

Let's look at the URLs used by BowerStatic:

/bowerstatic/components/jquery/2.1.1/dist/jquery.js
bowerstatic
The BowerStatic signature. You can change the default signature used by passing a signature argument to the Bower constructor.
components
The unique name of the bower_components directory which you registered with the bower object.
jquery
The name of the installed component as given by the name field in bower.json.
2.1.1
The version number of the installed component as given by the version field in bower.json.
dist/jquery.js
A relative path to a file within the component.

Caching

Cache busting

Caches in the browser and caching servers such as Varnish like to hold on to static resources, so that the static resources does not to be reloaded all the time.

But when you upgrade an application, or develop an application, you want the browser to request new resources from the server where those resources have changed.

Cache busting is a simple technique to make this happen: you serve changed resources under a new URL. BowerStatic does this automatically for you by including a version number or timestamp in the resource URLs.

BowerStatic makes sure that resources are served with caching headers set to cache them forever [1]. This means that after the first time a web browser accesses the browser, it does not have to request them from the server again. This takes load off your web server.

To take more load off your web server, you can install a install a caching proxy like Varnish or Squid in front of your web server, or use Apache's mod_cache. With those installed, the WSGI server only has to serve the resource once, and then it is served by cache after that.

Caching forever would not normally be advisable as it would make it hard to upgrade to newer versions of components. You would have to teach your users to issue a shift-reload to get the new version of JavaScript code. But with BowerStatic this is safe, because it busts the cache automatically for you. When a new version of a component is installed, the version number is updated, and new URLs are generated by the include mechanism.

[1]Well, for 10 years. But that's forever in web time.

Main endpoint

Bower has a concept of a main end-point for a component in its bower.json. You can include the main endpoint by including the component with its name without any file path after it:

include('jquery')

This includes the file listed in the main field in bower.json. In the case of jQuery, this is the same file as we already included in the earlier examples: dist/jquery.js.

A component can also specify an array of files in main. In this case only the first endpoint listed in this array is included.

The endpoint system is aware of Bower component dependencies. Suppose you include 'jquery-ui':

include('jquery-ui')

The jquery-ui component specifies in the dependencies field in its bower.json that it depends on the jquery component. When you include the jquery-ui endpoint, BowerStatic automatically also include the jquery endpoint for you. You therefore get two inclusions in your HTML:

<script
  type="text/javascript"
  src="/bowerstatic/static/jquery/2.1.1/dist/jquery.js">
</script>
<script
  type="text/javascript"
  src="/bowerstatic/static/jquery-ui/1.10.4/ui/jquery-ui.js">
</script>

If main lists a resource with an extension that has no renderer registered for it, that resource is not included.

WSGI Publisher and Injector

Earlier we described bower.wrap to wrap your WSGI application with the BowerStatic functionality. This is enough for many applications. Sometimes you may want to be able to use the static resource publishing and injecting-into-HTML behavior separately from each other, however.

Publisher

BowerStatic uses the publisher WSGI middleware to wrap a WSGI application so it can serve static resources automatically:

app = bower.publisher(my_wsgi_app)

app is now a WSGI application that does everything my_wsgi_app does, as well as serve Bower components under the special URL /bowerstatic.

Injector

BowerStatic also automates the inclusion of static resources in your HTML page, by inserting the appropriate <script> and <link> tags. This is done by another WSGI middleware, the injector.

You need to wrap the injector around your WSGI application as well:

app = bower.injector(my_wsgi_app)

Wrap

Before we saw bower.wrap. This wraps both a publisher and an injector around a WSGI application. So this:

app = bower.wrap(my_wsgi_app)

is equivalent to this:

app = bower.publisher(bower.injector(my_wsgi_app))

Morepath integration

See static resources with Morepath for information on how the more.static extension helps you use BowerStatic in the Morepath web framework.

Pyramid integration

For integration into the Pyramid web framework, there is a pyramid_bowerstatic extension or you can use djed.static.

Example Flask integration

The Flask web framework does not have a specific extension integrating BowerStatic yet, but you can use BowerStatic's WSGI integration layer to do so. Here is an example of how you integrate BowerStatic with Flask. This code assumes you have a bower_components directory next to this module:

from flask import Flask, request
import bowerstatic
import os.path

app = Flask(__name__)

bower = bowerstatic.Bower()

components = bower.components(
   'components',
    os.path.join(os.path.dirname(__file__), 'bower_components'))

@app.route('/')
def home():
    include = components.includer(request.environ)
    include('jquery')
    # it's important to have head and body elements in the
    # HTML so the includer has a point to include into
    return "<html><head></head><body></body></html>'

if __name__ == "__main__":
    # wrap app.wsgi_wrap, not the Flask app.
    app.wsgi_app = bower.wrap(app.wsgi_app)
    app.run(debug=True)

In the example we used a simple text string but you can use Jinja templates too. No special changes to the templates are necessary; the only thing required is that they have HTML <head>, </head>, <body> and </body> tags so that the includer has a point where it can include the static resources.

Using the Publisher and Injector with WebOb

The Injector and Publisher can also directly be used with WebOb request and response objects. This is useful for integration with web frameworks that already use WebOb:

from morepath import InjectorTween, PublisherTween

def handle(request):
   ... do application's work, returning response ...

# use wrapped_handle instead of handle to handle application
# requests with BowerStatic support
wrapped_handle = PublisherTween(bower, InjectorTween(bower, handle))

All that is required is a WebOb request and a response.

The Morepath and Pyramid integrations mentioned above already make use of this API.