Skip to content

Commit

Permalink
Added documentation for versions.
Browse files Browse the repository at this point in the history
  • Loading branch information
miracle2k committed Mar 9, 2012
1 parent acefe50 commit a79aa29
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 35 deletions.
4 changes: 3 additions & 1 deletion docs/bundles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ arguments:
applied in the order in which they are given.

* ``output`` - Name/path of the output file. All source files will be merged
and the result stored at this location.
and the result stored at this location. A ``%(version)s`` placeholder is
supported here, which will be replaced with the version of the file. See
:doc:`/expiring`.


Nested bundles
Expand Down
10 changes: 7 additions & 3 deletions docs/django/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ in your project's global settings.
.. autodata:: ASSETS_DEBUG
:noindex:

.. autodata:: ASSETS_UPDATER
.. autodata:: ASSETS_AUTO_BUILD
:noindex:

.. _django-setting-expire:
.. autodata:: ASSETS_URL_EXPIRE
:noindex:

.. autodata:: ASSETS_VERSIONS
:noindex:

.. autodata:: ASSETS_EXPIRE
.. autodata:: ASSETS_MANIFEST
:noindex:

.. autodata:: ASSETS_CACHE
Expand Down
8 changes: 5 additions & 3 deletions docs/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ The environment supports the following configuration options:

.. autoattribute:: webassets.env.Environment.debug

.. autoattribute:: webassets.env.Environment.updater
.. autoattribute:: webassets.env.Environment.auto_build

.. _environment-setting-expire:
.. autoattribute:: webassets.env.Environment.url_expire

.. autoattribute:: webassets.env.Environment.expire
.. autoattribute:: webassets.env.Environment.versions

.. autoattribute:: webassets.env.Environment.manifest

.. autoattribute:: webassets.env.Environment.cache

Expand Down
189 changes: 189 additions & 0 deletions docs/expiring.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
.. _expiry:


URL Expiry (cache busting)
==========================

For beginners
-------------

You are using ``webassets`` because you care about the performance of your
site. For the same reason, you have configured your web server to send out
your media files with a so called *far future expires* header: Your web server
sets the ``Expires`` header to some date many years in the future. Your user's
browser will never spend any time trying to retrieve an updated version.

.. note::

Of course, the user's browser will already use the ``Etag`` and
``Last-Modified/If-Modified-Since`` to avoid downloading content it has
already cached, and if your web server isn't misconfigured entirely, this
will work. The point of *far future expires* is to get rid of **even**
those requests which would return only a ``304 Not Modified`` response.

What if you actually deploy an update to your site? Now you need to convince
the browser to download new versions of your assets after all, but you have
just told it not to bother to check for new versions. You work around this by
*modifying the URL with which the asset is included*. There are two distinct
ways to so:

1) Append a version identifier as a querystring::

http://www.example.org/media/print.css?acefe50

2) Add a version identifier to the actual filename::

http://www.example.org/media/print.acefe50.css

How webassets helps you do this is explained in the sections below.

.. note::

Even if you are not using *far future expires* headers, you might still find
``webassets`` expiry features useful to navigate around any funny browser
caching behaviour that might require a ``Shift``-reload.


What is the version of a file
-----------------------------

To expire an URL, it is modified with a version identifier. What is this
identifier? By default, ``webassets`` will create an MD5-hash of the file
contents, and use the first few characters as the file version. ``webassets``
also allows you to use the *last modified* timestamp of the file. You can
configure this via the ``versions`` option::

env = Environment(...)
env.versions = 'hash' # the default
env.versions = 'hash:32' # use the full md5 hash
env.versions = 'timestamp' # use the last modified timestamp

It is generally recommended that you use a hash as the version, since it will
remain the same as long as the content does not change, regardless of any
filesystem metadata, which can change for any number of reasons.


Expire using a querystring
--------------------------

``webassets`` will automatically add the version as a querystring to the urls
it generates, by virtue of the ``url_expire`` option defaulting to ``True``.
If you want to be explicit::

env = Environment(...)
env.url_expire = True

There is nothing else you need to do here. The URLs that are generated might
look like this::

/media/print.css?acefe50

However, while the default, expiring with a querystring is not be the best
option:


Expire using the filename
-------------------------

Adding the version as a querystring has two problems. First, it may not always
be a browser that implements caching through which we need to bust. It is said
that certain (possibly older) proxies do ignore the querystring with respect
to their caching behavior.

Second, in certain more complex deployment scenarios, where you have multiple
frontend and/or multiple backend servers, an upgrade is anything but
instantaneous. You need to be able to serve both the old and the new version
of your assets at the same time. See for example how this affects you `when
using Google App Engine <http://bjk5.com/post/4918954974/js-css-packaging-to-minimize-requests-and-randomly-evil>`_.

To expire using the filename, you add a ``%(version)s`` placeholder to your
bundle output target::

bundle = Bundle(..., output='screen.%(version)s.css')

The URLs that are generated might look like this::

/media/screen.acefe50.css

.. note::

``webassets`` will use this modified filename for the actual output files
it writes to disk, as opposed to just modifying the URL it generates. You
do not have to configure your web server to do any rewriting.


About manifests
---------------

.. note::

This is mostly an advanced feature, and you might not have to bother with
it at all.

``webassets`` supports Environment-wide *manifests*. A manifest remembers the
current version of every bundle. What is this good for?

1) Speed. Calculating a hash can be expensive. Even if you are using
timestamp-based versions, that still means a stat-request to your disk.

.. note::

Note that even without a manifest, ``webassets`` will cache the version
in memory. It will only need to be calculated once per process. However,
if you have *many* bundles, and a very busy site, a manifest will allow
you to both skip calculating the version (e.g. creating a hash), as well
as read the versions of all bundles into memory at once.

.. note::

If you are using automatic building, all of this is mostly not true. In
order to determine whether a rebuild is required, ``webassets`` will need
to check the timestamps of all files involved in any case. It goes
without saying that using automatic building on a production site is a
convenience feature for small sites, and at odds with counting paper
clips in the form of filesystem ``stat`` calls.

2) Making it possible to know the version in the first place.

Depending on your configuration and deployment, consider that it might not
actually be possible for ``webassets`` to know what the version is.

If you are using a hash-based version, and your bundle's output target has
a placeholder, there is no way to know what the version is, *unless* is
has been written to a manifest during the build process.

The timestamp-based versioning mechanism can actually look at the source
files to determine the version. But, in more complex deployments, the source
files might not actually be available to read - they might be on a
completely different server altogether.

A manifest allows version information to be persisted.


In practice, by default the version information will be written to the cache.
You can explicitly request this behaviour be setting the ``manifest`` option::

env = Environment(...)
env.manifest = 'cache'

In a simple setup, where you are separately building on your local machine
during development, and building on the web server for production (maybe via
the automatic building feature, enabled by default), this is exactly would
you want. Don't worry about it.

There is a specific deployment scenario where you want to prebuild your bundles
locally, and for either of the two reasons above want to include the version
data pre-made when you deploy your app to the web server. In such a case, it
is not helpful to have the versions stored in the cache. Instead, ``webassets``
provides a manifest type that writes all information to a single file::

env = Environment(...)
env.manifest = 'file'
env.manifest = 'file:/tmp/manifest.to-be-deployed' # explict filename

You can then just copy this one file to the web server, and ``webassets``
will know all about the versions without having to consult the media files.

.. note::

The file is a pickled dict.
24 changes: 8 additions & 16 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ FAQ
Is there a cache-busting feature?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Yes! It's turned on by default. See the
:ref:`Environment.expire <environment-setting-expire>`
option (or :ref:`ASSETS_EXPIRE <django-setting-expire>` if using
``django_assets``).
Yes! See :doc:`/expiring`.


Relative URLs in my CSS code break if the merged asset is written to a different location than the source files. How do I fix this?
Expand All @@ -28,20 +25,15 @@ See :doc:`css_compilers` for how this is best done.
Is Google App Engine supported?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It generally works, though further improvements are planned. Due to the
way Google App Engine works (static files are stored on separate servers),
you need to build your assets locally, possibly using one of the management
commands provided for your preferred framework, and then deploy them.
Yes. Due to the way Google App Engine works (static files are stored on
separate servers), you need to build your assets locally, possibly using one
of the management commands provided for your preferred framework, and then
deploy them.

In production mode, you therefore want to disable the
``Environment.updater``/``ASSETS_UPDATER`` setting.
In production mode, you need to disable the ``Environment.auto_build`` setting.

Further, you currently need to disable
``Environment.expire``/``ASSETS_EXPIRE`` for webassets to work on Google's
servers. This means you will not get url expiration functionality. This will
be fixed in the future. In the meantime, you can write some custom code
to provide the feature. See `this gist <https://gist.github.com/1307521>`_
for an example.
For URL expiry functionality, you need to use a manifest that holds version
information. See :doc:`/expiring`.

There is a barebone Google App Engine example in the
`examples/appengine/ <https://github.com/miracle2k/webassets/blob/master/tests/>`_
Expand Down
4 changes: 2 additions & 2 deletions docs/generic/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ you do this depends a bit on how your site is rendered.
.. code-block:: python
>>> my_env['js_all'].urls()
('../static/media/gen/packed.js',)
('../static/media/gen/packed.js?9ae572c',)
This will always work. You can call your bundle's ``urls()`` method, which
will automatically merge and compress the source files, and return the
Expand Down Expand Up @@ -95,4 +95,4 @@ Further Reading
/css_compilers
/loaders
/integration/index
/faq
/faq
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ of framework used:
bundles
script
builtin_filters
expiring
custom_filters
css_compilers
loaders
integration/index
faq
upgrading
upgrading
6 changes: 3 additions & 3 deletions examples/appengine/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

env = Environment(path.join(path.dirname(__file__), 'static'), '/stylesheets')
# App Engine doesn't support automatic rebuilding.
env.updater = False
# URL expiry not currently supported on App Engine
env.expire = False
env.auto_build = False
# This file needs to be shipped with your code.
env.manifest = 'file'

bundle = Bundle('in.css', filters="cssmin", output="out.css")
env.add(bundle)
Expand Down
6 changes: 4 additions & 2 deletions src/django_assets/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ def __init__(self, object, append=None):

ASSETS_DEBUG = Environment.debug
ASSETS_CACHE = Environment.cache
ASSETS_UPDATER = Environment.updater
ASSETS_EXPIRE = Environment.expire
ASSETS_AUTO_BUILD = Environment.auto_build
ASSETS_URL_EXPIRE = Environment.url_expire
ASSETS_MANIFEST = Environment.manifest
ASSETS_VERSIONS = Environment.versions
ASSETS_URL = docwrap(Environment.url, """\n\nBy default, ``STATIC_URL``
will be used for this, or the older ``MEDIA_URL`` setting.""")
ASSETS_ROOT = docwrap(Environment.directory, """\n\nBy default,
Expand Down
7 changes: 3 additions & 4 deletions src/webassets/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,6 @@ def _get_cache(self):
*custom path*
Use the given directory as the cache directory.
Note: Currently, the cache is never used while in production mode.
""")

def _set_auto_build(self, value):
Expand Down Expand Up @@ -325,7 +323,8 @@ def _get_versions(self):
A bundle's version is what is appended to URLs when the
``url_expire`` option is enabled, and the version can be part
of a Bundle's output filename by use of the %(version)s placeholder.
of a Bundle's output filename by use of the ``%(version)s``
placeholder.
Valid values are:
Expand Down Expand Up @@ -384,7 +383,7 @@ def _get_url_expire(self):
webassets will have their version (see ``Environment.versions``)
appended as a querystring.
An alternative approach would be to use the %(version)s
An alternative approach would be to use the ``%(version)s``
placeholder in the bundle output file.
By default, this option is enabled.
Expand Down

0 comments on commit a79aa29

Please sign in to comment.