Creating a Resource Library¶
We’ve seen how to reuse existing resources, but how do you publish your own resources using Fanstatic?
Here’s how:
Your project¶
So, you’re developing a Python project. It’s set up in the standard Python way, along these lines:
fooproject/
setup.py
foo/
__init__.py
Making Fanstatic available in your project¶
In order to be able to import from fanstatic
in your project,
you need to make it available first. The standard way is to include it
in setup.py
, like this:
install_requires=[
'fanstatic',
]
Adding the resource directory¶
You need to place the resources in a subdirectory somewhere in your Python code.
Imagine you have some resources in a directory called
bar_resources
. You simply place this in your package:
fooproject/
setup.py
foo/
__init__.py
bar_resources/
a.css
b.js
Note that bar_resources
isn’t a Python package, so it doesn’t have
an __init__.py
. It’s just a directory.
Declaring the Library¶
You need to declare a Library
for bar
. In
__init__.py
(or any module in the package), write the following:
from fanstatic import Library
bar_library = Library('bar', 'bar_resources')
Here we construct a fanstatic Library named bar
, and we point to
the subdirectory bar_resources to find them.
Hooking it up to an entry point¶
To let Fanstatic know that this library exists so it will
automatically publish it, we need to add an entry point for the
library to your project’s setup.py
. Add this to the setup()
function:
entry_points={
'fanstatic.libraries': [
'bar = foo:bar_library',
],
},
This tells Fanstatic that there is a Library
instance in the
foo
package. What if you had defined the library not in
__init__.py
but in a module, such as foo.qux
? You would have
referred to it using foo.qux:bar_library
.
At this stage, Fanstatic can serve the resources in your library. The default URLS are:
/fanstatic/bar/a.css
/fanstatic/bar/b.js
Declaring resources for inclusion¶
While now the resources can be served, we can’t actually yet
.need()
them, so that we can have Fanstatic include them on web
pages for us. For this, we need to create Resource
instances. Let’s modify our original __init__.py
to read like
this:
from fanstatic import Library, Resource
bar_library = Library('bar', 'bar_resources')
a = Resource(bar_library, 'a.css')
b = Resource(bar_library, 'b.js')
Now we’re done!
Depending on resources¶
We can start using the resources in our code now. To make sure
b.js
is included in our web page, we can do this anywhere in our
code:
from foo import b
...
def somewhere_deep_in_our_code():
b.need()
An example¶
Need an example where it’s all put together? We maintain a Fanstatic
package called js.jquery
that wraps jQuery this way:
It’s also available on PyPI:
Bonus: shipping the library¶
You can declare any number of libraries and resources in your
application. What if you want to reuse a library in multiple
applications? That’s easy too: you just put your library, library
entry point, resource definitions and resource files in a separate
Python project. You can then use this in your application projects. If
it’s useful to other as well, you can also publish it on PyPi! The
various js.*
projects that we are maintaining for Fanstatic, such
as js.jquery
, are already examples of this.
Bonus: dependencies between resources¶
What if we really want to include a.css
whenever we pull in
b.js
, as code in b.js
depends on it? Change your code to this:
from fanstatic import Library, Resource
bar_library = Library('bar', 'bar_resources')
a = Resource(bar_library, 'a.css')
b = Resource(bar_library, 'b.js', depends=[a])
Whenever you .need()
b
now, you’ll also get a
included on
your page.
You can also use a Group
to group Resources together:
from fanstatic import Group
c = Group([a, b])
Bonus: a minified version¶
What if you have a minified version of your b.js
Javascript called
b.min.js
available in the bar_resources
directory and you want
to let Fanstatic know about it? You just write this:
from fanstatic import Library, Resource
bar_library = Library('bar', 'bar_resources')
a = Resource(bar_library, 'a.css')
b = Resource(bar_library, 'b.js', minified='b.min.js')
If you now configure Fanstatic to use the minified
mode, it will
automatically pull in b.min.js
instead of b.js
whenever you do
b.need()
.
The minified files can also be created automatically, see Compilers and Minifiers for details on that.
Bonus: preprocessing resources¶
If you prefer, say, CoffeeScript to JavaScript, you can have Fanstatic run the coffeescript compiler automatically:
from fanstatic import Library, Resource
baz_library = Library('baz', 'baz_resources')
a = Resource(baz_library, 'a.js', compilers={'js': 'coffee'})
See Compilers and Minifiers for detailed information on that.
Bonus: bundling of resources¶
Bundling of resources minimizes the amount of HTTP requests from a web page. Resources from the same Library can be bundled up into one, when they have the same renderer. Bundling is disabled by default. If you want bundling, set bundle to True:
from fanstatic import Library, Resource, Inclusion
qux_library = Library('qux', 'qux_resources')
a = Resource(qux_library, 'a.css')
b = Resource(qux_library, 'b.css')
needed = fanstatic.init_needed()
a.need()
b.need()
Inclusion(needed, bundle=True)
The resulting URL looks like this:
http://localhost/fanstatic/qux/:bundle:a.css;b.css
The fanstatic publisher knows about bundle URLs and serves a bundle of the two files.
If you don’t want your Resource to be bundled, give it the dont_bundle
argument.:
c = Resource(qux_library, 'a.css', dont_bundle=True)
Resources are bundled based on their Library. This means that bundles don’t span Libraries. If we were to allow bundles that span Libraries, we would get inefficient bundles. For an example look at the following example situation.:
from fanstatic import Library, Resource
foo = Library('foo', 'foo')
bar = Library('bar', 'bar')
a = Resource(foo, 'a.js')
b = Resource(bar, 'b.js', depends=[a])
c = Resource(bar, 'c.js', depends=[a])
If we need() resource b in page 1 of our application and would allow cross-library bundling, we would get a bundle of a + b. If we then need only resource c in page 2 of our application, we would render a bundle of a + c. In this example we see that cross-library bundling can lead to inefficient bundles, as the client downloads 2 * a + b + c. Fanstatic doesn’t do cross-library bundling, so the client downloads a + b + c.
When bundling resources, things could go haywire with regard to relative URLs in CSS files. Fanstatic prevents this by taking the dirname of the Resource into account:
from fanstatic import Library, Resource
foo = Library('foo', 'foo')
a = Resource(foo, 'a.css')
b = Resource(foo, 'sub/sub/b.css')
Fanstatic won’t bundle a and b, as b may have relative URLs that the browser would not be able to resolve. We could rewrite the CSS and inject URLs to the proper resources in order to have more efficient bundles, but we choose to leave the CSS unaltered.