Creating a Resource Library =========================== .. py:module:: fanstatic 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 :py:class:`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``. .. _`entry point`: http://reinout.vanrees.org/weblog/2010/01/06/zest-releaser-entry-points.html 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 :py:class:`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: https://github.com/fanstatic/js.jquery It's also available on PyPI: http://pypi.python.org/pypi/js.jquery 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. .. _PyPi: http://pypi.python.org 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 :py:class:`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 :doc:`compilers` 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 :doc:`compilers` for detailed information on that. .. _`CoffeeScript`: http://coffeescript.org 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.