How can I rename files with Grunt, based on the respective file's parent folder name?

GruntjsParent ChildRenameBuild Automation

Gruntjs Problem Overview


I have a the following structure:

src/
    modules/
        module1/
            js/
                main.js
            scss/
                main.scss
            index.html
        module2/
            js/
                main.js
            scss/
                main.scss
            index.html

I'd like to run a grunt task to copy these out to the following structure:

dev/
    js/
        module1.js
        module2.js
    css/
        module1.css
        module2.css
    module1.html
    module2.html

Is there a way to do this with an existing grunt plugin? If not, how could I achieve this?

Gruntjs Solutions


Solution 1 - Gruntjs

This can be done using the grunt-contrib-copy plugin.

The main thing to note is that you can change the destination programmatically by using a rename function (which takes in the destination and source of each file).

Here is a (somewhat brittle) sample Gruntfile.js that should copy to your desired structure:

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    copy: {
      main: {
        files: [
          {
            expand: true, 
            cwd: 'src/modules/', 
            src: ['**/*.js'], 
            dest: 'dev/js/', 
            rename: function(dest, src) {
              // use the source directory to create the file
              // example with your directory structure
              //   dest = 'dev/js/'
              //   src = 'module1/js/main.js'
              return dest + src.substring(0, src.indexOf('/')) + '.js';
            }
          },
          {
            expand: true, 
            cwd: 'src/modules/', 
            src: ['**/*.scss'], 
            dest: 'dev/css/', 
            rename: function(dest, src) {
              return dest + src.substring(0, src.indexOf('/')) + '.css';
            }
          },
          {
            expand: true, 
            cwd: 'src/modules/', 
            src: ['**/*.html'], 
            dest: 'dev/', 
            rename: function(dest, src) {
              return dest + src.substring(0, src.indexOf('/')) + '.html';
            }
          }
        ]
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-copy');

  // Default task(s).
  grunt.registerTask('default', ['copy']);
};

Solution 2 - Gruntjs

There's no need to use grunt-contrib-copy just for this any more, you can now take advantage of grunt.file.expandMapping which has options to just change the file extension, or to define a function that returns the output filename.

Here's an example of a files object in a jade task for compiling .jade templates into .html files:

files: [{
    expand: true, 
    src: "**/*.jade", 
    dest: "<%= distDir %>", 
    cwd: "<%= assetsDir %>/jade", 
    rename: function(dest, matchedSrcPath, options) {
        // return the destination path and filename:
        return (dest + matchedSrcPath).replace('.jade', '.html');
    }
}]

It would have been easier to use the ext: '.html' option instead of the rename option in this case, but I'm using rename here so you can see how it works.

More info about the ext and rename (and other) options in the grunt.file docs. Some more examples here and here.

Solution 3 - Gruntjs

You could simply use the options: expand : true, flatten: true

No need for custom rename callbacks.

Solution 4 - Gruntjs

Not exactly an answer to your question but i made it here looking for relative dest folders with grunt so... Here is how i solved it

...
base: {
  files:
  [
    {
      expand: true,
      cwd: 'app/design/frontend/',
      src: ['**/Magento_Sales/email-src/*.html'],
      dest: '../../Magento_Sales/email/',
      rename: function(dest, src, expand) {
        src = path.parse(src)
        return path.join(expand.cwd, src.dir, dest, src.base);
      }
    },
  ],
}
...

This little bit path.join(expand.cwd, src.dir, dest, src.base); just creating the path i need.

expand.cwd = app/design/frontend/

src.dir = <DYNAMIC_FOLDERS>/Magento_Sales/email-src/

dest = ../../Magento_Sales/email/

src.base = <FILE>.html

and all together it = app/design/frontend/<COMPANY>/<MAIN-THEME>/Magento_Sales/email/<FILE>.html

and in my situation it will now compile my html emails in relative destination folders

Solution 5 - Gruntjs

if u want to rename .coffee files to .js or similar then just adapt it ;)

sudo npm install grunt-contrib-copy

copy: {
  rename: {
    files: [{
     expand: true,
     dot: true,
     cwd: './app/scripts',
     dest: './app/scripts/',
     src: [
       '**/*.coffee'
     ],
     rename: function(dest, src) {
       console.log(dest + src);
       return dest + src.replace('.coffee','.js');
     }
   }]
  }
},

Solution 6 - Gruntjs

I wanted to save the file in a min folder created at the root where we found the file so it's more easy to moove the plugin folder into another project :

-- Plugins/    
     -- bootstrap/                
            -- bootstrap.js
            -- bootstrap.css
            -- min/
                 -- bootstrap.min.js
                 -- bootstrap.min.css

I post the solution I found thanks to this page maybe it can help others :

uglify: {
    options: {
        mangle: true
    },
    dynamic_mappings: {
        // Grunt will search for "**/*.js" under "Plugins/" when the "uglify" task
        // runs and build the appropriate src-dest file mappings then, so you
        // don't need to update the Gruntfile when files are added or removed.
        files: [
            {
                expand: true,     // Enable dynamic expansion.
                cwd: 'Plugins/',  // Src matches are relative to this path.
                src: ['**/*.js', '!**/*.min.js'], // Pattern to match (ignore .min files)
                dest: '/min/',     // Destination path prefix.
                ext: '.min.js',   // Dest filepaths will have this extension.
                extDot: 'last',   // Extensions in filenames begin after the first dot
                rename: function( dest, matchedPath, options ) {

                    Pathname = matchedPath.substring( 0, matchedPath.indexOf('/') );
                    Filename = matchedPath.replace( Pathname, '' );

                    //Return "Plugins / plugin_folder / min / file_name.min.js"
                    return options.cwd + Pathname + dest + Filename;
                }
            },
        ],
    },
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkeirogView Question on Stackoverflow
Solution 1 - GruntjsGloopyView Answer on Stackoverflow
Solution 2 - GruntjstobekView Answer on Stackoverflow
Solution 3 - GruntjsPaul0515View Answer on Stackoverflow
Solution 4 - GruntjsJames HarringtonView Answer on Stackoverflow
Solution 5 - GruntjsKlaus ZahiragicView Answer on Stackoverflow
Solution 6 - GruntjsRomain GALVEZView Answer on Stackoverflow