17

I have not found a good way to grunt-uglify multiple script files spread over multiple folders while keeping the folder structure including the uglified files intact. The only reason I want to do this is to be able to increase the performance of the "legacy" part of the web page I'm working on.

I have found a way around this which I don't want to do, since it will take to much time, and that is to do it like in this answer (they specify each 'src' and 'dest' pair separately): How to configure Grunt to minify files separately

An example of what I want to achieve:

**Source directory (no uglify applied):**
src
 |- app1
    |- randomFile.js
    |- scripts
       |- file1.js
       |- file2.js
    |- libs
       |- file3.js
       |- file4.js
 |- app2
   |- scripts
       |- file1.js
       |- file2.js

**Destination directory (uglify applied, and the same file name):**
dist
 |- app1
    |- randomFile.js
    |- scripts
       |- file1.js
       |- file2.js
    |- libs
       |- file3.js
       |- file4.js
 |- app2
    |- scripts
       |- file1.js
       |- file2.js

Btw, I want to do the same for CSS files if possible.

Is this possible?

1
  • I would recommend source maps Commented May 25, 2015 at 21:29

5 Answers 5

24

The principle in Rafa Heringer's answer on the post you linked to looks promising, with a little twist:

uglify: {
    min: {
        files: grunt.file.expandMapping(['path/**/*.js', 'path2/**/*.js'], 'destination/', {
            rename: function(destBase, destPath) {
                return destBase+destPath.replace('.js', '.min.js');
            }
        })
    }
}

The only difference here is the double asterisk between the base path and the wildcard filename with its extension. That will go through all the subfolders and, hopefully, output each find it finds in its rightful folder.

The output would be:

path/test.js => destination/path/test.min.js
path/subpath1/abc.js => destination/path/subpath1/abc.min.js
path/subpath2/yey.js => destination/path/subpath2/yey.min.js
path2/foo.js => destination/path2/foo.min.js

When it comes to doing the same with CSS (using the grunt-contrib-cssmin plugin), the approach mentioned above would still work, but you would have to combine it with the relevant plugin configurations that must be in place to output minified CSS content the way you want.

PS: I haven't tried running it myself!

Sign up to request clarification or add additional context in comments.

2 Comments

Seems to work, thanks! Only problem I have left now is to make all legacy js files go through the uglifier :s
While this is great and all, the problem is that this will also minify already minified files, so if you already have .min.js files in those directories, it will create .min.min.js and so on everytime you run it. How to fix this?
15

Similar to the answer by Dennis Liger, but it stores minified files in the same folder:

    uglify: {
        all: {
            files: [{
                expand: true,
                cwd: 'path/to/js/',
                src: ['*.js', '!*.min.js'],
                dest: 'path/to/js/',
                ext: '.min.js'
            }]
        }
    }

Comments

7

Actually, you can use the following approach:

uglify: {
  all: {
    files: [{
      expand: true,
      cwd: 'js/',
      src: ['*.js', '**/*.js'],
      dest: 'js-min/',
      ext: '.min.js',
    }],
  },
}

Comments

2

The answer from Wallace is great, but if the files you are trying to minify don't exist before grunt starts (i.e., if you are depending on another task), it won't work because the map is generated before any task is run.

I came up with a solution for minifying generated files individually, using the Node.js package uglify-js instead of grunt-contrib-uglify.

  • Add uglify-js to your package.json file
  • Add one of the following example to your Grunfile (just replace "YOUR FILES HERE" by the appropriate glob(s) if you are using Example 1).
  • If you need to change the minified file destination or extension, use Example 2 instead. It uses grunt.file.recurse with a callback which provides you with the root directory, subdirectory and file name of each file (it is easier then to build a custom destination path). Replace "FOLDER" by the directory you want to scan, and build your own "CUSTOM PATH HERE".

Example 1: With grunt.file.expand

grunt.registerTask('uglifyFiles', 'Uglifies files', function () {
    var jsp = require("uglify-js").parser,
        pro = require("uglify-js").uglify,
        count = 0;

    grunt.file.expand(['YOUR FILES HERE']).forEach(function (abspath) {
        // Exclude already minified files (with extension .min.js)
        if (!abspath.match(/\.min\.js$/i)) {
            // Get Abstract Syntax Tree
            var ast = jsp.parse(grunt.file.read(abspath));
            // If mangling
            // ast = pro.ast_mangle(ast);
            // If squeezing
            ast = pro.ast_squeeze(ast);
            // Write new file
            grunt.file.write(abspath.replace(/\.js$/i, '.min.js'), pro.gen_code(ast));
            count += 1;
        }
    });

    grunt.log.oklns("Successfully uglified " + count + " files");
});

Example 2: With grunt.file.recurse

grunt.registerTask('uglifyFiles', 'Uglifies files', function () {
    var jsp = require("uglify-js").parser,
        pro = require("uglify-js").uglify,
        count = 0;

    grunt.file.recurse('FOLDER', function callback(abspath, rootdir, subdir, filename) {
        // Exclude already minified files (with extension .min.js)
        if (!abspath.match(/\.min\.js$/i)) {
            // Get Abstract Syntax Tree
            var ast = jsp.parse(grunt.file.read(abspath));
            // If mangling
            // ast = pro.ast_mangle(ast);
            // If squeezing
            ast = pro.ast_squeeze(ast);
            // Write new file, using abspath or rootdir, subdir and filename
            grunt.file.write('CUSTOM PATH HERE', pro.gen_code(ast));
            count += 1;
        }
    });

    grunt.log.oklns("Successfully uglified " + count + " files");
});

Comments

0

This solution is not working for me.

This is working example:

path: {
    build: {
       src: 'assets',
       js: 'js',
       css: 'css'
    },
    js: 'js',
    css: 'css'
},
uglify: {
    scripts: {
        expand: true,
        cwd: '<%= path.js %>/',
        src: [
            '**/*.js',
            '*.js',
            // Skip minified scripts
            '**/!*.min.js',
            '!*.min.js'
        ],
        dest: '<%= path.build.src %>/<%= path.build.js %>/',
        rename: function (destBase, destPath) {
            return destBase + destPath.replace('.js', '.min.js');
        }
    }
},
// Same options for CSS minify
cssmin: {
    styles: {
        expand: true,
        cwd: '<%= path.css %>/',
        src: [
            '**/*.css',
            '*.css',
            // Skip minified styles
            '**/!*.min.css',
            '!*.min.css'
        ],
        dest: '<%= path.build.src %>/<%= path.build.css %>/',
        rename: function (destBase, destPath) {
            return destBase + destPath.replace('.css', '.min.css');
        }
    }
},
// And watch it for changes
watch: {
    js: {
        files: [
            '<%= path.js %>/*.js',
            '<%= path.js %>/**/*.js'
        ],
        tasks: [
            'uglify:scripts'
        ],
        options: {
            livereload: true
        }
    },
    css: {
        files: [
            '<%= path.css %>/*.css',
            '<%= path.css %>/**/*.css'
        ],
        tasks: [
            'cssmin:styles'
        ],
        options: {
            livereload: true
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.