Thursday, February 28, 2013

StealJS Builds with Grunt

GRUNT!

I have seen the future of javascript builds, and it is Grunt.  Please, please, please disregard my earlier post on building with maven; it just doesn't make sense to use such a heavyweight Java-based tool for building javascript projects.

Grunt is rightfully taking the Javascript world by storm. It's got a great community around it, as shown by the wealth of plugins available. It runs on Node, allows for package management via npm, and lets you start working with Javascript in a way which makes us stodgy old enterprise Java guys breathe slightly easier ;-) (more posts on that soon!).

Steal Builds, with Grunt

So naturally I'd like to share how I've gotten steal builds to work with my gruntfile. There is a grunt-steal plugin, but it hasn't yet been updated for grunt 0.4, and steal builds don't run on node anyways. (but, excitingly enough, I've heard rumors that steal will run in node sooner rather than later!)

Here are the npm packages I'm using for my steal build:

    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-shell');
    grunt.loadNpmTasks('grunt-exists');

My approach is to copy all of the source files into a target/ folder (ala maven!) so I can apply the package version where appropriate, and build the production.{css | js} files outside of the src dir.

Here's where I copy the source files. Note that I'm only including HTML in the phase where I am replacing the project.version and steal environment variables. As with maven, if you filter binary files it will corrupt them.

        copy: {
            build: {
                files: {
                    'target/': ['src/**/*.html']
                },
                options: {
                    processContent: function (content) {
                        return content.replace(/env\s*: 'development', \/\/ live/g, "env: 'production',").replace(/\$\{project\.version\}/g, grunt.template.process('<%= pkg.version %>'));
                    }
                }
            },
            build_other: {
                files: {
                    'target/': ['src/**/*', 'src/**/.*', '!src/**/*.html']
                }
            }
        },

Here's my corresponding HTML snippets which get replaced:

<head>
  <link href="app/production-${project.version}.css" rel="stylesheet">
</head>
<body>
   <script>
      (function() { 
        steal = { 
          env: 'development', // live 
          startFile: 'app', 
          executed: ['app/production.css'], 
          production: 'app/production-${project.version}.js' 
        }; 
      }());
    </script>
    <script src="steal/steal.js"></script>
</body>

Note also in the build_other phase, that I'm explicitly including src/**/* and src/**/.*, so that my .htaccess file comes along for the ride.

Next, I'm using the shell plugin to run the steal build, the same way as you would manually from the command line. First, I need to make the js script executable, then I run it using the target dir as the base working dir.

        shell: {
          chmodjs: {
            command: 'chmod 755 target/src/js'
          },

          steal_app: {
            command: './js app/scripts/build.js',
            options: {
                stdout: true,
                stderr: true,
                failOnError: true,
                execOptions: {
                    cwd: 'target/src'
                }
            }
          }

And voila! You now have a target/src dir with a deployable web application. My last step is to verify that our production files were built successfully.


        exists: {
            prod_files: [ 
              'target/src/app/production.js', 
              'target/src/app/production.css', 
            ]
         }

Also note that I haven't physically changed the name of the production.js file to production-{project.version}.js. I'm using a neat .htaccess trick from html5boilerplate to do my filename fingerprinting in apache. This works because I've set no-cache for *.html.

<IfModule mod_rewrite.c>
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^(.+)\-\d+\.\d+\.\d+\.(js|css|png|jpg|gif)$ $1.$2 [L]
</IfModule>

I'm going to go into more detail on integrating steal with jasmine tests, and exploring other grunt goodies, in (near!) future posts.

1 comment:

Guillaume Lambert said...

Nice write up man, really like the way you handle production builds with StealJS. I do it quite similarly, only difference is my .htaccess directive :



# screen (including .min version) and jmvc production builds
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^static/(.+)(screen|style|production)\.v(\d+)(\.min)?\.(js|css)$ %{ENV:REWRITEBASE}static/$1$2$4.$5 [L]



I also use another grunt plugin to set my cache busting, grunt-php-set-constant, because we set it in our PHP application config file. But I like the way you do it with grunt instead, replacing the string directly in the .html

I'll follow you on Twitter now, finally found someone doing things almost the same as I do ;)

Keep up the good work!

Guillaume
@falzhobel