Run build only if there are changes in src
The story:
We have a team of testers working on automating end-to-end tests using protractor for our internal AngularJS application. Here is the task they usually run for “local” testing:
grunt.registerTask('e2e:local', [
'build:prod',
'connect:test',
'protractor:local'
]);
It runs the “build” task, starts a webserver and runs the e2e tests against the local build.
The build:prod
task itself is defined as:
grunt.registerTask(
'build:prod', [
'clean',
'copy:all',
'copy:assets',
'wiredep',
'ngtemplates',
'useminPrepare',
'concat',
'ngAnnotate',
'autoprefixer',
'uglify',
'cssmin',
'copy:cssfix',
'usemin',
'copy:html',
'bowercopy',
'template:setProdVersion'
]
);
Here we have a lot of subtasks (it definitely could be improved, but this is how it looks now).
The problem:
Currently, it takes about 25 seconds for the build to complete. And, every time a person is running end-to-end tests, the build task is executed.
The question:
How can I run the build:prod
task only if there are changes in src
directory?
Note that the requirement here is to make it transparent for the testers who run the tests. I don’t want them to remember when they need to perform a build and when not.
In other words, the process should be automated. The goal is to automatically detect if build is needed or not.
Note that ideally I would like to leave the build task as is, so that if it is invoked directly via grunt build:prod
it would rebuild regardless of the datestamp of the previous build.
Thoughts and tries:
-
there is the closely related
grunt-newer
package, but, since we have a rather complicated build, having aclean
task at the beginning, I’m not sure how to apply it in my case -
what I was also thinking about is to, inside the
e2e:local
task, manually check the timestamps of the files insidedist
andsrc
and, based on that, decide ifbuild:prod
is needed to be invoked. I think this is whatgrunt-newer
is doing internally -
we started to use
jit-grunt
that helped to improve the performance
Here’s an idea if you use git:
How about using something like grunt-gitinfo and using the last commit in HEAD as a base?
The idea is:
- You create a new grunt task that checks for latest commit hash
- You’d save this commit hash in a file that’s added to
gitignore
(and is NOT in theclean
folder, typically can be in root of repo) - Before saving to file, it’d check the value already in it (standard node
fs
module can do the read/write easily) - If the hash doesn’t match, run
build:prod
task then save new commit hash - The testers build would depend on your new task instead of
build:prod
directly
Another option (still using git):
You can use something like grunt-githooks and create a git hook that runs after pull and calls the git build:prod
, then you can remove it from the dependencies of the grunt task that testers run.
You might have another code to check for githook and install it if required though, which can be a one-time extra step for testers, or maybe baked into the grunt task they call.
I’m surprised noone has mentioned grunt-contrib-watch yet (it’s in the gruntjs.com example file and I thought it was pretty commonly known!). From github: “Run predefined tasks whenever watched file patterns are added, changed or deleted.” – heres a sample grunt file that would run your tasks any time any .js files are modified in src/ or in test/, or if the Gruntfile is modified.
var filesToWatch = ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'];
grunt.initConfig({
watch: {
files: filesToWatch,
tasks: ['build:prod',
'connect:test',
'protractor:local']
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
You have your developers open a terminal and run grunt watch
before they start modifying files, and every time those files are modified the tasks will automatically be run (no more going back to the terminal to run grunt build:prod
every time).
It’s an excellent package and I suggest you check it out. — github — npmjs.org
npm install grunt-contrib-watch --save-dev
Not the answer your are looking for with grunt
, but this will be easy with gulp
.
var fs = require('fs');
var gulpif = require('gulp-if');
var sourceChanged = fs.statSync('build/directory').mtime > fs.statSync('source/directory').mtime;
gulp.task('build:prod', function() {
if (!sourceChanged) {
return false;
}
return gulp.src('./src/*.js')
.pipe(.... build ....)
.pipe(gulp.dest('./dist/'));
});
Here’s how we’ve done some Git HEAD sha work for our build. We use it to determine which version is currently deployed to our production environment – but I’m quite certain you could rework it to return a boolean and trigger the build if truthy.
Gruntfile.js
function getHeadSha() {
var curr, match, next="HEAD";
var repoDir = process.env.GIT_REPO_DIR || path.join(__dirname, '..');
try {
do {
curr = grunt.file.read(path.join(repoDir, '.git', next)).trim();
match = curr.match(/^ref: (.+)$/);
next = match && match[1];
} while (next);
} catch(ex) {
curr="not-found";
}
return curr;
}
grunt.initConfig({
replace: {
applicationVersion: {
src: '<%= config.dist %>/index.html',
overwrite: true,
replacements: [{
from: '{{APPLICATION_VERSION}}',
to: getHeadSha
}]
}
}
});
grunt.registerTask('build', {
'replace:applicationVersion',
/** other tasks **/
});
grunt.registerTask('e2e:local', {
'check_if_we_should_build',
/** other tasks **/
});
index.html
<html data-version="{{APPLICATION_VERSION}}">
<!-- -->
</html>
There’s also the git-info package which would simplify this whole process, we’re looking at switching over to that ourselves.
edit; I just noticed @meligy already pointed you in the direction of git-info. credit where credit is due.
I am not sure if its helpful or not but same things we have done it in our project using GULP framework. We have written a watcher in the gulp that continuously check for the source change and run a quick function to build the project. Its a Protractor Test case.
gulp.task('dome', function () {
gulp.src(["maintest.js"])
.pipe(notify("Change Found , Executing Scripts."))
.pipe(protractor({
configFile: "conf.js",
args: ['--baseUrl', 'http://127.0.0.1:8000']
})).on('error', function (e) {
throw e
});
})
gulp.task('default', function () {
gulp.watch('./webpages/*.js', ['dome']);
gulp.watch('maintest.js', ['dome']);
gulp.watch('conf.js', ['dome']);
});
I don’t have experience in protractor, but conceptually I think this could work.
What I could suggest is to set an alias in your ~/.cshrc to run the build commands only if a diff
command returns true.
#./cshrc
alias build_on_diff 'diff -r branch_dir_1 branch_dir_2\
if ( $status == 1 ) then\
build:prod\
endif'
Just replace the diff
command with whatever git uses, and it should work provided it returns a 1 status for differences detected. We apply a similar method at my workplace to avoid rebuilding files that haven’t changed.