1

I'm trying to set up a grunt task that outputs a minified css file and changes the file name with a timestamp.

My Gruntfile looks like this:

module.exports = function (grunt) {

  //project configurations
  grunt.initConfig({

    cssmin: {
      target: {
        src: ["css/aw2018.css", ],
        dest: "dist/aw2018.min.css"
      }
    }

    replace: {
      foo: {
        options: {
          variables: {
            'timestamp': '<%= new Date().getTime() %>'
          },
          force: true
        },
        files: [{
          expand: true,
          cwd: 'css/',
          src: ['*.css/*.js'],
          dest: 'dist/',
          ext: '.<%= new Date().getTime() %>.js'
        }]
      }
    }

  });

  //load cssmin plugin
  grunt.loadNpmTasks('grunt-contrib-cssmin');

  //create default task
  grunt.registerTask("default", ["cssmin"]);
  grunt.registerTask('default', 'replace');

};

But I get an error of Loading "Gruntfile.js" tasks...ERROR

SyntaxError: Unexpected identifier Warning: Task "default" not found. Use --force to continue.

EDIT:

This is what I'm ultimately trying to achieve:

  1. Minify a css file
  2. Add a timestamp to the end of the file name.

I would like to have it work for any css file in a folder but keep them separate. For instance, lets say I have aw2018.css and aw2017.css. I would like both of them to run through the task and then be output to their own individual minified css file with the timestamp of YYYY-MM-DD-HH-MM-SS at the end of the filename.

RobC
  • 22,977
  • 20
  • 73
  • 80
bauhau5
  • 15
  • 5
  • If you're trying to do this using [grunt-replace](https://www.npmjs.com/package/grunt-replace) then it's the wrong tool. That plugin is for replacing text strings in the _contents_ of files and not for filenames. The [`timestamp`](https://www.npmjs.com/package/grunt-replace#timestamp) option for that plugin is for preserving [atime](https://nodejs.org/api/fs.html#fs_stats_atime) and [mtime](https://nodejs.org/api/fs.html#fs_stats_mtime) when copying files - it has nothing to do with appending a timestamp to a filename. Try to describe in your question what you're wanting to achieve. – RobC May 05 '18 at 16:52
  • I think there is a basic syntax error, a comma missing after defining the `cssmin` task. SO without that comma, `grunt.initConfig` will fail. – muecas May 05 '18 at 17:37

1 Answers1

0

This can be achieved by utilizing grunt's rename function when building the files object dynamically, instead of using another task.

The documentation describes grunts rename function as follows:

rename Embeds a customized function, which returns a string containing the new destination and filename. This function is called for each matched src file (after extension renaming and flattening).

Inside the body of the rename function is where you add your custom logic to append a timestamp to each filename.

The following Gruntfile.js configuration shows how to achieve this:

Gruntfile.js

module.exports = function (grunt) {

  var path = require('path'); // Load nodes built-in `path` module.

  // Obtain local timestamp formatted as: YYYY-MM-DD-HH-MM-SS
  var tzOffset = (new Date()).getTimezoneOffset() * 60000;
  var timeStamp = (new Date(Date.now() - tzOffset)).toISOString().slice(0, -1)
      .replace(/\.[\w\W]+?$/, '') // Delete from dot to end.
      .replace(/\:|\s|T/g, '-');  // Replace colons, spaces, and T with hyphen.

  grunt.initConfig({
    cssmin: {
      timestamp: {
        files: [{
          expand: true,
          cwd: 'css/',
          src: ['aw2017.css', 'aw2018.css'],
          dest: 'dist/',

          /**
           * Grunt rename function generates new destination filepath,
           # adds timestamp, and new min.css extension to the file name.
           # (https://gruntjs.com/configuring-tasks#the-rename-property)
           #
           * @param {String} dest - The path to the desination directory.
           * @param {String} src - The path to the source directory.
           * @returns {String} New dest path with time-stamped filename.
           */
          rename: function(dest, src) {
            var fileExt = path.extname(src),
              fileName = path.basename(src, fileExt),
              dirName = path.dirname(src),
              newFileExt = ['.min', fileExt].join(''),
              newFileName = [fileName, '-', timeStamp, newFileExt].join(''),
              newDestPath = path.join(dest, dirName, newFileName);

            return newDestPath;
          }
        }]
      }
    }
  });

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

  grunt.registerTask('default', ['cssmin:timestamp']);
};

Additional info:

  1. Firstly, in the Gruntfile.js above, we load the nodejs built-in path module via the line reading.

    var path = require('path');
    

    This module is used later in the rename function to help create the new time-stamped filename, and ascertain the destination filepath to be return'ed:

  2. We then create a local timestamp formatted as YYYY-MM-DD-HH-MM-SS via the lines reading:

    var tzOffset = (new Date()).getTimezoneOffset() * 60000;
    var timeStamp = (new Date(Date.now() - tzOffset)).toISOString().slice(0, -1)
        .replace(/\.[\w\W]+?$/, '') // Delete from dot to end.
        .replace(/\:|\s|T/g, '-');  // Replace colons, spaces, and T with hyphen.
    

    Note: We assign the generated timestamp to the timeStamp variable outside of any grunt task(s) to ensure all resultant filenames get the same timestamp.

    The date/time format will be based on your local timezone and not UTC (Coordinated Universal Time).

  3. We then reconfigure your cssmin task to build the files object dynamically instead of utilizing the compact format. By configuring the task this way we get access to the rename function.


Further usage and modifications to the current config:

  1. The Gruntfile.js configuration provided above takes a two source CSS files, named aw2017.css and aw2018.css from the following directory structure:

    .
    └── css
        ├── aw2017.css
        └── aw2018.css
    

    After running the grunt command via your CLI, it outputs both minified (time-stamped) .css files to the new dist directory. Resulting as this:

    .
    ├── css
    │   ├── aw2017.css
    │   └── aw2018.css
    └── dist
        ├── aw2017-2018-05-09-08-35-57.min.css
        └── aw2018-2018-05-09-08-35-57.min.css
    
  2. However, if you want to also include the source css folder in the dist directory like this:

    .
    ├── css
    │   ├── aw2017.css
    │   └── aw2018.css
    └── dist
        └── css
            ├── aw2017-2018-05-09-08-35-57.min.css
            └── aw2018-2018-05-09-08-35-57.min.css
    

    then you need to change the values of the cwd and src properties in the cssmin task to this:

    // ...
    cwd: '.',
    src: ['css/aw2017.css', 'css/aw2018.css'],
    // ...
    

Minifying and time-stamping multiple .css files using a glob pattern

Currently, in your question, you seem to only want to minify two .css file, namely aw2017.css and aw2018.css.

However, if you wanted to minify (and time-stamp) many .css files found in the css directory, however many levels deep, you can utilize a globbing pattern. For example, lets say your source css directory looks like this:

.
└── css
   ├── a.css
   ├── b.css
   ├── foo
   │   ├── bar
   │   │   └── c.css
   │   └── d.css
   └── quux
       └── e.css

...and if you change the values of the cwd and src properties in your cssmin task to this:

// ...
cwd: '.',
src: ['css/**/*.css'],
// ...

Your resultant output will be something like this:

.
├── css
│   └── ...
└── dist
    └── css
        ├── a-2018-05-09-08-35-57.min.css
        ├── b-2018-05-09-08-35-57.min.css
        ├── foo
        │   ├── bar
        │   │   └── c-2018-05-09-08-35-57.min.css
        │   └── d-2018-05-09-08-35-57.min.css
        └── quux
            └── e-2018-05-09-08-35-57.min.css
RobC
  • 22,977
  • 20
  • 73
  • 80