Grunt Watch Event with Grunt Copy for Only Changed Files - gruntjs

Okay I've been stuck on this for 2 weeks, so hopefully someone else here has run across this problem. I'm trying to use Grunt to copy only files that have changed. I've seen numerous examples of how to do this with JSLINT and UGLIFY but no specific examples on how to do this with grunt-contrib-copy.
When you register a watch event and pass the filename to the copy subtask, the file name is accessible (i'm logging it out), but the file never copies over properly.
I'm hoping its a simple thing that i'm overlooking. Could someone please have a look at my code and see what I'm doing wrong?
//Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
options: {
base: 'app',
dist: 'dist',
},
copy: {
changedFiles: {
expand: true,
dot: true,
cwd: '<%= options.base %>',
src: ['**/*.*'],
dest: '<%= options.dist %>/'
}
},
watch: {
options: {
nospawn: true,
//debounceDelay: 1000,
},
css: {
files: ['app/css/*.css',
'app/js/*.js'
],
tasks: ['copy:changedFiles'],
}
}
});
grunt.event.on('watch', function(action, filepath, target){
grunt.log.writeln('target: ', target + '\n filepath: ' + filepath + '\n action: has ' + action);
grunt.config('copy.changedFiles.src', new Array(filepath) );
});
//load our copy task
grunt.loadNpmTasks('grunt-contrib-copy');
//load our watch task
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('copyChangedFiles', [
'watch:css'
]);
};
Basically my folder setup is as such:
-app
| - css
| - js
-dist
I'm watching the app folder and trying to copy files that change in the app directory and copy them to the dist directory. Dynamically modifying the copy src doesn't seem to be working.
The copy task when run by itself with watch and not on the watch event works just fine and copies every file over, but I'm interested in copying only files that change.
I've also tried a variation of this within my watch event, to no avail:
var copyDest = filepath.replace(grunt.config('copy.changedFiles.dest'), '');
var copyCwd = filepath.replace(grunt.config('copy.changedFiles.cwd'), '');
grunt.config('copy.changedFiles.cwd' , copyCwd);
grunt.config(['copy', 'changedFiles', 'src'] , [filepath]);
Has anyone ever successfully done this before using grunt copy? Or is there another task I should be using? I've tried the same with grunt-sync and that didn't seem to work either. I'm stuck.
Thanks for the help.

You should be able to use the grunt-newer package. The only thing I have noticed about this is that it doesn't do the delete action if the files are removed from the source and are currently in the destination of the copy.
However, for most purposes this should perform the task that you are looking for. Watch will be triggered on a file change, newer will only run if the files in destination are older than the src.
Note: nospawn is deprecated, and is now spawn. It was left for backwards compatibility.
I'm not sure it makes sense for files: [<pattern>] to not match the src pattern described in the copy task.
module.exports = function(grunt) {
grunt.initConfig({
options: {
base: 'app',
dist: 'dist',
},
copy: {
changedFiles: {
expand: true,
dot: true,
cwd: '<%= options.base %>',
src: ['**/*.*'],
dest: '<%= options.dist %>/'
}
},
watch: {
options: {
//nospawn is depricated but kept for compatibility. use spawn false instead
spawn: false,
cwd: '<%= options.base %>'
//debounceDelay: 1000,
},
css: {
//should match above
files: ['**/*.*'],
//On new file detection run copy:changedFiles
tasks: ['newer:copy:changedFiles']
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-newer');
grunt.registerTask('copyChangedFiles', ['watch:css']);
};

I was able to use this SO answer: How to modify grunt watch tasks based on the file changed? and modify it to suit my needs. I'm now able to copy only the files that changed.
My watch now looks like this:
var path = require('path');
grunt.event.on('watch', function(action, filepath, target){
grunt.log.writeln(target + ': ' + filepath + ' might have ' + action);
var siteDirectory = path.dirname(filepath);
//changes changed file source to that of the changed file
var option = 'copy.changedFiles.src';
var result = filepath;
grunt.log.writeln(option + ' changed to ' + result);
grunt.config(option, result);
//customizes output directory so that file goes to correct place
option = 'copy.changedFiles.dest';
result = path.resolve(__dirname + '/dist');
grunt.log.writeln(option + ' changed to ' + result);
grunt.config(option, result);
});
Now running grunt copyChangedFiles will watch the app directory for changes, and any time a *.css or *.js file is modified, it will copy it over to the dist directory.
I really hope this helps someone else since I spent two weeks getting this to work properly.

Related

Component-preload.js generated with comments

I'm working on some performance aspects for my app and, as a consequence, trying to remove the comments from the Component-preload.js file generated when building the app (I'm basically trying to uglify the component-preload)
To do that, I firstly tried to use the "#sap/grunt-sapui5-bestpractice-build" as in its documentation is stated that there's an uglify, but unfortunately it doesn't remove the comments.
Now, I'm working with external plugins to be able to achieve this, but it seems is just "impossible".
This is my gruntfile.js for the moment:
"use strict";
// Initial standard SAP Plugin
grunt.loadNpmTasks("#sap/grunt-sapui5-bestpractice-build");
// Copy plugin configuration
var copyConfig = {
copy: {
main: {
expand: true,
cwd: 'webapp/controller',
src: '**',
dest: 'webapp/aux',
}
}
};
grunt.config.merge(copyConfig);
grunt.loadNpmTasks("grunt-contrib-copy");
// Uglify plugin configuration
var uglifyConfig = {
uglify: {
dist: {
options:{
mangle:false
},
output: {
comments: false
},
files: [{
expand: true,
cwd: 'webapp/controller',
src: '**/*.js',
dest: 'webapp/controller'
}]
}
}
};
grunt.config.merge(uglifyConfig);
grunt.loadNpmTasks("grunt-contrib-uglify");
// Move plugin configuration
var moveConfig = {
move: {
main: {
src: 'webapp/aux/*',
dest: 'webapp/controller/'
}
}
};
grunt.config.merge(moveConfig);
grunt.loadNpmTasks("grunt-move");
// Custom clean plugin configuration
var cleanConfig = {
clean: {
custom: {
src: 'webapp/aux'
}
}
};
grunt.config.merge(cleanConfig);
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.registerTask("default", [
"clean",
"lint",
"copy",
"uglify",
"build",
"move",
"clean:custom"
]);
As you can see, I'm basically:
Copying the sources with comments.
Uglifying the original ones.
Calling the build ( expecting that it uses the uglified sources to generated the Component-preload.js without comments )
Restoring the ones that have comments into the "Controller" folder again.
At this point, I don't know which files is the IDE using to generate the Component-preload.js because I've even tried to do a build with:
Move: Moving the files from the "Controller" folder to an aux one.
Build: Expecting the build to crash or at least, not having any controller in the Component-preload.js
But the result from this test is that Component-preload.js is created and it contains the controller code, so it seems like before starting the whole procedure, the IDE saves a copy of all files.
Could you kindly help me understanding this? Or at least, how can I remove comments from the generated Component-preload.js file??
Thanks in advance and sorry if I haven't explained anything very clear.
Kr,
Pedro.

Passing arguments from command line to grunt copy task

var pathValue="";
module.exports = function(grunt) {
grunt.initConfig({
copy: {
main: {
files: [{
cwd: 'srcpath', // set working folder / root to copy
src: '**/*', // copy all files and subfolders
dest: 'pathValue', // destination folder
expand: true
}]
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('copy', function(n) {
var target = grunt.option('target');
pathValue = target;
// do something useful with target here
});
};
I'm trying to pass destination path to the copy task from command line. I tired the command
grunt copy --target=destpath
It says "Done without errors" but new folder is not getting created in the destination directory. Can someone please tell me what's the error in the code?
Try changing dest to a qualified js file path. Like this:
dest: 'dest/bob.js'

Grunt with compass not compiling

Hi I'm new to grunt and I'm having a problem getting it up and running with compass.
This is my Gruntfile:
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
//Read the package.json (optional)
pkg: grunt.file.readJSON('package.json'),
// Metadata.
meta: {
sassPath: 'templates/sass/',
cssPath: 'public/css/',
},
// Task configuration.
compass: {
dist: {
files: {
'<%= meta.cssPath %>*.css': '<%= meta.sassPath %>**/*.scss'
}
}
},
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-contrib-compass');
// Default task.
grunt.registerTask('default', ['compass']);
}
But when I run grunt --verbose I get this:
...
Running "default" task
Running "compass" task
Running "compass:dist" (compass) task
Verifying property compass.dist exists in config...OK
Files: templates/sass/ie.scss, templates/sass/print.scss, templates/sass/screen.scss, templates/sass/style.scss -> public/css/*.css
Options: (none)
Nothing to compile. If you're trying to start a new project, you have left off the directory argument.
Run "compass -h" to get help.
Done, without errors.
So it looks like it sees the files, and even sees where to put them... what is going wrong?
EDIT: Just so people know the final answer... I thought I had tried this before without success, but this morning I ended up getting this to work:
compass: {
dist: {
expand: true,
cwd: '<%= meta.scssPath %>',
src: ['{,*/}*.scss'],
dest: '<%= meta.cssPath %>',
ext: '.css'
}
},
EDIT 2: Okay, for the life of me don't know why, but it only works when a config.rb was present... why would grunt use the compass config is another question...
I think the issue is your destination file. You have to specify an actual file, not *.css. Try tweaking your config slightly:
compass: {
dist: {
files: {
'<%= meta.cssPath %>compiled.css': '<%= meta.sassPath %>**/*.scss'
}
}
}

Are there global variables in grunt?

While trying to do some kind of generic grunt task i got stuck while accessing variables/options.
I need some kind of global variable or something similar.
It seems I have overlooked something.
If I run grunt sassCompile:myprojectFolder, everything works fine
while grunt sassWatch:myprojectFolder does not.
I run it in verbose mode and it seems projectPath is empty while compass is being called by watch.
compass options (from verbose output):
sassCompile: config="projectRoot/myprojectFolder/config.rb" ...
sassWatch: config="config.rb" ...
This is the Gruntfile.js is used for testing:
What I am doing wrong?
(function() {
'use strict';
module.exports = function (grunt) {
grunt.initConfig({
compass: {
dev: {
options: {
config: "<%= projectPath %>config.rb",
basePath: "<%= projectPath %>",
specify: ["<%= projectPath %>src/sass/style*.scss","!**/*ie*.scss"],
bundleExec: true
}
}
},
watch: {
css: {
files: ['<%= projectPath %>../**/*.scss'],
tasks: ['compass']
}
}
});
grunt.registerTask('sassCompile', 'compass', function (project) {
grunt.config('projectPath', 'projectRoot/' + project + '/');
grunt.task.run('compass');
});
grunt.registerTask('sassWatch', 'watch', function (project) {
grunt.config('projectPath', 'projectRoot/' + project + '/');
grunt.task.run('watch');
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-compass');
};
}());
This is definitely an interesting approach you have there – it took me a while to understand what is going on.
What happens when run grunt sassWatch:myprojectFolder is that the watch task gets started with the projectPath you supplied after the colon, but, when watch detects a change it runs the compass task without the configuration.
That is because grunt-contrib-watch runs it's tasks by starting a new grunt process. You could run the tasks inside the same process by using options: { spawn: false }, but that seems to be discouraged.
I would suggest you try this:
watch: {
css: {
files: ['<%= projectPath %>../**/*.scss'],
tasks: ['sassCompile:<%= projectPath %>']
}
}
That way watch will run sassCompile:myprojectFolder in the spawned grunt process, making sure the configuration gets transferred to the compass task.
At the grunt page there is a section about global variables, but they won't work across multiple processes.
It would be possible to start a new async process by hand and handover the needed variables.
You could take a look at here and possible extend it to accept parameters for the new processes.

Using constants in gruntjs

I am trying to write my first Grunt task to copy some files from my common libs folder, which is out of my project directory.
Project Folder : /home/user/projects/bottle
Common Libs directory : /home/user/projects/common
Files' source are inside Common Libs directory at : lib/general/static/js/
Files' destination inside project folder : lib
I have a properties.json file with Common Libs directory path as shown below
{
"common_libs" : `/home/user/projects/common`
}
Now what I already tried is :
module.exports = function(grunt) {
var properties = grunt.file.readJSON('properties.json'),
paths = {
common_libs : properties.common_libs,
common_libs_js : this.common_libs + "lib/general/static/js/"
};
grunt.initConfig({
copy: {
main: {
files: [
{
expand: true,
flatten : true,
src: [
paths.common_libs_js + "/*"
],
dest: 'lib/',
filter: 'isFile'
}
]
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
};
I am running grunt as follows
grunt copy
With this no files are copied to the destination.
Help me in this.
Also I want to know
How can I use Ant's property tag type constants in GruntJS? Because I am getting the base folder from properties.json and I need to copy many files from different folders under the base folder.
Can we have these type of constants per task?
Thanks in advance.
There are a few issues with your code. First:
common_libs_js : this.common_libs + "lib/general/static/js/"
Unfortunately this.common_libs is undefined (this doesn't point to where you think it does), so common_libs_js ends up being 'undefinedlib/general/static/js/', which doesn't work.
The second problem is that on that same line you are concatenating paths, but the first path (from the properties file) doesn't seem to end with a slash, and would become '/home/user/projects/commonlib/general/static/js/' if it wasn't for the previous issue.
Third, you'd get a whole bunch of folders inside your dest path. When the expand option is used, Grunt uses the paths as given in the src property to create the folder structure. If you want /home/user/projects/common/lib/general/static/js/foo.js to be copied to lib/foo.js, you should set the cwd option to paths.common_libs_js and the src to '*.js' (or '**/*.js' for a match on any level).
People usually embed configuration properties inside Grunt's config, and then use template strings to access them. A very common way to write your task would be something like this (with a few changes, adjust as needed):
module.exports = function(grunt) {
grunt.initConfig({
properties: grunt.file.readJSON('properties.json'),
copy: {
main: {
expand: true,
cwd: '<%= properties.common_libs %>/lib/general/static/js',
src: '**/*.js',
dest: 'lib'
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
};
Alternatively, if you want your files to end up in 'lib/general/static/js/' in the destination path too:
module.exports = function(grunt) {
grunt.initConfig({
properties: grunt.file.readJSON('properties.json'),
copy: {
main: {
expand: true,
cwd: '<%= properties.common_libs %>',
src: 'lib/general/static/js/**/*.js',
dest: '.' // because src includes 'lib'
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
};
If you're not sure how Grunt sees your files, run it with grunt -v and it will tell you.
You might also want to consider git submodules for a non-Grunt solution.
For task-specific constants: you could use the task's (or target's) options hash for that, and access it with template strings:
module.exports = function(grunt) {
grunt.initConfig({
properties: grunt.file.readJSON('properties.json'),
copy: {
options: {
foo: 'lib'
},
main: {
options: {
bar: '**/*.js'
},
expand: true,
cwd: '<%= properties.common_libs %>/<%= copy.options.foo %>/general/static/js',
src: '<%= copy.options.main.bar %>',
dest: 'lib'
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
};
This is rarely done for anything other than actual options, though. Conflicts with real options can occur. You could also use the target namespace directly, setting the property directly inside main. But again, there are a few property names that may conflict.
If you want to override properties (e.g. for a release build), you can do this:
module.exports = function(grunt) {
grunt.registerTask('release', function() {
grunt.config.set('properties.common_libs', '/usr/lib/shared');
});
grunt.initConfig({
properties: grunt.file.readJSON('properties.json'),
copy: {
main: {
expand: true,
cwd: '<%= properties.common_libs %>/lib/general/static/js',
src: '**/*.js',
dest: 'lib'
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
};
Then you'd call your task with grunt release copy.
EDIT
Based on your updated question, it doesn't seem like the properties.json file is of much use to you. Why not just specify the properties in your Gruntfile?
module.exports = function(grunt) {
grunt.initConfig({
properties: {
base_dir: '../common',
base_js_dir: '<%= properties.base_dir %>/lib/general/static/js',
base_css_dir: '<%= properties.base_dir %>/lib/general/static/css'
},
copy: {
main: {
expand: true,
cwd: '<%= properties.base_js_dir %>',
src: '**/*.js',
dest: 'lib'
}
}
});
grunt.loadNpmTasks('grunt-contrib-copy');
};
You can also use the file together with this, if you want. Or even put these properties (with the template strings) inside the properties.json file. In the end it's just a matter of making Grunt see an object with template strings. It's up to you to provide that somehow.

Resources