Grunt Concat many files in many folders while maintaining folder structure - gruntjs

I am using Grunt concat in my project. I want to combine files on a folder level.
E.G.
js >
parent >
child1 >
a.js
b.js
c.js
child2 >
a.js
b.js
into:
js >
parent >
child1 >
child1combined.js
child2 >
child2combined.js
is there a way of doing this without specifically adding each "child" in its own concat line?

I ended up doing the following I found in this post.
grunt.registerTask("taskName", "Task Description", function() {
// get all module directories
grunt.file.expand("src/js/parent/*").forEach(function (dir) {
// get the module name from the directory name
var dirName = dir.substr(dir.lastIndexOf('/')+1);
// get the current concat object from initConfig
var concat = grunt.config.get('concat') || {};
// create a subtask for each module, find all src files
// and combine into a single js file per module
concat[dirName] = {
src: [dir + '/**/*.js'],
dest: 'build/js/parent/' + dirName + '/combined.js'
};
// add module subtasks to the concat task in initConfig
grunt.config.set('concat', concat);
});
});
Then just call taskName from your registerTask.

Use globbing patterns in your concat task. To quote the linked documentation:
It is often impractical to specify all source filepaths individually,
so Grunt supports filename expansion (also know as globbing).

I know this topic is old, but I want to share my solution because I couldn't find a simple solution working as expected on the web.
concat: {
files: [
{
expand: true,
src: ["**/*.js"],
dest: "dest/js",
cwd: "src/js",
rename: function(dst, src) {
const srcParts = String(src).split("/");
let dstPath = "";
let dstName = srcParts[0].split(".")[0];
if (srcParts.length > 1) {
dstName = srcParts[srcParts.length - 2];
dstPath =
srcParts
.slice(0, srcParts.length - 1)
.join("/") + "/";
}
return `${dst}/${dstPath + dstName}.js`;
}
}
]
}
I hope this helps someone :)

Related

How to use grunt to copy a series of files to the same directory they come from?

I have a series of directories like...
component A
- t9n
- componentAT9n.json
component B
- t9n
- componentBT9N.json
Where each of these directories I need to duplicate the one file ending in t9n.json to _en.json ultimately ending up with...
component A
- t9n
- componentAT9n.json
- componentAT9n_en.json
component B
- t9n
- componentBT9N.json
- componentBT9n_en.json
Using grunt is how I'm trying to do it, but I'm having a hard time figuring out how to copy each file it matches to the same directory.
Is this something I can accomplish just using grunt and grunt-contrib-copy ? Or is there maybe another plugin to do this?
In the copy task, I know I can use glob patterns to dynamically grab the source, but the destination I'm unsure of.
files: [
{
cwd: "src/app/js",
src: ["**/t9n/*.json"],
dest: ???,
expand: true
}
]
This situation ended up being just too complicated that I had to make a custom grunt task for it, this is what I did.
// Copies the root t9n file for each t9n directory to an _en.json file
grunt.registerTask("create-en-t9n-files", function() {
grunt.file
.expand("path/to/files/**/t9n/t9n+([A-Za-z0-9]).json")
.forEach(function(source) {
var destinationPath = source.split("/t9n/")[0] + "/t9n/";
var fileName = source.split("/t9n/")[1].split(".json")[0];
var enName = fileName + "_en.json";
grunt.file.copy(source, destinationPath + enName);
});
});

grunt-contrib-copy syntax for process option confusion

I'm trying to replace some placeholders in different files as I copy. My gruntfile works fine, but adding in the process option to do the replacements, it's just not working. Below is the relevant section of my gruntfile:
grunt.initConfig({
copy: {
js: {
files: [{
expand: true,
cwd: 'src/wp-content/themes/pilau-starter/',
src: ['**/*.js'],
dest: 'public/wp-content/themes/pilau-starter/'
}],
options: {
process: function ( content ) {
console.log( content );
content = content.replace( /pilauBreakpointLarge/g, breakpoints.large );
content = content.replace( /pilauBreakpointMedium/g, breakpoints.medium );
return content;
}
}
},
}
});
The paths can be understood in the context of the code on GitHub: https://github.com/pilau/starter (the public directory isn't committed to the repo because it's a starter theme). Those paths are variables in my original Gruntfile, and are working fine in all other tasks.
All the vars are set up OK. I've included the console.log( content ) to check if the process function's actually running - it doesn't seem to be, so I guess it's basic syntax.
There's an answer (https://stackoverflow.com/a/28600474/1087660) which seems to address this, but as far as I can tell, that way of doing it is simply bad JS syntax - not sure how it got marked as right.
--verbose output for running the copy task:
Running "copy:js" (copy) task
Verifying property copy.js exists in config...OK
Files: src/wp-content/themes/pilau-starter/js/admin.js -> public/wp-content/themes/pilau-starter/js/admin.js
Files: src/wp-content/themes/pilau-starter/js/flickity.js -> public/wp-content/themes/pilau-starter/js/flickity.js
Files: src/wp-content/themes/pilau-starter/js/global.js -> public/wp-content/themes/pilau-starter/js/global.js
Files: src/wp-content/themes/pilau-starter/js/modernizr.js -> public/wp-content/themes/pilau-starter/js/modernizr.js
Files: src/wp-content/themes/pilau-starter/js/picturefill.js -> public/wp-content/themes/pilau-starter/js/picturefill.js
Files: src/wp-content/themes/pilau-starter/js/respond.js -> public/wp-content/themes/pilau-starter/js/respond.js
Options: processContent=false, processContentExclude=[], process=undefined
Options: processContent=false, processContentExclude=[], process=undefined
Copying src/wp-content/themes/pilau-starter/js/admin.js -> public/wp-content/themes/pilau-starter/js/admin.js
Reading src/wp-content/themes/pilau-starter/js/admin.js...OK
Writing public/wp-content/themes/pilau-starter/js/admin.js...OK
Your version of grunt-contrib-copy is 0.4.0. As correctly point out by #nemesv above the property name to use in this version would be processContent not process.
I cloned your repo and switched to json-breakpoints branch. And ran grunt copy:js and it replaced the content.
Now,when you run grunt copy:js --verbose it will still show this
processContent is logged undefined because grunt uses JSON.stringify to log a value. And JSON.stringify returns undefined when you pass it a function definition.
If you are interested, here's the method reponsible for logging all the option
Log.prototype.writeflags = function(obj, prefix) {
var wordlist;
if (Array.isArray(obj)) {
wordlist = this.wordlist(obj);
} else if (typeof obj === 'object' && obj) {
wordlist = this.wordlist(Object.keys(obj).map(function(key) {
var val = obj[key];
return key + (val === true ? '' : '=' + JSON.stringify(val));
}));
}
this._writeln((prefix || 'Flags') + ': ' + (wordlist || '(none)'.cyan));
return this;
};
This doesn't appear to be an issue with the process option at all, but more an issue with srcThemeDir. I would log it to make sure you know exactly what it is, as it appears that it is causing the copy task to not find any files (and therefore not call the process function).

Grunt convert markdown to pdf recursively and dynamically

I am stuck writing a Gruntfile which aim is to convert a bunch of Markdown files to PDF dynamically. Giving the current folder hierarchy:
root/
|_subfolder1
| |_filename1.md
|_subfolder2
|_filename2.md
...
|_node_modules
|_subfolderN
|filenameN.md
I would like to run a Markdown to PDF task which would process the md file and ouput a PDF file with the matching filename in the same output directory.
I did create a custom task which is parsing current directory, ignoring the mode_modules folder and get the markdown file, but I don't know how to configure the md2pdf task with the good properties to reflect dynamic folder mapping.
Here's my current Gruntfile:
module.exports = function(grunt) {
// 1 - Configuration
grunt.initConfig({
md2pdf: {
}
});
// 2 - Plugins
grunt.loadNpmTasks('grunt-md2pdf');
// 3 - Task registering
grunt.registerTask('default', 'Get Subfolders', function() {
grunt.file.recurse('.', callback);
function callback(abspath, rootdir, subdir, filename) {
var filenameOutExt;
// if current occurence is a file subdir == undefined
// checking subdir to true means it's not undefined and
// the current path is a directory
if(subdir) {
// excluding node_modules folder
if (!subdir.match('node_modules')) {
// only process markdown files
if(filename.match('.md')) {
filenameOutExt = filename.split('.')[0];
// now for each markdown files, run md2pdf task
// and ouput filenameOutExt.pdf in same folder
// as the input files
}
}
}
}
});
};
I am using this plugin: https://www.npmjs.com/package/grunt-md2pdf
So my questions is how should I configure the md2pdf task to pass it the markdown files and generate matching filename pdf output in same directory.
Output should be:
root/
|_subfolder1
| |_filename1.md
|_filename1.pdf
|_subfolder2
|_filename2.md
|_filename2.pdf
...
|_node_modules
|_subfolderN
|filenameN.md
|_filenameN.pdf
Thanks a lot
According to this line, this task uses the grunt.files utilitary function. This makes things easier for us!
First, it's not usual in grunt to create a different task to find the files you need in any other task.
That is, each task should receive the files it needs to operate on. For example...
coffee:
main:
files: [
expand: true
cwd: 'assets/script'
src: ['**/*.coffee']
dest: "assets/script"
ext: '.js'
]
(Note that this config is in a Gruntfile.coffee file, hence the CoffeeScript syntax)
This type of file configuration using glob expansion is one of the most common. You can find details in the documentation.
It's pretty obvious:
In every directory (**), take everything (/*) from assets/script/ that ends with .coffee. Put it into assets/script. Rename extensions to .js.
So, your task can probably be configured like that:
md2pdf: {
main: {
files: [ {
expand: true,
src: ['**/*.md', '!node_modules/**/*'],
dest: "pdf/"
} ]
}
}
Ok figured it out !
module.exports = function(grunt) {
// 2 - Plugins
grunt.loadNpmTasks('grunt-md2pdf');
grunt.registerTask('default', 'Dynamically generate PDF from MD', function() {
grunt.file.expand("./**/*.md").forEach( function(file) {
if(!file.match('./node_modules')) {
var md2pdf = grunt.config.get('md2pdf') || {};
md2pdf[file] = {
src: file,
dest: file + '.pdf'
};
grunt.config.set('md2pdf', md2pdf);
}
});
grunt.task.run('md2pdf');
});
};

How to get grunt.file.readJSON() to wait until file is generated by another task

I'm working on setting up series of grunt tasks that work with RequireJS r.js compiler:
1) generates a .json file listing of all files in a directory
2) strips the ".js" from the filename (requirejs requires this)
3) use grunt.file.readJSON() to parse that file and use as a configuration option in my requirejs compilation task.
Here is the relevant code from my gruntfile.js:
module.exports = function (grunt) {
grunt.initConfig({
// create automatic list of all js code modules for requirejs to build
fileslist: {
modules: {
dest: 'content/js/auto-modules.json',
includes: ['**/*.js', '!app.js', '!libs/*'],
base: 'content/js',
itemTemplate: '\t{' +
'\n\t\t"name": "<%= File %>",' +
'\n\t\t"exclude": ["main"]' +
'\n\t}',
itemSeparator: ',\n',
listTemplate: '[' +
'\n\t<%= items %>\n' +
'\n]'
}
},
// remove .js from filenames in module list
replace: {
nodotjs: {
src: ['content/js/auto-modules.json'],
overwrite: true,
replacements: [
{ from: ".js", to: "" }
]
}
},
// do the requirejs bundling & minification
requirejs: {
compile: {
options: {
appDir: 'content/js',
baseUrl: '.',
mainConfigFile: 'content/js/app.js',
dir: 'content/js-build',
modules: grunt.file.readJSON('content/js/auto-modules.json'),
paths: {
jquery: "empty:",
modernizr: "empty:"
},
generateSourceMaps: true,
optimize: "uglify2",
preserveLicenseComments: false,
//findNestedDependencies: true,
wrapShim: true
}
}
}
});
grunt.loadNpmTasks('grunt-fileslist');
grunt.loadNpmTasks('grunt-text-replace');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.registerTask('default', ['fileslist','replace', 'requirejs']);
I'm running into a problem where, if the "content/js/auto-modules.json" file doesn't already exist on load of my config file, the file.readJSON() is executed immediately, before the file exists and the entire task fails and throws "Error: Unable to read file " If the file already exists, everything works beautifully.
How can I set this up so that the task configuration waits for that file to be created in the first task, and modified in the second task before it tries to load & parse the JSON in it for the third task? Or is there another way (perhaps using a different plugin) to generate a json object in one task, and then pass that object to another task?
Old post but I had a similar experience.
I was trying to load a some json config like:
conf: grunt.file.readJSON('conf.json'),
but if this file did not exist then it would fall in a heap and not do anything.
So I did the following to load it and populate defaults if it didnt exist:
grunt.registerTask('checkConf', 'ensure conf.json is present', function(){
var conf = {};
try{
conf = grunt.file.readJSON('./conf.json');
} catch (e){
conf.foo = "";
conf.bar = "";
grunt.file.write("./conf.json", JSON.stringify(conf) );
}
grunt.config.set('conf', conf);
});
You still may have some timing issues but this approach may help someone with a readJSON error.

Dynamic mapping for destinations in grunt.js

I have a project with several sub folders that contain JavaScript files I want to concatenate. what would be the right way to configure them?
eg.
source: /modules/$modulename/js/*.js (several files)
dest: /modules/$modulename/js/compiled.js
So what I want to do is to compile js-files of an unknown/unconfigured count of subfolders ($modulename) into one file per subfolder.
Is this possible?
The following function (built after hereandnow78's instructions) does the job:
grunt.registerTask('preparemodulejs', 'iterates over all module directories and compiles modules js files', function() {
// read all subdirectories from your modules folder
grunt.file.expand('./modules/*').forEach(function(dir){
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: [dir + '/js/*.js', '!' + dir + '/js/compiled.js'],
dest: dir + '/js/compiled.js'
};
// save the new concat config
grunt.config.set('concat', concat);
});
});
after that i put preparemodulejs before the concat job in my default configuration.
you will probably need to code your own task, where you iterate over your subfolders, and dynamically append to your concat configuration.
grunt.registerTask("your-task-name", "your description", function() {
// read all subdirectories from your modules folder
grunt.file.expand("./modules/*").forEach(function (dir) {
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: ['/modules/' + dir + '/js/*.js', '!/modules/' + dir + '/js/compiled.js'],
dest: '/modules/' + dir + '/js/compiled.js'
};
// save the new concat configuration
grunt.config.set('concat', concat);
});
// when finished run the concatinations
grunt.task.run('concat');
});
run this with:
$ grunt your-task-name
this code is untested, but i think it should do your job.
HINT: you can put this code into an external file and include in your gruntfile if you want to keep your gruntfile small, e.g. put this into a file inside a tasks-directory:
module.exports = function(grunt) {
grunt.registerTask("your-task-name", "your description", function() {
...
});
};
and load in in your gruntfile:
grunt.loadTasks("./tasks");

Resources