Posted on

What I Learned About Grunt Compress

Grunt Configuring Tasks Banner

I learned a few interesting things while setting up my WordPress Development Kit project with some new Grunt tasks.   These are my findings while getting some custom build tasks working with the Grunt compress plugin (grunt-contrib-compress).

You can setup the default compress mode to be zip by using the main.options attribute in the JSON config parameters.

You can tell Compress where to put the final zip file and what to name it using dynamic elements, such as the plugin slug as the base file name by using an anonymous function in the archive property.

Use the files property to set an array of files to be created.   In each element of the array you specify attributes such as what files are to go INTO the zip file, where they are stored in the zip file, whether or not to use relative paths and more.    This is where things were a bit confusing for me so I’ll expand on that in a moment.

Here is my starting Grunt Compress settings that I am working with.  I will explain what this does below:

    compress: {
        main: {
            options: {
                mode: 'zip',
                archive: function() {
                    return slug + '.zip';
                }
            },
            files: [
                {
                    expand: true,
                    cwd: '/var/www/wpslp/wp-content/plugins/',
                    src: ['*'],
                    dest: 'public/',
                }
            ]
        },
    },

Here is a snippet of code that goes with the above configuration to do something.

  /**
   * makezip
   *
   * Build the zip file for the plugin, avoiding the assets directory, .git folders, and .neon files.
   */
  grunt.registerTask('makezip', 'Make a zip file for the plugin.', function(slug){
      grunt.log.writeln('Placeholder for makezip.  Slug name is ' + slug);
      global.slug = slug;
      grunt.task.run('compress');
  });

What Is In The Above Configuration

One of the first important things to note is that Grunt has a fairly robust built-in file manager. This file manager is available to all tasks and allows task files to use a default set of file rules such as the cwd, expand, src, and dest properties you see in the configuration section above. The Files section of the Grunt Configuring Tasks page will provide more insight beyond what I describe below.

archive

In the example above this is an anonymous function. The global variable “slug” is set in the makezip task and this is used to create the final zip file name. In my case it will be the WP Plugin Slug and .zip such as store-locator-le.zip for my Store Locator Plus plugin.

files.expand

The expand property tells the Grunt file processor to do dynamic source-and-destination processing.

files.cwd

Instructs the current processor, Compress in this case, to strip out the fully qualified path and make all file names in the processing loop relative to the parameter in the cwd command.  In my case it will make all files relative to my WordPress plugin root directory /var/www/wpslp/wp-content/plugins/.

file.src

This tells Compress which files are to be included in this round of processing.  For Compress it is the files that will be included in the .zip file distribution.   It uses the rules of something called minimatch as a file pattern matching system.  minimatch will grab as FEW files as possible so the ‘*’ rule here works different that typical operating-system wildcard file listings.   It will ONLY match the files in the exact directory specified.    In my case only the FILES (no directories or subdirectories) that are in my wp-content/plugins directory which in my case is only grabbing my legacy publisher scripts I put in the WP plugins directory on my dev box (blah, what a bad design).    I will explain how I fix this later.

file.dest

This one kind of threw me.   You can see I put public/ in as my destination.   I THOUGHT it would put the resulting .zip file in a folder named public under my current Grunt working directory with a <slug>.zip file in it.   WRONG.   What this does is tells compress where inside the resulting zip file you want the files it “gathers” with the file.src pattern noted above.

In the setup above it created a file in the ROOT grunt directory named store-locator-le.zip.   Inside that zip file is a folder named “public” in which all the contents of my WP Plugin directory (base files only) reside.  NOT what I wanted!

My Grunt Compress Mess
My Grunt Compress Mess

Fixing The Initial Grunt Problems

The first thing to fix is getting the .zip file to go to the ./wp-dev-kit/public folder where I will fetch it with other tasks for publication to the WordPress public plugin directory or to my server for private access for premium add-on packs.   There are two items to fix: files.dest and the archive path.

Removing the dest: property from my files section solved the first issue.   Now the files that match the src specification will go into the top-level of the .zip that is created.

Adding ../public/ to the start of my anonymous archive function will store the files in my public folder which resides next to the running Grunt tasks folder.

First two relatively minor issues are fixed, but there are deeper issues to resolve, specifically getting the files in the specified plugin directory and then adding some methods to ignore the files I don’t want part of the kit.

    compress: {
        main: {
            options: {
                mode: 'zip',
                archive: function() {
                    return '../public/' + slug + '.zip';
                }
            },
            files: [
                {
                    expand: true,
                    cwd: '/var/www/wpslp/wp-content/plugins/',
                    src: ['*'],
                }
            ]
        },
    },

Step 2 – Only Get The Specified Plugin

Getting the specified plugin directory wasn’t difficult, but it did involve a bit of Google and learning about named variable configuration in Grunt and how to get them into my “variable space” so I can use the <%= varname %> syntax in my Compress settings.    Luckily Chris Wren wrote a nice Grunt related article for newbs such as myself.

First step, add the variable declaration at the top of the Gruntfile, right above grunt.initConfig and inside the module.exports.

Grunt currentPlugin "global"
Grunt currentPlugin “global”

With that in place I can now tell the Compress plugin to make all file processing relative the plugin slug directory and use the same variable to set my zip file base name:

    compress: {
        main: {
            options: {
                mode: 'zip',
                archive: function() {
                    return '../public/' + currentPlugin.slug + '.zip';
                }
            },
            files: [
                {
                    expand: true,
                    cwd: '/var/www/wpslp/wp-content/plugins/<%= currentPlugin.slug %>',
                    src: ['**'],
                }
            ]
        },
    },

Inside my makezip tasks I now set the currentPlugin variable properties as opposed to a generic global variable, which is what I really wanted to do in the first place:

  /**
   * makezip
   *
   * Build the zip file for the plugin, avoiding the assets directory, .git folders, and .neon files.
   */
  grunt.registerTask('makezip', 'Make a zip file for the plugin.', function(slug){
      grunt.log.writeln('Placeholder for makezip.  Slug name is ' + slug);
      currentPlugin.slug = slug;
      grunt.task.run('compress');
  });

Now I am only getting the files for the specified plugin and not the WordPress plugin directory root files.   While I’m in there I also chanced the src parameter to ** versus *.   ** will grab all files in the current directory and any sub-directories that are part of my plugin.

Excluding Folders and Files

The last step will be to filter out those files I don’t want to include per my “no assets directory”, “no .git”, “no nbproject” and no “apigen.neon” files or folders.   If you follow my WordPress work flow posts you’ll know that this is my development environment and I prefer to work “inline” with the plugin directory and clear out the “cruft” of development files in the production cycle.

Thankfully the Grunt file processor makes excluding files a simple task.   I extend the src property with some “do not include” settings like so:

            files: [
                {
                    expand: true,
                    cwd: '/var/www/wpslp/wp-content/plugins/<%= currentPlugin.slug %>',
                    src: [
                        '**',
                        '!**.neon',
                        '!**.md',
                        '!assets/**',
                        '!nbproject/**'
                    ],
                }
            ]

That source specification will get all files in the main and sub-directories of my plugin EXCEPT for anything ending in .neon, .md or anything in the assets or nbproject sub-directories. By default the file filter will ignore any files starting with a dot, such as my .git, .gitignore, and .gitmodules folders and files.

Sweet!

I’m already liking Grunt WAY more than writing Bash files!

Follow along with other blog posts about the WordPress Workflow and WordPress Development Kit.