Grunt: Watch file changes and compile parent directory - node.js

I'm working on a project using grunt, I haven't worked with grunt before and currently this is setup as to watch files and when a file has been changed recompile all the files (multiple subdirectories containing hundreds of files) using handlebars into html which is quite slow. I want to improve this to a faster process by only compiling what is needed.
Watching the files with grunt newer doesn't really work because there are dependencies within the directory and thus only recompiling the changed files will not result in a valid page.
I would basically need to recompile the whole parent directory of the file that has changed, but I'm not quite sure on how I would configure something like that.
Any hints where I should look at?
The assemble itself is configured like this:
var _ = require('lodash');
var path = require('path');
// expand the data files and loop over each filepath
var pages = _.flatten(_.map(grunt.file.expand('./src/**/*.json'), function(filepath) {
// read in the data file
var data = grunt.file.readJSON(filepath);
var dest=path.dirname(filepath)+ '/' +path.basename(filepath, path.extname(filepath));
dest=dest.replace("src/","");
var hbs;
if (data.hbs){
hbs=grunt.file.read(path.dirname(filepath)+ '/' + data.hbs)
}
// create a 'page' object to add to the 'pages' collection
return {
// the filename will determine how the page is named later
filename: dest,
// the data from the json file
data: data,
// add the recipe template as the page content
content:hbs
};
}));
return {
options: {
/*postprocess: require('pretty'),*/
marked: {sanitize: false},
data: '<%= options.src %>/**/*.json',
helpers: '<%= options.src %>/helpers/helper-*.js',
layoutdir: '<%= options.src %>/templates',
partials: ['<%= options.src %>/components/**/*.hbs']
},
build: {
options: {
layout: 'base.hbs',
assets: '<%= options.build %>',
pages: pages
},
files: [
{
cwd: '<%= options.src %>',
dest: '<%= options.build %>',
src: '!*'
}
]
},
}
So every time this loads all the pages get scanned down like /src/sites/abc/xyz/foo.json and get compiled, but I only want to have changed files. Watch does detect changed files, but all the files get compiled again and I'm not sure how I could get the changed files that watch has recognized in the config to only process part of the files.

I think what you need is already there in watch.
Check the Using the watch event in grunt doc.
Copying down the content here to satisfy the SO MODS/GODS.
This task will emit a watch event when watched files are modified. This is useful if you would like a simple notification when files are edited or if you're using this task in tandem with another task. Here is a simple example using the watch event:
grunt.initConfig({
watch: {
scripts: {
files: ['lib/*.js'],
},
},
});
grunt.event.on('watch', function(action, filepath, target) {
grunt.log.writeln(target + ': ' + filepath + ' has ' + action);
});
The watch event is not intended for replacing the standard Grunt API for configuring and running tasks. If you're trying to run tasks from within the watch event you're more than likely doing it wrong. Please read configuring tasks.
Compiling Files As Needed
A very common request is to only compile files as needed. Here is an example that will only lint changed files with the jshint task:
grunt.initConfig({
watch: {
scripts: {
files: ['lib/*.js'],
tasks: ['jshint'],
options: {
spawn: false,
},
},
},
jshint: {
all: {
src: ['lib/*.js'],
},
},
});
// on watch events configure jshint:all to only run on changed file
grunt.event.on('watch', function(action, filepath) {
grunt.config('jshint.all.src', filepath);
});
If you need to dynamically modify your config, the spawn option must be disabled to keep the watch running under the same context.
If you save multiple files simultaneously you may opt for a more robust method:
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
grunt.config('jshint.all.src', Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});

Related

Grunt CSS min - Issue with minifying different CSS files

I'm having issue with minifying multiple CSS files using grunt cssmin I'm not looking to minify all files into single file. I would like to have the files having same name with min.css extension.
My gruntfile.js is as follows.
module.exports = function (grunt) {
grunt.initConfig({
cssmin: {
target: {
files: [{
src: ['assets/css/*.css', '!assets/css/*.min.css'], // source files mask
dest: 'assets/css/', // destination folder
expand: true, // allow dynamic building
flatten: true, // remove all unnecessary nesting
ext: '.min.css' // replace .css to .min.css
}],
/* BELOW IS ONLY A TRICK USED TO MINIFY THE FILES SKIPPED BY THE ABOVE */
/*files: [{
src: ['assets/css/home.css', 'assets/css/institutions.css', 'assets/css/form-elements.css'], // source files mask
dest: 'assets/css/', // destination folder
expand: true, // allow dynamic building
flatten: true, // remove all unnecessary nesting
ext: '.min.css' // replace .css to .min.css
}],*/
}
}
});
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.registerTask('default', [ 'cssmin' ]);
};
As per the code (except the commented out part), it is suppose to minify all the css files. But for some reason it skips few css files from the directory. To minify those, I have to comment out the first files[{}] and uncomment the later.
It doesn't work when both files[{}] are uncommented. I'm clueless on why its happening.
My Nodejs version is: v0.10.25
Thanks in advance.
Note: I used to get warnings & task aborts on grunt cssmin due to node version issue and got it fixed by downgrading nvm version to nvm v0.10.39. Thanks to blindMoe for pointing the solution

How to use yeoman, grunt, usemin 2 and requirejs?

I'm trying to wrap my head around using Grunt, usemin 2, requirejs, and uglify. What I'm observing in my builds is that requirejs is not properly including my dependencies into my single concatenated build file. When I run index.html out of /dist, I see errors when looking for 'jquery', 'app', and some third party js files or sometimes "define is not defined".
I read the following issues on grunt-usemin and removing the require blocks, but some questions still remain in those threads.
Recommended way to handle RequireJS, concat, uglify
How to handle requirejs in v2.0
I followed up my search and came across this post How to integrate Yeoman and Requirejs, which sort of got me there in that I saw the Requirejs optimizer running when I changed from using grunt-contrib-requirejs to grunt-requirejs. Unfortunately, I still see these errors:
Uncaught ReferenceError: define is not defined.
I have the following in my index.html:
<!-- build:js js/main.js -->
<script src="bower_components/requirejs/require.js"></script>
<script src="js/main.js"></script>
<!-- endbuild -->
Here is my Grunt file: http://jsbin.com/futocusu/3/edit?js
There was talk in issue #112 about creating an article on using Yeoman on this topic, but I don't think it's been written yet.
Has anyone figured out the best way to use usemin v2 with grunt and requirejs to output to a single concat+uglify file on build? I'm also not sure what the difference is in using grunt-contrib-requirejs and grunt-requirejs and when to use which one.
It looks as though you are trying to do too much with main.js.
I have the following build tasks in Gruntfile.js
grunt.registerTask('build', [
'copy', // copies the src directory to dist (htdocs)
'requirejs', // do an r.js build to concat modular dependencies
'concat:head', // concats js in the head
'uglify:head', // compresses js in the head
'uglify:foot', // compresses js in the foot
'cssmin', // minifies and concats css
'usemin', // runs through html and inputs minified js and css
'clean:afterBuild' // deletes files that are not required for build
]);
Here are each of the relevant Grunt tasks (for me these are stored in separate files because I use load-grunt-config). If you would like to use these in your gruntfile then all you need to do is grab everything that is in the returned object and stick that in your task value in your gruntfile:
copy
module.exports = function (grunt, options) {
return {
main: {
cwd: 'src/',
src: '**',
dest: 'dist/',
expand: true,
flatten: false
},
};
};
requirejs
module.exports = function(grunt, options) {
return {
compile: {
options: {
appDir: "src/to/require/app",
baseUrl: "./",
mainConfigFile: "src/to/require/app/common",
dir: "dist/to/require/app",
// build a common layer
modules: [
{
"name": "common"
}
]
}
}
};
};
concat
module.exports = function (grunt, options) {
return {
head: {
/* other stuff */
},
foot: {
src: [
'dist/to/require/app/some_other_js.js',
'dist/to/require/app/external/require.js',
'dist/to/require/app/external/require.text.js',
'dist/to/require/app/common.js'
],
dest: 'src/to/require/app/compiled_footer_js.js',
}
};
};
uglify
module.exports = function (grunt, options) {
return {
head: {
/* other stuff *
},
foot: {
files: {
'src/to/require/app/compiled_footer_js.min.js': ['src/to/require/app/compiled_footer_js.js']
}
}
};
};
usemin
module.exports = function (grunt, options) {
return {
html: [
'src/path/to/index.html'
]
};
};

Using Gulp to build requireJS project - gulp-requirejs

I am trying to use gulp-requirejs to build a demo project. I expect result to be a single file with all js dependencies and template included. Here is my gulpfile.js
var gulp = require('gulp');
var rjs = require('gulp-requirejs');
var paths = {
scripts: ['app/**/*.js'],
images: 'app/img/**/*'
};
gulp.task('requirejsBuild', function() {
rjs({
name: 'main',
baseUrl: './app',
out: 'result.js'
})
.pipe(gulp.dest('app/dist'));
});
// The default task (called when you run `gulp` from cli)
gulp.task('default', ['requirejsBuild']);
The above build file works with no error, but the result.js only contains the content of main.js and config.js. All the view files, jquery, underscore, backbone is not included.
How can I configure gulp-requirejs to put every js template into one js file?
If it is not the right way to go, can you please suggest other method?
Edit
config.js
require.config({
paths: {
"almond": "/bower_components/almond/almond",
"underscore": "/bower_components/lodash/dist/lodash.underscore",
"jquery": "/bower_components/jquery/dist/jquery",
"backbone": "/bower_components/backbone/backbone",
"text":"/bower_components/requirejs-text/text",
"book": "./model-book"
}
});
main.js
// Break out the application running from the configuration definition to
// assist with testing.
require(["config"], function() {
// Kick off the application.
require(["app", "router"], function(app, Router) {
// Define your master router on the application namespace and trigger all
// navigation from this instance.
app.router = new Router();
// Trigger the initial route and enable HTML5 History API support, set the
// root folder to '/' by default. Change in app.js.
Backbone.history.start({ pushState: false, root: '/' });
});
});
The output is just a combination this two files, which is not what I expected.
gulp-requirejs has been blacklisted by the gulp folks. They see the RequireJS optimizer as its own build system, incompatible with gulp. I don't know much about that, but I did find an alternative in amd-optimize that worked for me.
npm install amd-optimize --save-dev
Then in your gulpfile:
var amdOptimize = require('amd-optimize');
var concat = require('gulp-concat');
gulp.task('bundle', function ()
{
return gulp.src('**/*.js')
.pipe(amdOptimize('main'))
.pipe(concat('main-bundle.js'))
.pipe(gulp.dest('dist'));
});
The output of amdOptimize is a stream which contains the dependencies of the primary module (main in the above example) in an order that resolves correctly when loaded. These files are then concatenated together via concat into a single file main-bundle.js before being written into the dist folder.
You could also minify this file and perform other transformations as needed.
As an aside, in my case I was compiling TypeScript into AMD modules for bundling. Thinking this through further I realized that when bundling everything I don't need the asynchronous loading provided by AMD/RequireJS. I am going to experiment with having TypeScript compile CommonJS modules instead, then bundling them using webpack or browserify, both of which seem to have good support within gulp.
UPDATE
My previous answer always reported taskReady even if requirejs reported an error. I reconsidered this approach and added error logging. Also I try to fail the build completely as described here gulp-jshint: How to fail the build? because a silent fail really eats your time.
See updated code below.
Drew's comment about blacklist was very helpfull and gulp folks suggest using requirejs directly. So I post my direct requirejs solution:
var DIST = './dist';
var requirejs = require('requirejs');
var requirejsConfig = require('./requireConfig.js').RJSConfig;
gulp.task('requirejs', function (taskReady) {
requirejsConfig.name = 'index';
requirejsConfig.out = DIST + 'app.js';
requirejsConfig.optimize = 'uglify';
requirejs.optimize(requirejsConfig, function () {
taskReady();
}, function (error) {
console.error('requirejs task failed', JSON.stringify(error))
process.exit(1);
});
});
The file at ./dist/app.js is built and uglified. And this way gulp will know when require has finished building. So the task can be used as a dependency.
My solution works like this:
./client/js/main.js:
require.config({
paths: {
jquery: "../vendor/jquery/dist/jquery",
...
},
shim: {
...
}
});
define(["jquery"], function($) {
console.log($);
});
./gulpfile.js:
var gulp = require('gulp'),
....
amdOptimize = require("amd-optimize"),
concat = require('gulp-concat'),
...
gulp.task('scripts', function(cb) {
var js = gulp.src(path.scripts + '.js')
.pipe(cached('scripts'))
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(remember('scripts'))
.pipe(amdOptimize("main",
{
name: "main",
configFile: "./client/js/main.js",
baseUrl: './client/js'
}
))
.pipe(concat('main.js'));
.pipe(gulp.dest(path.destScripts));
}
...
This part was important:
configFile: "./client/js/main.js",
baseUrl: './client/js'
This allowed me to keep my configuration in one place. Otherwise I was having to duplicate my paths and shims into gulpfile.js.
This works for me. I seems that one ought to add in uglification etc via gulp if desired. .pipe(uglify()) ...
Currently I have to duplicate the config in main.js to run asynchronously.
....
var amdOptimize = require("amd-optimize");
...
var js = gulp.src(path.scripts + '.js')
.pipe(cached('scripts'))
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(remember('scripts'))
.pipe(amdOptimize("main",
{
name: "main",
paths: {
jquery: "client/vendor/jquery/dist/jquery",
jqueryColor: "client/vendor/jquery-color/jquery.color",
bootstrap: "client/vendor/bootstrap/dist/js/bootstrap",
underscore: "client/vendor/underscore-amd/underscore"
},
shim: {
jqueryColor : {
deps: ["jquery"]
},
bootstrap: {
deps: ["jquery"]
},
app: {
deps: ["bootstrap", "jqueryColor", "jquery"]
}
}
}
))
.pipe(concat('main.js'));
Try this code in your gulpfile:
// Node modules
var
fs = require('fs'),
vm = require('vm'),
merge = require('deeply');
// Gulp and plugins
var
gulp = require('gulp'),
gulprjs= require('gulp-requirejs-bundler');
// Config
var
requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('app/config.js') + '; require;'),
requireJsOptimizerConfig = merge(requireJsRuntimeConfig, {
name: 'main',
baseUrl: './app',
out: 'result.js',
paths: {
requireLib: 'bower_modules/requirejs/require'
},
insertRequire: ['main'],
// aliases from config.js - libs will be included to result.js
include: [
'requireLib',
"almond",
"underscore",
"jquery",
"backbone",
"text",
"book"
]
});
gulp.task('requirejsBuild', ['component-scripts', 'external-scripts'], function (cb) {
return gulprjs(requireJsOptimizerConfig)
.pipe(gulp.dest('app/dist'));
});
Sorry for my english. This solution works for me. (I used gulp-requirejs at my job)
I think you've forgotten to set mainConfigFile in your gulpfile.js. So, this code will be work
gulp.task('requirejsBuild', function() {
rjs({
name: 'main',
mainConfigFile: 'path_to_config/config.js',
baseUrl: './app',
out: 'result.js'
})
.pipe(gulp.dest('app/dist'));
});
In addition, I think when you run that task in gulp, require can not find its config file and
This is not gulp-requirejs fault.
The reason why only main.js and config.js is in the output is because you're not requiring/defining any other files. Without doing so, the require optimizer wont understand which files to add, the paths in your config-file isn't a way to require them!
For example you could load a main.js file from your config file and in main define all your files (not optimal but just a an example).
In the bottom of your config-file:
// Load the main app module to start the app
requirejs(["main"]);
The main.js-file: (just adding jquery to show the technique.
define(["jquery"], function($) {});
I might also recommend gulp-requirejs-optimize instead, mainly because it adds the minification/obfuscation functions gulp-requirejs lacks: https://github.com/jlouns/gulp-requirejs-optimize
How to implement it:
var requirejsOptimize = require('gulp-requirejs-optimize');
gulp.task('requirejsoptimize', function () {
return gulp.src('src/js/require.config.js')
.pipe(requirejsOptimize(function(file) {
return {
baseUrl: "src/js",
mainConfigFile: 'src/js/require.config.js',
paths: {
requireLib: "vendor/require/require"
},
include: "requireLib",
name: "require.config",
out: "dist/js/bundle2.js"
};
})).pipe(gulp.dest(''));
});

How set/get environment specific variable in a Yeoman Ember app

Considering an Yeoman Ember app.
I've looked different tools like:
https://github.com/logankoester/grunt-environment
https://github.com/jsoverson/grunt-env
https://github.com/outaTiME/grunt-config
But I don't quite see how can you set/get for instance different adapter url in your router.js depending on some grunt/node environment.
One approach would be having for example two files: development.properties and production.properties, and using grunt you can copy them to a current.properties file and then you can read the current.properties file always, using for example nconf from any place of your application.
You just need to use the grunt-contrib-copy tasks in your grunt file.
Summary: create a gruntfile that read the environment from the command line and copy the corresponding file property to current.properties, and then in your route.js you just include nconf and you just use it.
Also as an alternative nconf can read parameters from the command line given to node when you run it, so you can avoid the grunt file and run it like node app.js --property=value and take the value of the property with nconf from your route.js
So I ended up figuring out a way that works well but only with two env:
I made a pull-request for the ember-generator to start using that pattern:
https://github.com/borisrorsvort/generator-ember/commit/66f26e9e5a7599da53cb99b85e8ef5864648349b
Here is an implementation that works with yeoman ember generator:
Your replace task should look something like this:
replace: {
app: {
options: {
variables: {
ember: 'bower_components/ember/ember.js',
ember_data: 'bower_components/ember-data-shim/ember-data.prod.js',
app_config: 'scripts/app_config/development.js'
}
},
files: [{
src: '<%= yeoman.app %>/index.html',
dest: '.tmp/index.html'
}]
},
dist: {
options: {
variables: {
ember: 'bower_components/ember/ember.prod.js',
ember_data: 'bower_components/ember-data-shim/ember-data.prod.js',
app_config: 'scripts/app_config/production.js'
}
},
files: [{
src: '<%= yeoman.app %>/index.html',
dest: '.tmp/index.html'
}]
}
}
======
Then add '##app_config' the index.html under ##ember and ##ember_data so that it get replaced by the correct string
======
Then create two files:
app/templates/scripts/app_config/development.js
app/templates/scripts/app_config/production.js
These contain an AppConfig onject that you can use in your Ember App
var AppConfig = {};
======
Now Imagine you want to change your adapter when you're building the app:
Just use:
MyApp.ApplicationAdapter = DS.RESTAdapter.extend({
host: AppConfig.apiAdapterUrl
});

In grunt, if I'm watching multiple files and two or more change, how can I only run tasks on the changed files?

I've got an initConfig with this code in it:
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
options: {
spawn: false
},
coffee: {
files: [
'src/**/*.coffee'
],
tasks: ['coffee', 'coffeelint', 'concat', 'qunit']
},
...
coffee: {
glob_to_multiple: {
expand: true,
flatten: false,
cwd: '.',
src: ['src/**/*.coffee'],
ext: '.js'
}
},
...
grunt.event.on('watch', function (action, filepath) {
if (grunt.file.isMatch("**/*.coffee", filepath)) {
grunt.config(['coffee', 'glob_to_multiple', 'src'], filepath);
}
});
This is supposed to compile only the .coffee files that have changed. This works pretty well. But I just noticed that if I modify multiple at once, it will output this:
Waiting...src\test\resources\app\js\FILE1.coffee
src\main\resources\app\js\FILE2.coffee
OK
>> File "src\test\resources\app\js\FILE1.coffee" changed.
>> File "src\main\resources\app\js\FILE2.coffee" changed.
Running "coffee:glob_to_multiple" (coffee) task
File src/main/resources/app/js/FILE2.js created.
...
As you can see, I've changed two files, but it's only running the tasks on "FILE2.js". How can I avoid this? I want it to run coffee:glob_to_multiple on FILE1 and FILE2, not just one of them.
NOTE: I'm pretty sure the documentation explains how to do this:
If you save multiple files simultaneously you may opt for a more robust method:
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
grunt.config(['jshint', 'all'], Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
Following that documentation, I made this change to my code:
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
grunt.config(['coffee', 'glob_to_multiple', 'src'], Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
if (grunt.file.isMatch("**/*.coffee", filepath)) {
changedFiles[filepath] = action;
onChange();
}
});
And things worked exactly the way I want. But I'm not sure how this works. Could someone explain it to me?
It's a pretty sophisticated solution using Lo-Dash debounce ;-) (in a sec...)
Know that when you used your older code of:
grunt.config(['coffee', 'glob_to_multiple', 'src'], filepath);
Grunt is instructed to run the coffee task with the new file. The problem with this is that it's a synchronic process and so when another file is changed ( usually this happens in a matter of milliseconds) then Grunt Watch won't allow you to run another process until the debounceDelay has been reached.
The default debounceDelay is 500 ms, but this can be changed using options of the watch task. (read more About option.debounceDelay
Basically when you save multiple files, as you saw - only the first file saved is changed. In order to bypass this, a great utility for delaying (debouncing) function run is in the grunt.util._.debounce (Lo-Dash link to it is here
The function's parameters are:
_.debounce(func, wait, options)
So it takes in the function, how many ms to wait, and some options (that we don't need here).
When you call the debounce utility it will delay the execution of the function the waitTime and that way - when you save multiple files at once - all the calls will add up to a single function call after those 200ms time period.
That way - the most useful line here, besides the debounce util is the following:
changedFiles[filepath] = action;
Which will add the files to the (at first) empty object of changedFiles. Notice that after the debounce function has launched we reset the changedFiles obj so that the next call will contain only freshly changed files.
Amazing solution indeed ;-)

Resources