by Cameron Wardzala
When I set out to integrate Bower and Grunt into our current Python projects I wasn’t looking to upset the balance, but instead improve the process for front-end development. Integrating these tools allows us to lighten our repository by moving third party dependencies to Bower, along with giving us easier access to build tools like Require.js and Sass. Ultimately these tools give us the ability to deploy optimized assets without the need to check them into git and manage changes to built files across branches.
File Structure and setup
With our current python file structure, we have a nice setup for integrating the new tools. There are a number of files that need to be added to utilize the new tools.
Setting up Bower is simple.
$ bower init
Answer the prompts to generate your bower.json.
After you have a bower.json you will need to create a .bowerrc to tell Bower where to install components.
In your favorite editor open .bowerrc and add the following.
This tells Bower to install its components into a bower_components folder under the subdirectory AppName. If you didn’t do this, your site would not have access to the installed components because they are outside the web server root.
Now you are free to install components.
$ bower install --save jquery
Once you have bower you can start installing components, but you need to be able to build them. Enter Grunt.
We first need to create a package.json to outline what node packages we need.
We will be using a few grunt plugins to handle creating tasks for us.
- node-sass – Implements Node bindings for libsass.
- grunt-contrib-watch – tasks for watching files and executing tasks when changes occur.
- grunt-sass – tasks for building Sass with node-sass.
- load-grunt-tasks – automatically load grunt tasks from installed plugins.
- time-grunt – display execution summary on end.
Once we have our package.json created and dependencies declared we can install those dependancies with one command.
$ npm install
After our dependancies are installed we can write our Gruntfile.
While there is a lot going on in this Gruntfile.js, there are a few key items to explain.
Running the server and building sass
We have a custom Grunt task called ‘runApp’ that will kick off our python server using the ‘run_appname.sh’ bash script.
The task starts by spawning a child process using Node’s built in child process tasks, and runs the ‘run_appname.sh’ script. We don’t need to pass any arguments to the script, but we do set the cwd (current working directory) with the options argument. Additionally we log out all events from the child process, stdout, stderr, data, and close.
To run the server in tandem with the watch task we have a combined task called ‘server’ that will first start the python server and second start the watch to rebuild JS and CSS files when they change.
Building and deploying
$ grunt build
While this simplifies things a little it also adds some overhead to our server stack. In addition to needing Python we also need Node and NPM.
The typical deployment workflow would be as follows.
- fetch the latest from origin
- pull the latest code from the master branch
- activate the virtualenv and update/install any python packages
- install or update node packages
- install or update bower components
- build the latest css and js using grunt
- restart the service/application using supervisord
This is a simple deploy workflow, there could be more complex steps involved like tagging new and old releases, etc…
To configure the views to use either development or production versions of our files we use a ‘Config’ python class. Imported from ‘/AppName/common/config.py’ as ‘CONFIG’.
This sets up our configuration variables, of which we have a ‘js’ object.
The ‘CONFIG.js’ object contains two properties.
- ‘loader’ – the loader script to use (require.js, almond.js, etc AMD loader…)
The config.py values can be overridden for local development via ‘/AppName/common/local_config.py’.
The local config is never checked into version control, allowing everyone to configure the app for their environment without conflict.
In our route callback we return the ‘CONFIG.js’ data to the views as the ‘js’ property, along with any other view data.
In our view we output the correct loader and main file using the ‘js.loader’ and ‘js.main’ properties respectively.
As we begin to use these tools on more projects I suspect more changes/improvements will happen. This first attempt was to simply integrate tools we are accustomed to using on other simpler projects into more advanced projects that utilize server languages. As we improve this integration I would like to see us attempt to follow this process with other languages like PHP, and frameworks like Magento.