Integrating BowerStatic¶
Introduction¶
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¶
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')
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 times, like above, the html can be constructed with a format string, it is also possible to supply a string. Ie:
bower.register_renderer('.foo', "<foo>{url}</foo>")
url and content are available in the 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 theBower
constructor. components
- The unique name of the
bower_components
directory which you registered with thebower
object. jquery
- The name of the installed component as given by the
name
field inbower.json
. 2.1.1
- The version number of the installed component as given by the
version
field inbower.json
. dist/jquery.js
- A relative path to a file within the component.
Caching¶
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.