This goal of this project is to create a basic skeleton / structure for Single Page Web Applications. It's meant to act as a guide / suggestion for building a Front End Javascript application. Vertebrae is a culmination of working on several applications over the past year. I've found this structure to be very useful, and I hope you will too.
I've incorporated the following technologies into the structure which I consider extremely useful for structuring and driving your front end application:
- Backbone: Thus the title. Brings decently coherent structure to your app
- Underscore: Required for Backbone; adds some nice convenience methods
- Zepto: I generally prefer it over jQuery
- Handlebars: my favorite JS templating engine; has a nice balance between feature set and being light-weight
- RequireJS: Asynchronous module management for your front end code. Really can't say enough good things about it. Once you understand how it works, you'll be writing highly modular, easily testable code in no time.
- Sass: Helps to manage your CSS
Additionally, for development/deployment purposes Vertabrae also includes:
- NodeJS: The only way to run your server-side JS. Required by several build tools (e.g. Grunt)
- Grunt: Fantastic JS-based Task Runner. Used to drive just about everything: tests, handlebars
- Karma: Used to be called 'Testacular'. A JS Test runner that can execute your tests in multiple browsers simultaneously, or go headless via PhantomJS and run in the background
- Jasmine: BDD JS testing
- Bower: Package Manager; use to download libraries rather than check into source code
The overall goal of encouraging the usage of a task runner like Grunt is to reinforce and encourage practices from the server-side world to your front end code.
The project is structured such that all static assets go into the 'assets' folder, and are then broken down into CSS and JS (if we had images, fonts, etc, they'd go here as well). The CSS folder is further broken down into an 'scss' folder, containing the individual sass files.
The JS folder contains the meat of the project. Here, we have the following folders: app (application code), libs (third-party libraries, including a sub-folder for Bower downloaded code), and test (test code). Within app, we break down further into subfolders for 'templates', 'views', 'models', etc. The test app mirrors this structure, but contains "-Spec.js" files.
In addition, index.html gives an example of how to include the libraries to get started.
- Ensure you have [NodeJS][node] (including NPM), Grunt, Bower and Sass installed. Refer to their "getting started" guides to install
- Navigate to the project folder, obviously
- Configure the project to fit your environment / requirements: Update init.js, init.js, and Gruntfile.js to reflect your chosen project structure if different from this
- Install the development dependencies by running:
npm install
(Note: I sometimes have to execute this twice for new enivronments) - Install the production dependencies by running:
bower install
(Note: update the .bowerrc file to change the bower installation path)
At this point you'll be able to proceed with development. The goal is to keep your code as modular as possible: construct your code in the AMD format and rely on Require to handle module loading. For example, consider the following View:
// inject the needed modules
define(["templates", "backbone"], function(templates, Backbone) {
// because they're loaded asynchronously, we cannot guarantee they'll be
// ready until the return object is parsed
return Backbone.View.extend({
// it's minor, but by injecting the templates object we do not have
// to worry about the namespace structure
template: templates.index,
className: "content",
render: function() {
this.$el.append(this.template());
return this;
}
});
});
This module has no knowledge of the outside components (it doesn't know the 'shape' of other components, like the global namespace), beyond the Backbone View and the name of the template. Furthermore, by setting up this module loading pattern we can create mocks during testing.
All templatized markup should be written in Handlebars and saved in assets/js/app/templates/handlebars. A grunt task will process each .hbs file and pre-compile them into one file, hbs_compiled.js
All tests should be written as an AMD module with Jasmine syntax. Each file name should end with 'Spec' (e.g. FooSpec.js). See the test folder for an example
grunt handlebars
: precompile all handlebars templatesgrunt sass
: process the .scss files intoassets/css/style.css
grunt jshint
: run jsHint through all specified filesgrunt requirejs
: compile the source JS files into one file,assets/js/compiled-app.js
grunt karma
: execute the unit tests. By default this uses PhantomJS, and will watch your js files continously. Any change will re-run all testsgrunt
: the default task will performgrunt jshint
andgrunt karma
and finally:
grunt watch
: this will run a continuous process which watches the .scss and .hbs files, then processes them appropriately if anything changes.
I highly recommend having two terminals open during development, one dedicated to grunt watch
and the other to ``grunt karma```. This will alert you immediately of any broken tests, and will automatically refresh your css and templates without you worrying about it after every change.
The current setup uses Sass and the RequireJS Optimizer to compile the SCSS into CSS and concatinate & minify the source JS files into one main JS file.
While the SASS task is set to run automatically with grunt watch
, the JS compilation is not. I recommend using the source files as part of development, but run the complication as part of your dpeloyment build.
Currently, each task only creates one .css and one .js file, but this could be expanded to create several modules, useful in a larger multi-single page app, or if one wants to create multiple 'bundles' of their compiled files for different pages within a larger application (as opposed to loading all JS all the time).
The grunt requirejs
task creates a compiled version at assets/js/compiled-app.js
. The test index.html file loads RequireJS, then loads assets/js/init.js
, the main configuration / initialization file.
In order to use the concatenated & minifed version, one must tell Require to load it instead. This is done by editing index.html and replacing:
<script type="text/javascript" src="/assets/js/libs/bower_components/requirejs/require.js" data-main="/assets/js/init"></script>
with
<script type="text/javascript" src="/assets/js/libs/bower_components/requirejs/require.js" data-main="/assets/js/compiled-app"></script>
The compiled CSS and JS files are placed directly in the assets/css and assets/js folders, intermingled with the src code. This could perhaps be separated out into dedicated src
folders for the SCSS and non-compiled JS code.