This is Part 2 in a series of blogs about various bits and pieces in the tornado-utils package. Part 1 is here
tornado_static
This code takes care of two things: 1) optimizing your static resources and 2) bundling and serving them with unique names so you can cache aggressively.
The trick is to make your development environment such that there's no need to do anything when in "debug mode" but when in "production mode" it needs to be perfect. Which files (e.g. jquery.js or style.css) you use and bundle is up to you and it's something you control from the templates in your Tornado app. Not a config setting because, almost always, which resources (aka. assets) you need is known and relevant only to the templates where you're working.
Using UI modules in Tornado requires a bit of Tornado-fu but here's one example and here is another (untested) example:
# app.py
import tornado.web
from tornado_utils.tornado_static import (
StaticURL, Static, PlainStaticURL, PlainStatic)
class Application(tornado.web.Application):
def __init__(self):
ui_modules = {}
if options.debug:
ui_modules['Static'] = PlainStatic
ui_modules['StaticURL'] = PlainStaticURL
else:
ui_modules['Static'] = Static
ui_modules['StaticURL'] = StaticURL
app_settings = dict(
template_path="templates",
static_path="static",
ui_modules=ui_modules,
debug=options.debug,
UGLIFYJS_LOCATION='~/bin/uglifyjs',
CLOSURE_LOCATION="static/compiler.jar",
YUI_LOCATION="static/yuicompressor-2.4.2.jar",
cdn_prefix="cdn.mycloud.com",
)
handlers = [
(r"/", HomeHandler),
(r"/entry/([0-9]+)", EntryHandler),
]
super(Application, self).__init__(handlers, **app_settings)
def main(): # pragma: no cover
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
Note! If you're looking to optimize your static resources in a Tornado app you probably already have a "framework" for setting up UI modules into your app. The above code is just to wet your appetite and to show how easy it is to set up. The real magic starts to happen in the template code. Here's a sample implementation:
<!doctype html>
<html>
<head>
<title>{% block title %}{{ page_title }}{% end %}</title>
<meta charset="utf-8">
{% module Static("css/ext/jquery.gritter.css", "css/style.css") %}
</head>
<body>
<header>...</header>
{% block content %}
{% end %}
{% module Static("js/ext/head.load.min.js") %}
<script>
var POST_LOAD = '{% module StaticURL("js/dojo.js", "js/dojo.async.js") %}';
</script>
</body>
</html>
What you get when run is a template that looks like this:
<!doctype html>
<html>
<head>
<title>My title</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="//cdn.mycloud.com/combined/jquery.gritter.css.style.1313206609.css">
</head>
...
(Note that this will create a whitespace optimized filed called "jquery.gritter.css.style.1313206609.css" in "/tmp/combined")
Have a play and see if it makes sense in your app. I do believe this can do with some Open Source love but so far it works great for me on Kwissle, DoneCal and TooCoolFor.Me