When i first started creating Chrome extensions i kept running into two issues that prevented me from really enjoying the development:
- Cumbersome debugging & testing
- Time-consuming preparation of new releases
In this article i will show how Grunt can help in streamlining above steps.
Grunt
Grunt is a javascript based task runner used for automation. There’s a ton of plugins already available for automating most commons tasks like minification, linting and unit testing. Take a look on http://gruntjs.com and give it a spin if you haven’t already. There are also alternatives out there, check out Gulp.
Genius Lyrics
The lightweight Genius Lyrics extension comes into action when you watch a video on YouTube.It automatically contacts Genius and checks if lyrics for the video you are watching are available and includes them as an extra tab in the site. Genius (formerly known as RapGenius) provides rich annotated lyrics using a large fan-base and artists themselves.
What i wanted to achieve with Grunt is the following:
- Create a good test version that equals the code i actually release.
- Generate a release package based on the current code base
Project structure
If you’re not familiar with Chrome extensions you can check out the getting started guide.
The base of every extension is manifest file with meta data about the project and one or more javascript files providing the functionality.
Besides that you probably have some resources like one or more stylesheets and images.
file/dir | description |
---|---|
.git | Git versioning files |
.gitignore | Git ignore file (1) |
Gruntfile.js | Grunt configuration and tasks |
node_modules | All installed NPM packages |
package.json | NPM package information |
releases | Folder with releases (2) |
src | Latest codebase |
testing | Latest codebase after Grunt actions |
1) I have Git on all files except releases, node_modules and the testing folder. This way i can also keep track of changes in the gruntfile and package dependancies (packages.json)
2) The releases folder holds both .zip files (genius-lyrics-1_7_1.zip) as the orignal source (genius-lyrics-1_7_1-src).
Workflow
Testing
The latest source code is kept in /src , these are the files i edit. After i’m done doing changes in run the ‘grunt testing’ command.
This copies all the source files to the testing folder, minifies them, and runs JShint to validate my code.
You can also let grunt run the testing tasks itself if any changes occure to the source directory. Check out the watch plugin for that.
In chrome i set the /testing folder as the base of my extension.
This way the code i test in chrome is the exact same code (minified, cleaned up) as i use to upload to the Chrome store itself.
This prevents running into problems where things might be broken ‘live’ and not in my development environment.
Releasing
When i’m happy about a new version and i want to release it i run the ‘grunt release’ command.
This looks up the version number in the package.json file and uses the version to generate a zip file ready to upload to the Chrome store.
The task also makes a copy of the current code and puts it in the releases folder. An extra way to easisly test an older extension version in Chrome without have to fetch anything from your versioning software.
I still have to do the upload process manually, but at least i don’t have to worry about inconsistenies or wrong version numbers.
Please note, you have to change the version number in package.json manually.
However you could always create a grunt replace task which fetches the version number from src/manifest and sets it in your package.json.
"name": "genius-lyrics",
"version": "1.7.4",
Gruntfile
Below is the gruntfile i use to achieve the mentioned tasks. It can perform 3 sets of tasks:
- ‘grunt‘ – When no arguments are given the testing tasks are executed
- Remove all files from the testing folder
- Copy current source files to /testing
- Minify and clean the javascript files in /testing
- ‘grunt release‘ – Create a zip file ready to upload and a copy of the current source
- Clean release folder for older files of the current version
- Copy current source files to versioned release folder
- Zip the testing folder and use the version in the filename
- ‘grunt cleanup‘ – Empties the testing folder
All is documented in the commented Gruntfile below. You can download the file from Github.
module.exports = function(grunt) { // Get version number from packages.json and convert '.' to '_' var sFormattedVersion = grunt.file.readJSON('package.json').version.replace(/\./g, "_"); // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), // JSHint, a tool that helps to detect errors and potential problems in your JavaScript code (http://jshint.com/) jshint: { options: { multistr: true, curly: false, eqeqeq: false, eqnull: false, browser: false, globals: { jQuery: true }, }, myFiles: ['src/contentscript.js'], }, clean: { // Remove all files in the testing folder testing: ['testing/'], // Remove the release source files from the current version release: ['releases/<%= pkg.name %>-'+sFormattedVersion+'-src/'] }, // Copy files from one directory to another copy: { // copy files from src/ to testing/ testing: { files: [ // set current working dir to src/ and copy files so you don't end up with the directory name like /testing/src/ // exclude DS_store files (Mac) // exclude Git files // include hidden files (starting with a dot) { expand: true, cwd: 'src/', src: ['**', '!.DS_Store', '!.git/**'], dest: 'testing/', dot: true } ] }, // copy files from src/ to a release source folder sourcefiles: { files: [ { expand: true, cwd: 'src/', src: ['**', '!.DS_Store', '!.git/**'], dest: 'releases/<%= pkg.name %>-'+sFormattedVersion+'-src/', dot: true } ] } }, // This minifies javascript files and removes console.log debugging messages uglify: { options: { mangle: false, compress: { drop_console: true } }, my_target: { files: { 'testing/contentscript.js': ['testing/contentscript.js'] } } }, // creates a zip file from all files in the testing folder named with the current version zip: { main: { cwd: 'testing/', src: ['testing/**'], dest: 'releases/<%= pkg.name %>-'+sFormattedVersion+'.zip' } } }); // Load the plugins grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-zip'); grunt.loadNpmTasks('grunt-contrib-jshint'); // Default tasks, if you run grunt without any arguments it will the testing tasks grunt.registerTask('default', ['clean:testing', 'copy:testing', 'uglify']); // Release task, zips the testing folder and renames it to the latest version grunt.registerTask('release', ['clean:release', 'copy:sourcefiles', 'zip']); // Cleanup action, clean testing folder only grunt.registerTask('cleanup', ['clean:testing']); };
uitleg op welke manierne deze aan te roepen is
Improvements
As the CSS and Javascript files are being loaded locally (a user installs extensions on his computer) i didn’t bother to compress them. But of course you could with Grunt. An improvement still to make is having the current version in only one place. Either the package.json or the manifest should be leading and the version should be copied from that file. If you can think of other improvements, please let me know.
No more drama!
This is just one way to use Grunt for a project. I love how versatile it is, and how quick you can make big improvements that save you loads of time (and frustration). The examples above are just an example, and i’m open to improvements and like to hear how you use Grunt or Gulp to make your life happier.