Let’s Give Grunt Tasks the Marie Kondo Organization Treatment

We live in an era of webpack and npm scripts. Good or bad, they took the lead for bundling and task running, along with bits of Rollup, JSPM and Gulp. But let's face it. Some of your older projects are still using good ol' Grunt. While it no longer glimmers as brightly, it does the job well so there's little reason to touch it.

Though, from time to time, you wonder if there’s a way to make those projects better, right? Then start from "Organizing Your Grunt Tasks" article and come back. I'll wait. That’ll set the stage for this post and then we'll take it further together to create a solid organization of Grunt tasks.

Automatic Speed Daemon task loading

It’s no fun writing loading declarations for each task, like this:

grunt.loadNpmTasks('grunt-contrib-clean')
grunt.loadNpmTasks('grunt-contrib-watch')
grunt.loadNpmTasks('grunt-csso')
grunt.loadNpmTasks('grunt-postcss')
grunt.loadNpmTasks('grunt-sass')
grunt.loadNpmTasks('grunt-uncss')

grunt.initConfig({})

It's common to use load-grunt-tasks to load all tasks automatically instead. But what if I tell you there is a faster way?

Try jit-grunt! Similar to load-grunt-tasks, but even faster than native grunt.loadNpmTasks.

The difference can be striking, especially in projects with large codebases.

Without jit-grunt

loading tasks     5.7s  ▇▇▇▇▇▇▇▇ 84%
assemble:compile  1.1s  ▇▇ 16%
Total 6.8s

With jit-grunt

loading tasks     111ms  ▇ 8%
loading assemble  221ms  ▇▇ 16%
assemble:compile   1.1s  ▇▇▇▇▇▇▇▇ 77%
Total 1.4s

1.4 seconds doesn't really make it a Speed Daemon... so I kinda lied. But still, it's six times faster than the traditional way! If you're curious how that's possible, read about the original issue which led to the creation of jit-grunt.

How is jit-grunt used? First, install:

npm install jit-grunt --save

Then replace all tasks load statements with a single line:

module.exports = function (grunt) {
  // Intead of this:
  // grunt.loadNpmTasks('grunt-contrib-clean')
  // grunt.loadNpmTasks('grunt-contrib-watch')
  // grunt.loadNpmTasks('grunt-csso')
  // grunt.loadNpmTasks('grunt-postcss')
  // grunt.loadNpmTasks('grunt-sass')
  // grunt.loadNpmTasks('grunt-uncss')

  // Or instead of this, if you've used `load-grunt-tasks`
  // require('load-grunt-tasks')(grunt, {
  //   scope: ['devDependencies', 'dependencies'] 
  // })

  // Use this:
  require('jit-grunt')(grunt)

  grunt.initConfig({})
}

Done!

Better configs loading

In the last example, we told Grunt how to load tasks itself, but we didn't quite finish the job. As “Organizing Your Grunt Tasks" suggests, one of the most useful things we're trying to do here is split up a monolithic Gruntfile into smaller standalone files.

If you read the mentioned article, you'll know it's better to move all task configuration into external files. So, instead of a large single gruntfile.js file:

module.exports = function (grunt) {
  require('jit-grunt')(grunt)

  grunt.initConfig({
    clean: {/* task configuration goes here */},
    watch: {/* task configuration goes here */},
    csso: {/* task configuration goes here */},
    postcss: {/* task configuration goes here */},
    sass: {/* task configuration goes here */},
    uncss: {/* task configuration goes here */}
  })
}

We want this:

tasks
  ├─ postcss.js
  ├─ concat.js
  ├─ cssmin.js
  ├─ jshint.js
  ├─ jsvalidate.js
  ├─ uglify.js
  ├─ watch.js
  └─ sass.js
gruntfile.js

But that will force us to load each external configuration into gruntfile.js manually, and that takes time! We need a way to load our configuration files automatically.

We’ll use load-grunt-configs for that purpose. It takes a path, grabs all of the configuration files there and gives us a merged config object which we use for Grunt config initialization.

Here how it works:

module.exports = function (grunt) {
  require('jit-grunt')(grunt)

  const configs = require('load-grunt-configs')(grunt, {
    config: { src: 'tasks/.js' }
  })

  grunt.initConfig(configs)
  grunt.registerTask('default', ['cssmin'])
}

Grunt can do the same thing natively! Take a look at grunt.task.loadTasks (or it's alias grunt.loadTasks).

Use it like this:

module.exports = function (grunt) {
  require('jit-grunt')(grunt)

  grunt.initConfig({})

  // Load all your external configs.
  // It's important to use it _after_ Grunt config has been initialized,
  // otherwise it will have nothing to work with.
  grunt.loadTasks('tasks')

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

Grunt will automatically load all js or coffee config files from the specified directory. Nice and clean! But, if you'll try to use it, you'll notice it does nothing. How is that? We still need to do one more thing.

Let's look into our gruntfile.js code once again, this time without the comments:

module.exports = function (grunt) {
  require('jit-grunt')(grunt)

  grunt.initConfig({})

  grunt.loadTasks('tasks')

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

Notice that grunt.loadTasks loads files from tasks directory, but never assigns it to our actual Grunt config.

Compare it with a way load-grunt-configs works:

module.exports = function (grunt) {
  require('jit-grunt')(grunt)

  // 1. Load configs
  const configs = require('load-grunt-configs')(grunt, {
    config: { src: 'tasks/.js' }
  })

  // 2. Assign configs
  grunt.initConfig(configs)

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

We initialize our Grunt config before actually loadings tasks configuration. If you are getting a strong feeling that it will make us end up with empty Grunt config — you're totally right. You see, on contrary to the load-grunt-configs, grunt.loadTasks just imports files into gruntfile.js. It does nothing more.

Woah! So, how do we make use of it? Let's explore!

First, create a file inside directory tasks named test.js

module.exports = function () {
  console.log("Hi! I'm an external task and I'm taking precious space in your console!")
}

Let's run Grunt now:

$ grunt

We'll see printed to the console:

> Hi! I'm an external task and I'm taking precious space in your console!

So, upon importing grunt.loadTasks, every function is executed as it loads files. That's nice, but what's the use of it for us? We still can't do a thing we actually want — to configure our tasks.

Hold my beer because there is a way to command Grunt from within external configuration files! Using grunt.loadTasks upon importing provides current Grunt instance as a function first argument and also binds it to this.

So, we can update our Gruntfile:

module.exports = function (grunt) {
  require('jit-grunt')(grunt)

  grunt.initConfig({
    // Add some value to work with
    testingValue: 123
  })

  grunt.loadTasks('tasks')

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

...and change the external config file tasks/test.js:

// Add `grunt` as first function argument
module.exports = function (grunt) {
  // Now, use Grunt methods on `grunt` instance
  grunt.log.error('I am a Grunt error!')

  // Or use them on `this` which does the same
  this.log.error('I am a Grunt error too, from the same instance, but from `this`!')

  const config = grunt.config.get()

  grunt.log.ok('And here goes current config:')
  grunt.log.ok(config)
}

Now, let’s run Grunt again:

$ grunt

And what we'll get:

> I am Grunt error!
> I am Grunt error too, from the same instance, but from `this`!
> And here goes current config:
> {
    testingValue: 123
  }

See how we accessed native Grunt methods from an external file and were even able to retrieve the current Grunt config? Are you thinking about that too? Yeah, the full power of Grunt is already there, right at our fingertips in each file!

If you are wondering why methods inside external files can affect our main Grunt instance, it is because of a referencing. grunt.loadTasks passing this and grunt to our current Grunt instance — not a copy of it. By invoking methods on that reference, we're able to read and mutate our main Grunt configuration file.

Now, we need to actually configure something! One last thing...

This time, let’s make configuration loading work for real

Alright, we’ve come a long way. Our tasks are loaded automatically and faster. We learned how to load external configs with native Grunt method. But our task configs are still not quite there because they do not end up in Grunt config.

But we’re almost there! We learned that we can use any Grunt instance methods in imported files using grunt.loadTasks. They are available on grunt and this instances.

Among many other methods, there is a precious grunt.config method. It allows us to set a value in an existing Grunt config. The main one, which we initialized in our Gruntfile... remember that one?

What's important is the way we can define tasks configurations. Exactly what we need!

// tasks/test.js

module.exports = function (grunt) {
  grunt.config('csso', {
    build: {
      files: { 'style.css': 'styles.css' }
    }
  })

  // same as
  // this.config('csso', {
  //   build: {
  //     files: { 'style.css': 'styles.css' }
  //   }
  // })
}

Now let's update Gruntfile to log the current config. We need to see what we did, after all:

module.exports = function (grunt) {
  require('jit-grunt')(grunt)

  grunt.initConfig({
    testingValue: 123
  })

  grunt.loadTasks('tasks')

  // Log our current config
  console.log(grunt.config())

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

Run Grunt:

$ grunt

...and here’s what we see:

> {
    testingValue: 123,
    csso: {
      build: {
        files: {
          'style.css': 'styles.css'
        }
      }
    }
  }

grunt.config sets csso value when imported, so the CSSO task is now configured and ready to run when Grunt is invoked. Perfect.

Note that if you used load-grunt-configs previously, you had a code like that, where each file exports a configuration object:

// tasks/grunt-csso.js

module.exports = {
  target: {
    files: { 'style.css': 'styles.css' }
  }
}

That needs to be changed to a function, as described above:

// tasks/grunt-csso.js

module.exports = function (grunt) {
  grunt.config('csso', {
    build: {
      files: { 'style.css': 'styles.css' }
    }
  })
}

OK, one more one more last thing... this time for real!

Taking external config files to the next level

We learned a lot. Load tasks, load external configuration files, define a configuration with Grunt methods... that's fine, but where's the profit?

Hold my beer again!

By this time, we’ve externalized all our task configuration files. So, the our project directory looks something like this:

tasks
  ├─ grunt-browser-sync.js  
  ├─ grunt-cache-bust.js
  ├─ grunt-contrib-clean.js 
  ├─ grunt-contrib-copy.js  
  ├─ grunt-contrib-htmlmin.js   
  ├─ grunt-contrib-uglify.js
  ├─ grunt-contrib-watch.js 
  ├─ grunt-csso.js  
  ├─ grunt-nunjucks-2-html.js   
  ├─ grunt-postcss.js   
  ├─ grunt-processhtml.js
  ├─ grunt-responsive-image.js  
  ├─ grunt-sass.js  
  ├─ grunt-shell.js 
  ├─ grunt-sitemap-xml.js   
  ├─ grunt-size-report.js   
  ├─ grunt-spritesmith-map.mustache 
  ├─ grunt-spritesmith.js   
  ├─ grunt-standard.js  
  ├─ grunt-stylelint.js 
  ├─ grunt-tinypng.js   
  ├─ grunt-uncss.js 
  └─ grunt-webfont.js
gruntfile.js

That keeps Gruntfile relatively small and things seem to be well organized. But do you get a clear picture of the project just by glancing into this cold and lifeless list of tasks? What actually do they do? What's the flow?

Can you tell that Sass files are going through grunt-sass, then grunt-postcss:autoprefixer, then grunt-uncss, and finally through grunt-csso? Is it obvious that the clean task is cleaning the CSS or that grunt-spritesmith is generating a Sass file which should be picked up too, as grunt-watch watches over changes?

Seems like things are all over the place. We may have gone too far with externalization!

So, finally... now what if tell you that there’s yet a better way would be group configs... based on features? Instead of a not-so-helpful list of tasks, we'll get a sensible list of features. How about that?

tasks
  ├─ data.js 
  ├─ fonts.js 
  ├─ icons.js 
  ├─ images.js 
  ├─ misc.js 
  ├─ scripts.js 
  ├─ sprites.js 
  ├─ styles.js 
  └─ templates.js
gruntfile.js

That tells me a story! But how could we do that?

We already learned about grunt.config. And believe it or not, you can use it multiple times in a single external file to configure multiple tasks at once! Let’s see how it works:

// tasks/styles.js

module.exports = function (grunt) {
  // Configuring Sass task
  grunt.config('sass', {
    build: {/* options */}
  })
  
  // Configuring PostCSS task
  grunt.config('postcss', {
    autoprefix: {/* options */}
  })
}

One file, multiple configurations. Quite flexible! But there is an issue we missed.

How should we deal with tasks such as grunt-contrib-watch? Its configuration is a whole monolithic thing with definitions for each task that are unable to be split.

// tasks/grunt-contrib-watch.js

module.exports = function (grunt) {
  grunt.config('watch', {
    sprites: {/* options */},
    styles: {/* options */},
    templates: {/* options */}
  })
}

We can't simply use grunt.config to set watch configuration in each file, as it will override the same watch configuration in already imported files. And leaving it in a standalone file sounds like a bad option too — after all, we want to keep all related things close.

Fret not! grunt.config.merge to the rescue!

While grunt.config explicitly sets and overrides any existing values in Grunt config, grunt.config.merge recursively merges values with existing values in other Grunt config files giving us a single Grunt config. A simple, but effective way to keep related things together.

An example:

// tasks/styles.js

module.exports = function (grunt) {
  grunt.config.merge({
    watch: {
      templates: {/* options */}
    }
  })
}
// tasks/templates.js

module.exports = function (grunt) {
  grunt.config.merge({
    watch: {
      styles: {/* options */}
    }
  })
}

This will produce a single Grunt config:

{
  watch: {
    styles: {/* options */},
    templates: {/* options */}
  }
}

Just what we needed! Let's apply this to the real issue — our styles-related configuration files. Replace our three external task files:

tasks
  ├─ grunt-sass.js
  ├─ grunt-postcss.js   
  └─ grunt-contrib-watch.js

...with a single tasks/styles.js file that combines them all:

module.exports = function (grunt) {
  grunt.config('sass', {
    build: {
      files: [
        {
          expand: true,
          cwd: 'source/styles',
          src: '{,**/}*.scss',
          dest: 'build/assets/styles',
          ext: '.compiled.css'
        }
      ]
    }
  })

  grunt.config('postcss', {
    autoprefix: {
      files: [
        {
          expand: true,
          cwd: 'build/assets/styles',
          src: '{,**/}*.compiled.css',
          dest: 'build/assets/styles',
          ext: '.prefixed.css'
        }
      ]
    }
  })

  // Note that we need to use `grunt.config.merge` here!
  grunt.config.merge({
    watch: {
      styles: {
        files: ['source/styles/{,**/}*.scss'],
        tasks: ['sass', 'postcss:autoprefix']
      }
    }
  })
}

Now it's much easier to tell just by glancing into tasks/styles.js that styles have three related tasks. I'm sure you can imagine extending this concept to other grouped tasks, like all the things you might want to do with scripts, images, or anything else. That gives us a reasonable configuration organization. Finding things will be much easier, trust me.

And that's it! The whole point of what we learned.

That’s a wrap

Grunt is no longer the new darling it once was when it first hit the scene. But to date, it is a straightforward and reliable tool that does its job well. With proper handling, it gives even fewer reasons to swap it for something newer.

Let's recap what we can do to organize our tasks efficiently:

  1. Load tasks using jit-grunt instead of load-grunt-tasks. It's same but insanely faster.
  2. Move specific task configurations out from Gruntfile and into external config files to keep things organized.
  3. Use native grunt.task.loadTasks to load external config files. It's simple but powerful as it exposes all Grunt capabilities.
  4. Finally, think about a better way to organize your config files! Group them by feature or domain instead of the task itself. Use grunt.config.merge to split complex tasks like watch.

And, for sure, check Grunt documentation. It’s still worth a read after all these years.

If you’d like to see a real-world example, check out Kotsu, a Grunt-based starter kit and static website generator. You'll find even more tricks in there.

Got better ideas about how to organize Grunt configs even better? Please share them in the comments!

The post Let’s Give Grunt Tasks the Marie Kondo Organization Treatment appeared first on CSS-Tricks.

What Is the Right Size for a User Story?

There is no one-size-fits-all user story.

A question that comes up again and again from teams is “What is the right size for a User Story?” -- a day? a week? three weeks? 1 point? 5 points? 10 points? 37? 100? 5 words? 20 words?

Short answer: There isn’t a universal right size for a User Story; it depends on your team, their skills, and their level of domain knowledge.

Prioritizing

You're faced with a lot of decisions in everyday work. There are multiple tasks calling for your focus, and you can burn daylight or even burn out trying to decide what comes first. There's a phenomenon called decision fatigue. There have been many studies that you can make poor choices when you're not able to decide what is most important that can lead to things like impaired judgement and even purchase decisions.

So how can you figure out what's most important to work on first, or even what tasks to work on at all? In this post, we'll explore how to sift through the inevitable weight of our to-do lists so that we can be efficient and clear with our direction.

If you feel like your to-do list is ruling you instead of you ruling it, read on.

How to invest your time

If you’re going to think smarter (and not harder) about how you prioritize tasks, you have to invest a little time away from your to-do list. Here’s the thing: what you work on informs your values. You may think it’s no big thing to work overtime for a little while, but something will be sacrificed with this decision. If you’re a parent, you might spend less time with your kids. Maybe it's less time hanging out with friends. Maybe you give up sleep or eating well.

Similarly, if you choose not to work much or effectively, you’re deciding that your values lie outside your career, which is ok too. No judgement here. But where you invest your time is not just about what you value, but also what you don’t value.

photo of computer and notepad
Photo by Jessica Lewis, Unsplash

Every quarter or so, I break down all the tasks I’ve committed doing. I write them all down, and I create four quadrants containing all of things I care about. This could be: helps the community, helps one-to-one relationships (which can include coworkers, friends, and family), makes money, and things I find personally fulfilling. This is just an example and your quadrants may be different, of course.

I then take all the things I have going and place them in the quadrants. I see how many of the boxes each one takes up. Some endeavors are counted in a quadrant twice to add weight to it.

Here are a couple allotments for me:

  • Writing things on CSS-Tricks (like this very article) is one of the rarest instances where all boxes are checked
  • Mentoring people checks: building one-to-one relationships, and something that I find personally fulfilling

Anything that fills just one box has to be reconsidered. Anything that fills nothing is usually on the chopping block. This activity allows me to see where I really want to invest my energy. It’s actually illuminating because sometimes I don’t really know what I value and how to prioritize tasks around those things until I do the exercise.

From here, the rest of the prioritization gets a little easier. I now have a better idea where my efforts are really paying off. I also know when I’m wasting my own time. This distinction helps me prevent decision fatigue quite a bit because I have a guiding light to help make choices.

What do you have to do?

In order to figure out a plan for yourself, the first thing you should be doing is gathering all of your tasks, large and small. Letting those things fester in our minds can make us feel burdened by what we have to do, so get it all out on proverbial paper. You can use notebooks, a homemade to-do list, applications like Notion, Evernote, or Clear. It's up to you.

I use several lists for a few reasons: I tend to retain information better if I write it down multiple in multiple places. For example, I need some things on mobile. I use Clear for those things, Notion on desktop, and a paper teacher planner (which I've shared before). I find that the repetition helps me solidify what’s important. That might not be right for you. That’s cool, do what works.

Coding priorities

You may work at a company that creates tickets for tasks. Some tickets are simple and you can knock them out quickly; some can be arduous. Whenever I have a larger coding task, I first break it down into a to-do list as comments in my code. It looks vaguely like this:

// get the request from the server
// give us an error if it failed
// do x thing with that request
// format the data like so

That way, I can fill in the blanks with each task. I usually try to split each one into different pull requests so I can keep myself organized and make it easier on my reviewers. When it’s all done, I’ll go back and prune the comments or replace them with comments that explain the why (not how).

Overall prioritization

There are some tasks that are pretty straightforward. You need to do the thing. It’s indisputable. Other people are counting on you or your future self will depend on it somehow. These are actually easier to prioritize.

First I break down large tasks into smaller pieces. That helps me put things in order:

  1. Things that are actively on fire or are time-sensitive
  2. Things that can be done quickly
  3. Things that need a scheduled block of time
  4. Things that I may get to further along

Part of the reason we do the small things first is that morale is important. I feel more incensed to get my other work done because being productive feels good — there's a small dopamine rush associated with every check. This is also why I put things I've already accomplished on my list. It may sound silly, but acknowledging accomplishments makes me more likely to keep going and pushes me through the more complicated tasks.

Some people, like Alice Goldfuss, have this down to a science:

Keep yourself motivated

Keeping yourself motivated is key. Sometimes this means keeping your own morale in mind. The good news is you know yourself pretty well. Here are some tricks that have served me well:

  • Put things on your to do list that you enjoy. You will start to associate your to do list with happiness. For instance, recently I put "read a dumb thriller fiction" on my to do list. It's ridiculous, but it works. It also helps me remember to do things outside of code all day.
  • Put things on your to do list that you've already done. When you mark them off you can get a sense of satisfaction that may entice you to gather more things you can check off. I think a lot of people do this. I definitely didn't come up with it.
  • Customize it a bit. Whether I use paper or digital means, I spend a minute or two customizing it and making it my own. It makes it more important to me, because I've invested in it, and I'm more likely to keep up with the contents.

Prioritizing based on energy levels

Chris Coyier mentioned a great blog post by Alex Sexton when I was pondering how I feel better suited for things like meetings on some days and heads-down coding on others.

I think this is brilliant! I’ve definitely had times in my career where I've suffered because of sporadic meetings with ill-defined amounts of time in between that were too small to be heads-down coding, and I would make up for it at night. That's not a recipe for success; it's a recipe for fatigue.

man's hand writing in a planner
Photo by Ilya Ilford, Unsplash

Lately, I’ve been working on grouping similar tasks. For example, meetings should all happen in succession because it’s easier for me to jump from one to another than it is having an hour in between. I’m also more keen to communicate with others on Monday, when I’m getting the lay of the land. Towards the end of the week, my energy is higher if I’m dedicated to coding, especially if I’ve allotted uninterrupted time.

Notice when your energy levels are high and when they wane. Notice when you’re more productive for social activities and when you’re better off working in isolation. The more you study yourself, the easier planning becomes.

Scheduling

Now that you have your priorities in order, go ahead and schedule your time. I like to use the weekly planner in Notion. I’ll take all the things I think I can get done in a week and break them down day-by-day, based on when it makes the most sense to do each task. When I finish a task, it’s dragged to the top. Anything that’s unchecked at the bottom by the end of the day moves over to the next. I also have general to-do lists, and task-based lists, but the weekly planner is my holy grail. Again, you don’t have to do it exactly like I do, find what works for you.

weekly status notion
I find some art to use as the cover for my weekly schedule so that I enjoy going to it. It's the little things.

Know yourself

Most of these systems work for me because I know my strengths and weaknesses and I play to them: I work more efficiently when things are broken down into steps. I work best when my work has the most meaning, and I remove the cruft. I work best when I work along with, not against, my energy levels, where possible.

There’s one more thing I haven't addressed yet: forgiveness. You’ll be more productive on some days (or hell, even years!) than you will be on others, and that's OK. Life is full of ebbs and flows.

It’s good to check in to see if your waning productivity is the result of simply moving a little slower, signals signs of depression, represents misalignment with personal goals (or the company where you work), or if you’re reacting to a toxic environment.

In all cases, forgiveness is in order. In some cases, you may need more than a prioritization plan. But truly, you’ll never be able to do everything, because no one is capable of that. People you see on social media might also only be showing their successes, and conveniently leaving out their failures and struggles. The fact is that we all get distraught, and sometimes mental illness isn’t something we want to announce over the megaphone of social media. Try not to compare yourself to others. Forgive yourself a bit. Work an amount that’s reasonable for you.


Hopefully, if you’re prioritizing well, the amount of time that you do have to work is structured in a way that actually relieves stress and allows you to accomplish the most you can, when you can. The goal of prioritizing is to bring into focus the work that you find fulfilling, and removes tasks that don’t. That's the sweet spot.

The post Prioritizing appeared first on CSS-Tricks.

Gradle Goodness: Only Show All Tasks in a Group [Snippet]

To get an overview of all Gradle tasks in our project, we need to run the tasks task. Since Gradle 5.1, we can use the --group option followed by a group name. Gradle will then show all tasks belonging to the group and not the other tasks in the project.

Suppose we have a Gradle Java project and want to show the tasks that belong to the build group: