17 March 2013 – Updated to included the new simpler syntax of 0.6.0
So I stumbled across a thread last week on the Sencha forums marked under their “Trending Threads”. It’s title:
Sencha Cmd Needs Some F*****G Coffee!!!
It’s an eye catching title! Specifically there was one post there which said:
I really would love to see someone starting a yeoman/grunt based community replacement for the Sencha CMD
Having written a POC of this at an old company I thought it right that I make a proper open source version and share it. So last week I hastily hacked up my first version and very prematurely posted it to the thread, twitter and here. It was crap, worked only on my project and was riddled with a lot of bad code and bugs (apologies if you tried it and hit problems).
Over the last few days I decided to tidy up the code, add some tests, and road test it on a few more project. I’m now happy to say that it’s working pretty solidly on Ext.js and Sencha Touch projects. So I’d like to run through the simple setup on an existing project. My choice was the Pandora MVC project that Sencha provide as their MVC walkthrough application, but the following steps should work just as well on any existing project your have.
If you get stuck or have feedback please leave a comment below.
Getting started on an existing project
I’m assuming you have node.js installed, if not grab a copy from http://nodejs.org/
First thing we need to do is add a package.json file to your project which can be done by running the following on the command line:
$ cd project/root/src/dir $ npm init
This will ask a few questions about the project name, version, test commands, etc. For the ones where you know the data fill it in, for the others just hit enter and accept the defaults – they can always be changed later.
Next up you need to get the latest version of Grunt. To do this run the following:
$ npm uninstall -g grunt $ npm install -g grunt-cli $ npm install grunt --save-dev
You may need to run the first two as root, depending on your OS and previous installs
This should add grunt locally to your project. Running grunt should give:
$ grunt A valid Gruntfile could not be found. Please see the getting started guide for more information on how to configure grunt: http://gruntjs.com/getting-started Fatal error: Unable to find Gruntfile.
Like Ant, make, etc they all need a file which defines some tasks to run. For grunt this file should be called Gruntfile.js and be in your project root. Here’s a simple one to get started with:
module.exports = function(grunt) {
grunt.initConfig({
});
grunt.registerTask('default', []);
};
Hopefully if you run grunt again with the above in a file called Gruntfile.js you should see:
$ grunt Done, without errors.
Adding the sencha_dependencies task
To add the sencha_dependencies task to your project you need to do two things. First add it as a dependency via NPM:
$ npm install grunt-sencha-dependencies --save-dev
And then we need to add it to our Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
sencha_dependencies: {
app: {
options : {
appFile: 'app/app.js',
pageToProcess: 'index.html'
}
}
}
});
grunt.loadNpmTasks('grunt-sencha-dependencies');
grunt.registerTask('default', ['sencha_dependencies:app']);
};
For your project you’ll need to tweak the following:
appFile– This should be the relative path of the file containing your call toExt.applicationfrom your html filepageToProcess– This should be the name of the html file which we’ll use to run your app in headlessly and figure out what files get loaded and in what order you need them in productionpageRoot– By default the task assumes your html pages live in the root of your project. If thats not the case and they live under some subdirectory, then you should set this property to be your subdirectory
With the file now updated with the above try running:
$ grunt Running "sencha_dependencies:app" (sencha_dependencies) task Processing Sencha app file index.html... >> Success! 230 files added to property sencha_dependencies_app Done, without errors.
This tells you that it found 230 files which your app requires and it has now written it to a new global property called sencha_dependencies_app
If you wanted to see what files it found then you can pass in the --verbose flag and you should see something like:
$ grunt --verbose
<snip>
>> Success! 225 files added to property sencha_dependencies_app
Files are:
../libs/ext-4.1.1a/ext-debug.js
../libs/ext-4.1.1a/src/util/Observable.js
../libs/ext-4.1.1a/src/app/Controller.js
../libs/ext-4.1.1a/src/util/Filter.js
../libs/ext-4.1.1a/src/util/AbstractMixedCollection.js
../libs/ext-4.1.1a/src/util/Sorter.js
../libs/ext-4.1.1a/src/util/Sortable.js
../libs/ext-4.1.1a/src/util/MixedCollection.js
../libs/ext-4.1.1a/src/data/proxy/Proxy.js
../libs/ext-4.1.1a/src/data/Operation.js
<snip>
../libs/ext-4.1.1a/src/data/reader/Array.js
../libs/ext-4.1.1a/src/data/ArrayStore.js
../libs/ext-4.1.1a/src/data/Batch.js
app/app.js
Done, without errors.
Using this with our tasks to create output files
On it’s own the above isn’t all that useful, but we can use the output of the task and pass it on to any other grunt task we want now. I think the most obvious example is wanting to concatenate and minify the code, in addition let’s also generate source maps to help with debugging in chrome. For this we need another task:
$ npm install grunt-contrib-uglify --save-dev
And in our Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
sencha_dependencies: {
app: {
options : {
appFile: 'app/app.js',
pageToProcess: 'index.html'
}
}
},
uglify: {
app: {
options: {
sourceMap: 'build/source-map.js'
},
files: {
'build/app.min.js': ['<%= sencha_dependencies_app %>']
}
}
}
});
grunt.loadNpmTasks('grunt-sencha-dependencies');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['sencha_dependencies:app', 'uglify:app]);
};
Now if you run grunt, you’ll see:
$ grunt Running "sencha_dependencies:app" (sencha_dependencies) task Processing Sencha app file index.html... >> Success! 230 files added to property sencha_dependencies_app Running "uglify:app" (uglify) task Source Map "build/source-map.js" created. File "build/app.min.js" created. Uncompressed size: 3488667 bytes. Compressed size: 262655 bytes gzipped (890454 bytes minified). Done, without errors.
The uglify task uses the files output from the sencha_dependencies task via the property it created:
<%= sencha_dependencies_app %>
This is special templating syntax which means the value in the property won’t be resolved until it’s needed. After that we can see uglify created a source-map.js which will work with debuggers that support them (chrome and some versions of firebug), and finally it concated and minifed all our files into dest/app.min.js
Using the minified file in our production application
Now we have a minified set of all the files we should make sure it gets used when we go live. I’ve seen teams in the past create an index-development.html and an index-production.html. This is one option, but grunt supports a copy tasks which can do any replacement we want, so let’s keep one index.html and assume we want to update it into our build output directory.
$ npm install grunt-contrib-copy --save-dev
Our grunt task will look like:
<snip>
grunt.initConfig({
<snip>
copy: {
app: {
files: [
{expand: true, src: ['resources/css/ext-all.css'], dest: 'build/', cwd: '../libs/ext-4.1.1a/',},
{expand: true, src: ['resources/themes/images/**'], dest: 'build/', cwd: '../libs/ext-4.1.1a/',},
{src: ['index.html'], dest: 'build/'}
],
options: {
processContent: function(content, filePath) {
// process only the index.html content
if (/index.html/.test(filePath)) {
// remove the ext script
content = content.replace(/<script.*ext.js"><\/script>/, '');
// now update the css location
content = content.replace(/\.\.\/libs\/ext-4.1.1a\//, '');
// now change our app.js to app.min.js
content = content.replace(/app\/app.js/, 'app.min.js');
}
return content;
}
}
}
}
});
grunt.loadNpmTasks('grunt-sencha-dependencies');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('default', ['sencha_dependencies:app', 'uglify:app', 'copy:app']);
};
This seems a little bit complicated but let me break it down:
files: [
{expand: true, src: ['resources/css/ext-all.css'], dest: 'build/', cwd: '../libs/ext-4.1.1a/',},
{expand: true, src: ['resources/themes/images/**'], dest: 'build/', cwd: '../libs/ext-4.1.1a/',},
{src: ['index.html'], dest: 'build/'}
]
The above will move the CSS and image files over to our build directory, and in our addition it will move our index.html over to the build directory.
options: {
processContent: function(content, filePath) {
// process only the index.html content
if (/index.html/.test(filePath)) {
// remove the ext script
content = content.replace(/<script.*ext.js"><\/script>/, '');
// now change our app.js to app.min.js
content = content.replace(/app\/app.js/, 'app.min.js');
// now update the css location
content = content.replace(/\.\.\/libs\/ext-4.1.1a\//, '');
}
return content;
}
}
This function is a hook the task provides to do… well pretty much anything! In my case I want to only process the index.html file and all I want to do is:
- remove the reference to the ext.js library JavaScript file (it’s now in a minified file)
- swap the app.js with our new minified app.min.j
- and also update the url location of our css file to point to our build version
That’s it… this grunt file now does pretty much want any basic app needs.
More information
This is just a small walkthrough that will hopefully get people up and running with grunt on Ext.js projects. If you do have questions on the above please leave a comment below, or if you find a bug in the grunt-sencha-dependencies task please file an issue over at https://github.com/mattgoldspink/grunt-sencha-dependencies. Similarly, if there’s features you think would be useful then please let me know (though bear in mind I want to keep this task simple and just deal with dependencies).
In addition if you want to see a fuller example Grunt file on a project please take a look at the Pandora MVC example in the project – https://github.com/mattgoldspink/grunt-sencha-dependencies/tree/master/test/integration/pandora-ext-4.1.1a
Hi,
I’m trying to reproduce steps described in your article but I have problems in the ‘Adding the sencha_dependencies task’ step. After modifying the Gruntfile.js introducing what you describe, the grunt command fails with the following message:
Loading “sencha_dependencies.js” tasks…ERROR
>> Error: Cannot find module ‘.\lib\splitArrayIntoThree.js’
Warning: Task “sencha_dependencies:app” not found. Use –force to continue.
I have installed grunt-sencha-dependencies, I don’t understand the problem. Have you an idea of what is missing?
Thanks in advance.
Regards,
Giuseppe
Hi Guiseppe,
Apologies for the delay in responding. I’ve been travelling for the last few weeks without internet access.
At a guess you’re trying to run on Windows. There’s a known bug (https://github.com/mattgoldspink/grunt-sencha-dependencies/issues/28) which I’m still working on fixing. If you follow that issue I’ll be sure to update it once it’s fixed. Apologies for the hassle.
Matt
Thanks for providing this. Any idea why I would be getting this error:
Fatal error: ENOENT, no such file or directory ‘path/to/my/app/touch,
Grunt out put is this:
Running “sencha_dependencies:app” (sencha_dependencies) task
Verifying property sencha_dependencies.app exists in config…OK
File: [no files]
Processing Sencha app file index.html…
Reading ./index.html…OK
Processing source…OK
Writing ./701641.html…OK
Fatal error: ENOENT, no such file or directory …
Hi Jamie,
Apologies for the delay in responding. I’ve been travelling for the last few weeks without internet access.
It’s not immediately obvious, but I suspect you may have something misconfigured in the task. Can you head over to github page and create a new issue: https://github.com/mattgoldspink/grunt-sencha-dependencies/issues/new with details about your grunt config and the directory structure. Also if you could run with “grunt -v” and give the output, that will hopefully help me debug it.
Thanks,
Matt