Interpolated array in Grunt template is interpreted as a string - gruntjs

Previous title: "Why is Grunt's concat task not using dynamic configuration values?"
I am trying to dynamically configure the files that are concatenated by Grunt, and in doing so I came across this issue where the grunt-contrib-concat plugin does not seem to pick up the dynamically set values. At first I thought I was doing something wrong, but after creating my own task and using the same dynamic values everything came out just as intended. So that leaves the question of why is the grunt concat task not doing picking up and using the same values?
A gruntfile that reproduces the behaviour is seen below (gist: fatso83/73875acd1fa3662ef360).
// Grunt file that shows how dynamic config (and option!) values
// are not used in the grunt-contrib-concat task. Run using 'grunt'
module.exports = function(grunt){
grunt.initConfig({
concat : {
foo : {
nonull : true,
src: '<%= grunt.config.get("myfiles") %>',
dest : 'outfile.txt'
}
},
myTask : {
bar : '<%= grunt.config.get("myfiles") %>'
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerMultiTask('myTask', function() {
grunt.log.writeln('myTask:' + this.target + ' data=' + this.data);
});
grunt.registerTask('default', ['myTask','concat']);
grunt.config.set('myfiles',['file1.txt', 'file2.txt'])
}
EDIT: A new lead:
After literally hours of going nowhere I came across this sentence on Grunt's homepage:
nonull If set to true then the operation will include non-matching
patterns. Combined with grunt's --verbose flag, this option can help
debug file path issues.
Adding that to the config (edited above to reflect this) I got this error message which at least show that something is doing something with the dynamic values:
Running "concat:foo" (concat) task
>> Source file "file1.txt,file2.txt" not found.
Warning: Unable to write "outfile.txt/file1.txt,file2.txt" file
(Error code: ENOTDIR). Use --force to continue.
After some more debugging in the other task, myTask, I have found out that the data sent in to the task as this.data is a string value, not an array. This is perhaps not very surprising, given that we do string interpolation, but this is not consistent with other interpolation features. For instance will <%= otherTask.fooTarget.src %> get the other task's src property as an array value.
Now the question is really how can I avoid passing the interpolated value as an array, rather than a string, to the concat task?

Updated after reading up on the Grunt source code
After I found out that the problem was our array was interpreted as a string I quickly found a related question with a solution that seemed promising. Simply by enclosing the interpolated array string with curly brackets Grunt was able to find the files!
Unfortunately, the globbing pattern we are effectively creating does not preserve the specified file order. In the related question above I posted a thorough explanation of what was going on and how you can work around it in the general case.
For my specific case, where I reference a field in the configuration object there is actually no need for a function call to retreive it as it is directly available in the templates own scope! Therefore, instead of calling grunt.config.get('myfiles'), I can simply do <%= myfiles %>.
For the example above:
grunt.initConfig({
concat : {
foo : {
nonull : true,
src: '<%= myfiles %>',
dest : 'outfile.txt'
}
},
myTask : {
bar : '<%= myfiles %>'
}
});

Related

Grunt relative file path globbing

Is it possible to use Globbing partially on a directory in a file path?
I have a grunt-contrib-less task set up, the file path for my task looks something like this:
files: {
"../../application/user/themes/some-theme-5.1.1.5830/css/main.css": "less/base.less",
}
However the version number in the relative path may sometime change, such as:
files: {
"../../application/user/themes/some-theme-5.1.1.5831/css/main.css": "less/base.less",
}
Ideally I'd like to something like this:
files: {
"../../application/user/themes/some-theme-*/css/main.css": "less/base.less",
}
Is there a way of doing this? With the above syntax it stops searching after the asterisk.
One potential solution to achieve this is to utilize grunts --options feature.
When running a grunt task via the command line it is possible to specify an additional options value.
In your scenario you could pass in the version number of the folder name that is going to change. (I.e. In your case the part that you tried to specify using the asterisk character (*) E.g. '5.1.1.5830'
Caveat: For this solution to be of any use it does require knowing what that value, (the version number), of the destination folder is upfront prior to running the task via the command line.
Example Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
themesFolder: {
namePart: '0.0.0.0' // <-- If no option is passed via the CLI this name will be used.
},
less: {
production: {
options: {
// ...
},
files: {
// The destination path below utilizes a grunt template for the part
// of the folder name that will change. E.g. '5.1.1.0'
'../../application/user/themes/some-theme-<%= themesFolder.name %>/css/main.css': 'less/base.less'
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('saveFolderNameFromOption', 'Uses the option provided to configure name part.', function(n) {
var themesFolder = grunt.option('themesFolder');
if (themesFolder) {
// Store the option value so it can be referenced in the less task.
grunt.config('themesFolder.namePart', themesFolder);
}
});
grunt.registerTask('processLess', ['saveFolderNameFromOption', 'less:production']);
};
Running the ProcessLess task
Run the task via the command line as follows:
$ grunt processLess --themesFolder=5.1.1.5830
Note: The additional option that is specified. Namely: --themesFolder=5.1.1.5830
When using the above command the .css output will be directed to the following path:
'../../application/user/themes/some-theme-5.1.1.5830/css/main.css': 'less/base.less'
Now, each time you run the task you modify the options accordingly.
Benefits: By providing the version number as an option via the CLI will avoid having to reconfigure your Gruntfile.js each time it is run.

Grunt-complexity on all the files in a directory

I'd like to run Grunt-Complexity on all the files in a directory?
I'd like to get this kind of output.
Is there a way?
My js files are all under a subdirectory called "js".
Here's my gruntfile:
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
// Task configuration.
complexity: {
generic: {
src: ['grunt.js', 'js/*'],
//exclude: ['doNotTest.js'],
options: {
breakOnErrors: false,
jsLintXML: 'report.xml', // create XML JSLint-like report
checkstyleXML: 'checkstyle.xml', // create checkstyle report
pmdXML: 'pmd.xml', // create pmd report
errorsOnly: false, // show only maintainability errors
cyclomatic: [3, 7, 12], // or optionally a single value, like 3
halstead: [8, 13, 20], // or optionally a single value, like 8
maintainability: 100,
hideComplexFunctions: false, // only display maintainability
broadcast: false // broadcast data over event-bus
}
}
}
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-complexity');
// Default task.
grunt.registerTask('default', 'complexity');
};
I'm simply calling this by typing
grunt
from the command line.
then if I type this
grunt complexity js/*
I get
Warning: Task "js/AgencyMediaController.js" not found. Use --force to continue.
Aborted due to warnings.
And AgencyMediaController.js is the first file in my js directory. So it's having a look and listing the files, but then it crashes.
Thanx!
example:
for all js file in JS folder:
src: ['js/**/*.js']
for ass .scss files in scss folder:
src: ['scss/**/*.scss']
I suggest for you create a config for your src folder can be easy in future folder changes in future projects:
sample:
var src;
config.src = src = {
sassMain : 'scss/main.scss',
distFolder : 'public/stylesheets/lovelycss.dist.css',
devFolder : 'public/stylesheets/lovelycss.dev.css',
libFolder : 'lib/**/*.js',
sassFolder : 'scss/**/*.scss',
spriteCssFolder : 'scss/helpers/_sprite.scss',
spriteDestImg : 'public/images/sprite/spritesheet.png',
spriteSrc : 'public/images/min/*.{png,jpg,gif}',
imageminCwd : 'public/images/',
imageminDest : 'public/images/min'
};
//grunt Watch ===============================
config.watch = {
scripts: {
files: ["<%= src.libFolder %>", "<%= src.sassFolder %>"]
,tasks: ["dev", "sass:dist"]
//,tasks: ["dev",'sass:dist']
}
}
I hope that helped you.
It's been quite a long while since I asked this question. I just ran into the same issue again and found the answer so here it is:
In the end it turned out to be that one of the files I was trying to analyse was causing the crash. This particular Javascript environment allows for C-like preprocessor directives and the Javascript file had something like this:
var mySettings = {
//#ifdef FOO_CONSTANT
setting : constants.FOO_SETTING
//#endif
//#ifdef BAR_CONSTANT
setting : constants.BAR_SETTING
//#endif
};
I guess the problem is that if this is read as strictly Javascript, the preprocessor directives are just plane comments, and there's a comma missing between the two properties, so Grunt complexity is unable to read this because of a syntax error. Using --force makes no difference BTW.
The annoying part is that this is all the error shows:
$ grunt --force
Running "complexity:generic" (complexity) task
Warning: undefined: Unexpected token, expected , (17570:1) Used --force, continuing.
Done, but with warnings.
So while it does say expected , (175:1) it doesn't say in which of the several Javascript files in this project the problem was found!
Just adding exclude: ['path/to/MyFileWithPreprocessorDirectives.js'] to Gruntfile.js in order to exclude this file from the analysis gets me around the problem.

Change format of the Source map created from closure compiler

I have this closure-compiler task defined with following options:
'closure-compiler': {
files: {
},
options: {
externs: [],
compilation_level: 'ADVANCED_OPTIMIZATIONS',
language_in: 'ECMASCRIPT5_STRICT',
create_source_map: '<%= sourceDir %>js/<%= outputName %>.min.js.map',
output_wrapper: '%output%\n//# sourceMappingURL=<%= sourceMapURL %>js/<%= outputName %>.min.js.map'
}
}
the sourcemap is created and it looks like this:
{
"version":3,
"file":"build/js/game.min.js",
"lineCount":39,
"mappings":"AAEA,...",
"sources":["/src/js/utils.js","/src/js/game/Button.js",...],
"names":[...]
}
but then the source map doesnt work, what I need is:
{
"version":3,
"file":"game.min.js",
"lineCount":39,
"mappings":"AAEA,...",
"sources":["utils.js","game/Button.js",...],
"names":[...]
}
what should I do to have the sourcemap created in that form?
For Grunt, there are many options for sourcemaps that have to be handled as a separate build step. It lacks the power of the gulp-sourcemaps plugin and so either each tool has to handle every conceivable option for generating a sourcemap or another tool must be used.
Post processing a sourcemap in this fashion isn't too difficult as sourcemaps are JSON data.
grunt-sourcemap-localize looks to do exactly what you are wanting.

How can I skip a grunt task if a directory is empty

I'm using grunt-contrib's concat and uglify modules to process some javascript. Currently if src/js/ is empty, they will still create an (empty) concat'd file, along with the minified version and a source map.
I want to task to detect if the src/js/ folder is empty before proceeding, and if it is, then the task should skip (not fail). Any ideas how to do this?
The solution may not be the prettiest, but could give you an idea. You'll need to run something like npm install --save-dev glob first. This is based on part of the Milkshake project you mentioned.
grunt.registerTask('build_js', function(){
// get first task's `src` config property and see
// if any file matches the glob pattern
if (grunt.config('concat').js.src.some(function(src){
return require('glob').sync(src).length;
})) {
// if so, run the task chain
grunt.task.run([
'trimtrailingspaces:js'
, 'concat:js'
, 'uglify:yomama'
]);
}
});
A gist for comparison: https://gist.github.com/kosmotaur/61bff2bc807b28a9fcfa
With this plugin:
https://www.npmjs.org/package/grunt-file-exists
You can check file existence. (I didn't try, but the source looks like supporting grunt expands. (*, ** ...)
For example like this::
grunt.initConfig({
fileExists: {
scripts: ['a.js', 'b.js']
},
});
grunt.registerTask('conditionaltask', [
'fileExists',
'maintask',
]);
But maybe if the file doesn't exist it will fail with error instead of simple skip.
(I didn't test it.)
If this is a problem you can modify a bit the source of this plugin to run the related task if the file exists:
The config:
grunt.initConfig({
fileExists: {
scripts: ['a.js', 'b.js'],
options: {tasks: ['maintask']}
},
});
grunt.registerTask('conditionaltask', [
'fileExists',
]);
And you should add this:
grunt.task.run(options.tasks);
In this file:
https://github.com/alexeiskachykhin/grunt-file-exists/blob/master/tasks/fileExists.js
after this line:
grunt.log.ok();
Maybe this is just a more up-to-date answer as the others are more than a year old, but you don't need a plugin for this; you can use grunt.file.expand to test if files matching a certain globbing pattern exist.
Update of #Kosmotaur's answer (path is just hard-code here though for simplicity):
grunt.registerTask('build_js', function(){
// if any file matches the glob pattern
if (grunt.file.expand("subdir/**/*.js").length) { /** new bit here **/
// if so, run the task chain
grunt.task.run([
'trimtrailingspaces:js'
, 'concat:js'
, 'uglify:yomama'
]);
}
});

Use Grunt task variables in config

I'd like to define variables in the config of my Grunt task, which are being replaced by my task.
My use-case is that I'm trying to create separate JS output files, based on a config.json which contains configurations for multiple sites.
An example of my config:
extractProjectConfigs: {
options: {
merge: '<%= yeoman.dist %>/extracted.js',
configWrapper: 'window.config = {{ extractedConfig }};'
},
prod: {
src: ['./config/*.json'],
dest: '.tmp/scripts/{{ configKey }}/searchbox.js'
}
}
In my extractProjectConfigs task, I define the variables configKey and extractedConfig and I'd like the values of both to be in my config.
How can I achieve this? I already tried including Grunt Templates in my config definition (i.e. <%= extractedConfig %> or <%= configKey %>), but since Grunt Template parses variables before passing them to my task, it basically means my variables turn into empty strings.
Turned out Grunt Templates were the right way to go. Grunt Templates parse variables defined by <%= %> before passing them to the task. However, Grunt Templates also offer a way to determine your own variable delimiter, which allows you to use Grunt Templates in your task that are parsed after the task ran, meaning I can use variables in my config.
I ended up using the following config:
extractProjectConfigs: {
options: {
merge: '<%= yeoman.dist %>/extracted.js',
configWrapper: 'window.config = {{= extractedConfig }};'
},
prod: {
src: ['./config/*.json'],
dest: '.tmp/scripts/{{= configKey }}/searchbox.js'
}
}
In my task, I used the following custom Grunt template function to parse the config:
function parseTemplate(template, vars) {
grunt.template.addDelimiters('double-brackets', '{{', '}}');
return grunt.template.process(template, {
data: vars,
delimiters: 'double-brackets'
});
}
This function can then be used within the task, for instance as follows:
var dest = parseTemplate(file.dest, {
projectKey: 'foo'
});
This can then be used within grunt.file.write: grunt.file.write(dest, 'write something to dest with projectKey replaced by foo');

Resources