{"id":1525,"date":"2015-03-06T15:50:39","date_gmt":"2015-03-06T14:50:39","guid":{"rendered":"http:\/\/blog.gocept.com\/?p=1525"},"modified":"2015-03-06T15:50:39","modified_gmt":"2015-03-06T14:50:39","slug":"manage-javascript-dependencies-with-bowerstatic","status":"publish","type":"post","link":"https:\/\/blog.gocept.com\/2015\/03\/06\/manage-javascript-dependencies-with-bowerstatic\/","title":{"rendered":"Manage JavaScript dependencies with BowerStatic"},"content":{"rendered":"
Last month I explained how to use Fanstatic to manage JS dependencies<\/a>. Since we were more and more displeased by using Fanstatic<\/a>, we recently switched to BowerStatic<\/a>, the new kid on the block. Since the setup is a bit more complicated and you need more tools to have the same features as you had with Fanstatic, I will describe how to set up the full toolchain. After that, I will shortly summarize the benefits and drawbacks of BowerStatic, so you can decide for yourself if you prefer BowerStatic above Fanstatic or the other way around.<\/p>\n Of course the choices made in the toolchain of BowerStatic are strongly linked to other tools we use. Since we set up large projects using Buildout<\/a>, we like to use Buildout recipes to solve a problem. Therefore this blogpost is most interesting when you are using Buildout yourself.<\/p>\n BowerStatic was created by Martijn Faassen<\/a>, who was a core developer of Fanstatic. We think of BowerStatic as a reimagined version of Fanstatic, avoiding disadvantageos decisions made in the past. One problem of Fanstatic was, that it tried to do too many things: Fetching the files, delivering them to the client and bundling them into one large file.<\/p>\n Therefore BowerStatic has a narrow focus: It only helps to manage which libraries should be delivered to the client. It does not care how the files were retrieved or whether you want to have a bundled resource. To automate these steps as well, you must look for additional tools.<\/p>\n We use the following toolchain:<\/p>\n Since you need all of these tools to have the same features as Fanstatic, I will give a short description, how to set things up.<\/p>\n Bower<\/a> is a small tool to download libraries and their dependencies by using a simple description of the library in JSON, which is named The great thing about Bower is, that you just point to a github repository in case this certain library is not registered on the Bower server. In this case it will check out the trunk of the repository and creates the It’s even better if the repository contains a To download external dependencies via Bower, we added bowerrecipe<\/a> to our Buildout configuration. This way we can just list the libraries and their version in the recipe configuration. Bower will automatically download those libraries, as well as their dependencies. The following example will download Bootstrap and jQuery, since Bootstrap depends on jQuery.<\/p>\n By default the libraries will be stored inside BowerStatic<\/a> uses libraries downloaded via Bower to resolve JS dependencies and delivers all required files to the client. To do so, it must be registered as a WSGI middleware. Compared to Fanstatic, the setup of BowerStatic is a little bit more complicated:<\/p>\n This may sound like voodoo to you. The documentation of BowerStatic<\/a> contains some insightful statements why the setup is so complicated. Written in code the setup will look like this:<\/p>\n Let’s assume in Then you have the JS library called Since we were used to Fanstatics automatism to bundle and minify all resources, we wanted to have something similar with BowerStatic. It is not sure if BowerStatic will ever contain bundling mechanisms, since this may be out of the narrow scope it wants to preserve.<\/p>\n Therefore we build our own Buildout recipe, gocept.recipe.bowerstaticbundler<\/a>. This recipe will create another Bower library in To use the bundled library in production and the separate files in development mode, we build a custom include with an environment variable switch:<\/p>\n Of course you must use this include method instead of using the BowerStatic component directly, i.e. replace alo occurences of This solution is good enough for ourselves, but may not be ready for your purposes. We have build it with a certain project in mind, so you might come across bugs when using it with different JS libraries. Since we will soon use it in production, we will happily fix bugs you report and merge tested improvements. You can find the code and issue tracker on bitbucket<\/a>.<\/p>\n The combination of Bower, BowerStatic and a bundling mechanism has the same benefits as Fanstatic, i.e. that you no longer need to copy JS files into your project and link them statically, as well as the benefits of bundled resources to speed up the initial page load.<\/p>\n The biggest contrast of these solutions is how external libraries have to be prepared: With Fanstatic you had to build an integration package for each library and release a version on PyPI for each version of the library. With BowerStatic it’s enough to have a current How it works<\/h2>\n
\n
Bower<\/h2>\n
bower.json<\/code>. The description contains a version string, the path to contained files, a list of libraries it depends on and a lot more metadata like author, homepage, keywords and so on. For example:<\/p>\n
\r\n{\r\n \"name\": \"bootstrap\",\r\n \"version\": \"3.3.2\",\r\n \"main\": [\r\n \"less\/bootstrap.less\",\r\n \"dist\/css\/bootstrap.css\",\r\n \"dist\/js\/bootstrap.js\",\r\n \"dist\/fonts\/glyphicons-halflings-regular.eot\",\r\n \"dist\/fonts\/glyphicons-halflings-regular.svg\",\r\n \"dist\/fonts\/glyphicons-halflings-regular.ttf\",\r\n \"dist\/fonts\/glyphicons-halflings-regular.woff\"\r\n ],\r\n \"dependencies\": {\r\n \"jquery\": \">= 1.9.1\"\r\n }\r\n}\r\n<\/pre>\n
bower.json<\/code> all by itself using the commit hash as it’s version. Of course it cannot auto-detect dependencies, which are normally declared in the
bower.json<\/code>.<\/p>\n
bower.json<\/code> file. In this case Bower will only load the files mentioned there and can retrieve dependencies. Therefore it makes no difference if you point Bower to a registered package, e.g.
jquery-ui<\/code>, or if you point it to the github repository of this project, e.g.
github.com\/jquery\/jquery-ui<\/code>. Of course the version will be different (stable release vs trunk).<\/p>\n
\r\n[bower]\r\nrecipe = bowerrecipe\r\npackages =\r\n bootstrap#3.3.x\r\nexecutable = ${buildout:bin-directory}\/bower\r\n<\/pre>\n
parts\/bower\/downloads<\/code>. The destination can be changed inside the recipe configuration.<\/p>\n
BowerStatic<\/h2>\n
\n
parts\/bower<\/code><\/li>\n
bower.json<\/code> at given path<\/li>\n<\/ol>\n
\r\nimport bowerstatic\r\nimport os\r\nimport pkg_resources\r\n\r\nbower = bowerstatic.Bower()\r\nexternal_components = bower.components(\r\n name='components', path='parts\/bower\/downloads')\r\nlocal_components = bower.local_components(\r\n name='local', component_collection=external_components)\r\n\r\nlocal_components.component(path=pkg_resources.resource_filename(\r\n 'my.cool.package', 'resources'), version=None)\r\n<\/pre>\n
my.cool.package.resources<\/code> is a
bower.json<\/code> like:<\/p>\n
\r\n{\r\n \"name\": \"custom\",\r\n \"version\": \"0.1\",\r\n \"main\": [\r\n \"custom.js\",\r\n \"custom.css\",\r\n ],\r\n \"dependencies\": {\r\n \"bootstrap\": \"*\"\r\n }\r\n}\r\n<\/pre>\n
custom<\/code> in your local component collection. After handling this setup, you can use BowerStatic similar to Fanstatic, by including JS files inside any view. However, you always need the component collection to do so.
local_components.include(self.request.environ, 'custom')<\/code> will load
custom.js<\/code>,
custom.css<\/code>, the bootstrap library and the jQuery library. You could also write
local_components.include(self.request.environ, 'jquery')<\/code>, which would only load jQuery.<\/p>\n
Bundling<\/h2>\n
parts\/bower<\/code>, which contains a minified JS file, a minified CSS file and assets like images and fonts. By using the dependency mechanism of BowerStatic, all JS files are bundled in the right order.<\/p>\n
\r\ndef bower_include(environ, name):\r\n if not os.environ.get('BOWERSTATIC_DEBUG'):\r\n name = 'bowerstatic_bundle_' + name.replace('.', '_')\r\n include = local_components.includer(environ)\r\n include(name)\r\n<\/pre>\n
local_components.include(environ, name)<\/code> with
bower_include(environ, name)<\/code>.<\/p>\n
Benefits<\/h2>\n
bower.json<\/code> inside the library. So both need some kind of work and maintenance.<\/p>\n