Skip to main content

The Cheeky Monkey Media Blog

A few words from the apes, monkeys, and various primates that make up the Cheeky Monkey Super Squad.

Require JS, Grunt, Bower, Foundation, Compass and Drupal - Let’s just be friends, with benefits.

Require JS, Grunt, Bower, Foundation, Compass and Drupal - Let’s just be friends, with benefits.

In depth, but not too deep

Okay, so you want to use RequireJS to manage and organize your JS and all the dependencies, in a modular way. You also don’t want deal with RequireJS caching the heck out of your JS changes during development, otherwise...Derpal..  but, you do if you’re ready for production.

Also...  Compiling with Grunt (compass), component package management with Bower, and having it not interfere with Drupal. (.info files… we’ll get to that).

And finally, I’m not a fan of installing ruby gems and all that globally on your machine. We want this specific to the project, and keeping the versions in sync, with that Gemlock file that gets generated after the “bundle install”, which installs those ruby gems.

What we’re gonna do:

  1. Set up our files and structure for front-end development, and having it work with foundation, but not interfere with other theme related stuff.
  2. Set Up some PHP to have drupal differentiate between a ‘dev’ environment and production, as well as getting a form of cache busting in there for Require.
  3. Setup RequireJS, and make use of that cache busting.
  4. Get a sample Require JS module running, that will only load the needed JS files related to the homepage.

First thing’s first.

From what I’ve seen, it’s been common practice to have all your FE (front-end) related files in the theme folder, including your gruntfile.js, package.json, Gemfile.. etc.. Problems can arise from this.  Some bower or node packages contain .info files that aren’t Drupal standard .info files.

Drupal unfortunately recursively scans all your sub folders, and if it finds additional .info files (which would be installed along with node or bower packages) it will read them and derp out.  You can expect to find very weird problems, like not being able to install drupal or the theme properly, or unexplainable php errors.

Below is a link to a thread where people are proposing solutions.. But we’re doing things differently, so we won’t run into the issue at all.

https://www.drupal.org/node/2329453

Our Solution.  -- Don’t use the theme folder for all your setup and compiling related files.

Instead, I simply like to use a custom subfolder off of Drupal’s root folder.  Since everything is “user interface” related, it makes sense to call the folder “UI”.

So we have: [drupal-root-folder]/UI

Alright, now we can begin to put all of our front-end compiling related files in there.

Let’s start with our Node Modules, we’ll need a bunch, to get all this working.  We first need a ‘package.json’ file that will contain a list of all the node modules and their version.

File: [drupal-root-folder]/UI/package.json

  1. {
  2. "name": "cmmdrupalboiler",
  3. "version": "0.1.0",
  4. "private": true,
  5. "config": {
  6. "unsafe-perm": true
  7. },
  8. "scripts": {
  9. "install": "bundle install --path ruby_gems && bower install"
  10. },
  11. "devDependencies": {
  12. "load-grunt-tasks": "~0.3.0",
  13. "grunt": "~0.4.5",
  14. "grunt-contrib-watch": "~0.6.1",
  15. "grunt-contrib-compass": "~1.0.3",
  16. "grunt-contrib-cssmin": "~0.8.0",
  17. "grunt-contrib-jshint": "~0.8.0",
  18. "grunt-contrib-clean": "~0.5.0",
  19. "grunt-contrib-requirejs": "~0.4.4",
  20. "grunt-contrib-uglify": "~0.3.3",
  21. "grunt-contrib-copy": "~0.8.0",
  22. "node-bourbon": "~1.2.3"
  23. }
  24. }

Looks like a pretty standard package file. You may have noticed the ‘scripts’ section.  It just allows us to simply only run “npm install” and it will also install all the required bower modules and ruby gems, we’ll talk about those later in the grunt file.

I’ve also included ‘node-bourbon’ since that comes from foundation. So we’ll need it. (Unless you don’t plan on using it of course).

Okay, so we got node packages listed out, now lets do some bower stuff.  We’ll need the ‘bower.json’ file for this.

File: [drupal-root-folder]/UI/bower.json

 

  1. {
  2. "name": "cmmdrupalboiler",
  3. "private": "true",
  4. "dependencies": {
  5. "intentionjs": "0.9.9",
  6. "underscore": "1.7.0",
  7. "viewportsize": "latest",
  8. "requirejs": "latest"
  9. }
  10. }

Just some bower components, so we can use them.. talk to them, tease them, snuggle them. We’re including requireJS through bower here as well.

Next up, your Gems. (Gemfile)

File: [drupal-root-folder]/UI/Gemfile

  1. source 'http://rubygems.org'
  2. gem 'sass', '3.4.5'
  3. gem 'compass', '~> 1.0.3'

Okay now that we’ve got all our resources setup, we need to install them. So, do:

  1. npm install

And then magical unicorns will install all those packages for ya.

Grunt!

Well, now that we’ve got our packages installed, we can setup our gruntfile.  This one is a bit of a doozy, but bare with me.. we can snuggle later.

Im going to go through the file bit by bit. Follow along with the downloaded code.

File: [drupal-root-folder]/UI/Gruntfile.js

  1. /*
  2. * Base Gruntfile Module
  3. */
  4. module.exports = function (grunt) {
  5. 'use strict';
  6. return this.registerTask('default', ['build']);
  7. };

First part is just standard syntax for starting a grunt file. And we just register the default task, which is our ‘build’ task. -- We’ll add that actual task later. This is just the opening and closing of the function, obviously there’s a lot more in it, I’m just being syntactically correct.

Next:

  1. /*
  2. * load grunt tasks automatically
  3. */
  4. require('load-grunt-tasks')(grunt);
  5. var bourbon = require('node-bourbon').includePaths;
  6. /*
  7. * setup paths
  8. */
  9. var path_vars = {
  10. theme_path: '../sites/all/themes/cmmdrupalboiler',
  11. foundation_path: '../sites/all/themes/zurb_foundation',
  12. compile_path: '../UI',
  13. bower_path: '<%= path_vars.compile_path %>/bower_components',
  14. gem_path: '<%= path_vars.compile_path %>/ruby_gems',
  15. js_base_path: '<%= path_vars.theme_path %>/js',
  16. js_src_path: '<%= path_vars.theme_path %>/js/src',
  17. js_bld_path: '<%= path_vars.theme_path %>/js/dist'
  18. };
  19. /*
  20. * foundation - additional libraries
  21. */
  22. var foundation_libs = [
  23. '<%= path_vars.foundation_path %>/js/vendor/placeholder.js',
  24. '<%= path_vars.foundation_path %>/js/vendor/fastclick.js'
  25. ];
  26. /*
  27. * foundation - core JS
  28. */
  29. var foundation_js = [
  30. '<%= path_vars.foundation_path %>/js/foundation/foundation.js',
  31. '<%= path_vars.foundation_path %>/js/foundation/foundation.abide.js',
  32. '<%= path_vars.foundation_path %>/js/foundation/foundation.accordion.js',
  33. '<%= path_vars.foundation_path %>/js/foundation/foundation.alert.js',
  34. '<%= path_vars.foundation_path %>/js/foundation/foundation.clearing.js',
  35. '<%= path_vars.foundation_path %>/js/foundation/foundation.dropdown.js',
  36. '<%= path_vars.foundation_path %>/js/foundation/foundation.equalizer.js',
  37. '<%= path_vars.foundation_path %>/js/foundation/foundation.interchange.js',
  38. '<%= path_vars.foundation_path %>/js/foundation/foundation.joyride.js',
  39. '<%= path_vars.foundation_path %>/js/foundation/foundation.magellan.js',
  40. '<%= path_vars.foundation_path %>/js/foundation/foundation.offcanvas.js',
  41. '<%= path_vars.foundation_path %>/js/foundation/foundation.orbit.js',
  42. '<%= path_vars.foundation_path %>/js/foundation/foundation.reveal.js',
  43. '<%= path_vars.foundation_path %>/js/foundation/foundation.slider.js',
  44. '<%= path_vars.foundation_path %>/js/foundation/foundation.tab.js',
  45. '<%= path_vars.foundation_path %>/js/foundation/foundation.tooltip.js',
  46. '<%= path_vars.foundation_path %>/js/foundation/foundation.topbar.js'
  47. ];

So here we just setup all the variables, and paths to everything. We also include all the paths to the JS files that are needed by foundation. The foundation stuff was taken right out of the gruntfile, from it’s “zurb_foundation” theme folder.

So now for the tasks.

  1. /*
  2. * grunt tasks config
  3. */
  4. grunt.initConfig({
  5. path_vars: path_vars,
  6. pkg: this.file.readJSON('package.json'),
  7. /*
  8. * grunt clean
  9. */
  10. clean: {
  11. css: ['<%= path_vars.theme_path %>/css'],
  12. js: ['<%= path_vars.theme_path %>/js/dist'],
  13. options: {
  14. force: true
  15. }
  16. },
  17. ...

We need a ‘clean’ task that will clear out all the old CSS files, and the all old JS files, so we don’t run into problems with old versions, or orphaned files making things messy.

  1. /*
  2. * grunt copy
  3. */
  4. copy: {
  5. main: {
  6. files: [{
  7. expand: true,
  8. src: [
  9. '<%= path_vars.bower_path %>/underscore/underscore-min.js',
  10. '<%= path_vars.bower_path %>/intentionjs/intention.js',
  11. '<%= path_vars.bower_path %>/viewportsize/viewportSize-min.js',
  12. '<%= path_vars.bower_path %>/requirejs/require.js',
  13. ],
  14. dest: '<%= path_vars.js_src_path %>/vendor',
  15. filter: 'isFile',
  16. flatten: true
  17. }]
  18. }
  19. },
  20. ...

Okay, so here’s some more interesting stuff.  I like bower, but I don’t like having all the files that come with each bower component. It makes things messy, and you don’t need all that on your server, or say your GIT repo.

I’ve setup a ‘copy’ task, that will copy all the desired JS files, from their specific locations (from the bower install), to the one specified location, to where they will actually be used, by either drupal, or in this case RequireJS.  We’ll get to that. Let’s keep moving on through the Gruntfile.js

  1. /*
  2. * grunt compass
  3. */
  4. compass: {
  5. clean: {
  6. options: {
  7. clean: true
  8. }
  9. },
  10. dev: {
  11. options: {
  12. basePath: '<%= path_vars.theme_path %>',
  13. sassDir: 'scss',
  14. cssDir: 'css',
  15. imagesDir: 'images',
  16. importPath: [
  17. '<%= path_vars.foundation_path %>/scss/'
  18. ].concat(bourbon),
  19. bundleExec: true
  20. },
  21. },
  22. build: {
  23. options: {
  24. basePath: '<%= path_vars.theme_path %>',
  25. sassDir: 'scss',
  26. cssDir: 'css',
  27. imagesDir: 'images',
  28. importPath: [
  29. '<%= path_vars.foundation_path %>/scss/'
  30. ].concat(bourbon),
  31. outputStyle: 'compressed',
  32. noLineComments: true,
  33. environment: 'production',
  34. bundleExec: true,
  35. }
  36. }
  37. },

We now have two different build options for compass. A ‘dev’ version that compiles all your sass (or scss), leaving in comments and line comments and all that good stuff.  Then a build version, where all that is striped out, and the css is compressed.

Right, compass, not sass.  I like the way compass outputs it’s line comments for debugging and some of the other options it has.  The gruntfile that comes with foundation is using a compiled version of sass, which is fast, but.. doesn’t have all the nice options. At this point, it’s your choice what ya like to use.

MOAR!

  1. /*
  2. * grunt jshint
  3. */
  4. jshint: {
  5. options: {
  6. jshintrc: '.jshintrc'
  7. },
  8. all: [
  9. 'Gruntfile.js',
  10. '<%= path_vars.js_src_path %>/main.js',
  11. '<%= path_vars.js_src_path %>/modules/*.js',
  12. '!<%= path_vars.js_src_path %>/vendor/**/*.js'
  13. ]
  14. },

A little hint hurt no body. This will just help us keep our JS clean.  The .jshintrc file is included in the code, but that’s beyond the scope of this.  For now, you can see that we are just keeping the gruntfile, main.js file (requirejs main file), everything in our custom modules folder, but nothing in the vendor folder.. since that’s all 3rd party js code.

  1. /*
  2. * grunt uglify
  3. */
  4. uglify: {
  5. options: {
  6. sourceMap: false
  7. },
  8. dist: {
  9. files: {
  10. '<%= path_vars.js_base_path %>/libs.min.js': [foundation_libs],
  11. '<%= path_vars.js_base_path %>/foundation.min.js': [foundation_js]
  12. }
  13. }
  14. },

We’re just using uglify manually here, to uglify foundation’s JS code.  This is also pulled from foundations default gruntfile.  So, keeping foundation in mind, whispering sweet little nothings and giving snuggles here and there. Making sure it feels the love.   Onto the homestretch for our little gruntfile.

 

  1. /*
  2. * grunt requirejs
  3. */
  4. requirejs: {
  5. compile: {
  6. options: {
  7. baseUrl: '.',
  8. appDir: '<%= path_vars.js_src_path %>',
  9. dir: '<%= path_vars.js_bld_path %>',
  10. mainConfigFile: '<%= path_vars.theme_path %>/js/src/main.js',
  11. optimize: 'uglify',
  12. preserveLicenseComments: false,
  13. useStrict: true,
  14. wrap: true,
  15. removeCombined: true
  16. }
  17. }
  18. },

There’s our friend require.  We’re telling require to move all our require related JS code into the ‘dist’ folder, and then uglifying it there for compression.  This task only runs when we do a ‘grunt build’, and not on “grunt dev”.  WHY?, cause you’ll see, stop whining. *arnold voice*

  1. /*
  2. * grunt watch
  3. */
  4. watch: {
  5. grunt: { files: ['Gruntfile.js'] },
  6. compass: {
  7. files: ['<%= path_vars.theme_path %>/scss/**/*.scss'],
  8. tasks: ['compass:dev']
  9. },
  10. js: {
  11. files: ['<%= jshint.all %>'],
  12. tasks: ['jshint', 'uglify']
  13. }
  14. }

And last but not least our watch task.  We’re wanting to watch for any changes to the gruntfile itself, the scss source files, or any of the JS files, which are defined in the jshint task.

Okay, last part.

 

  1. /*
  2. * register 'dev' task
  3. * : for development compilation
  4. */
  5. grunt.registerTask('dev', [
  6. 'copy',
  7. 'clean',
  8. 'compass:dev',
  9. 'uglify',
  10. 'watch'
  11. ]);
  12. /*
  13. * register 'build' task
  14. * : for production compilation
  15. */
  16. grunt.registerTask('build', [
  17. 'copy',
  18. 'clean',
  19. 'compass:build',
  20. 'uglify',
  21. 'requirejs'
  22. ]);

We’ve got two different running tasks.  The dev one being for when we want to work on the project. It first runs ‘copy’ to copy over all the bower related files we need to use. Then ‘clean’ to clean up our files. Then “compass:dev” to get some freshly compiled css out there and ready for development/debugging. Then ‘uglify’ (for foundation JS), Then ‘watch’ to watch for any changes.

The other task is reserved for production use.  When we’re happy with our code, and got everything figured out, we’ll run the ‘grunt build’ task. (Or just ‘grunt’ since that’s our default). The same ‘copy’, ‘clean’ tasks run.  But now we’re running compass:build, that will compress everything and remove needless comments and such.  Then ‘uglify’ (for foundation JS), Then require task will run, and move all the JS to the dist folder, and uglify it all. 

So at this point, you might be wondering what’s the point of the dist folder, how are we going to tell require to use everything in the ‘dist’ folder, and not ‘src’ for production.  This is where we do some PHP fun.  But before we do that, we do that snuggling we were talking about, and grab a coffee.

Continue With Part 2

Download the code from GitHub

Web Development

Would you like to know more about what we do?

View Our Services

Graphic Design Cheeky Monkey

Have a look at some our client work

View Our Work

Cheeky Monkey Discovery

Learn more about Cheeky Monkey Media

About Us

Comments