Finding unused assets and CSS

Similar to reducing build times by finding and removing unused packages from our package.json, we can use bash to find other assets that are not used anywhere in our codebase and unnecessarily increase the repo size.

We're going to use the find command to create a list of all the assets we want to iterate over and check if they are used in our codebase. Of course, this can be done for multiple formats and files, either separately or in the same list and iteration. To keep it simple here, we're going to create a temporary file with all images (png and jpg) and use the grep command to check for any occurrences in our code.

command line
#!/bin/bash
mkdir ./tmp
directory="./src"
find "$directory" -name *.jpg -exec basename \; > ./tmp/patterns.txt
find "$directory" -name *.png -exec basename \; > ./tmp/patterns.txt
for pattern in $(cat ./tmp/patterns.txt); do
grep -Rq $pattern "$directory" || echo $pattern;
done
rm -rf ./tmp
# optional

The real lookup happens inside our loop, which iterates over all the images we collected with find. We're giving grep the -Rq flags to make it recursive and quiet, so it doesn't return any errors to the console and then pipe the outcome to echo. The || pipe will only ever output the results of grep to echo when the left-hand statement throws an error, which in our case means grep didn't find the pattern in any of the files it was looking through. Which also means, the image wasn't used anywhere in our code.

Finding unused CSS selectors

Just like using a script to optimize our assets, it can be very beneficial to have a re-usable bash script that can help us find unused CSS in our projects and works the same across all projects. Let's take a look at how we can achieve this with the tools we've already learned about.

First, we need to get a list of all CSS selectors that have been declared across our codebase. We can use grep to go through all *.css and *.scss files, looking for the pattern ^\s*\., which equals CSS classes — strings starting with a dot. We'll first write them into a temporary file, before using a few sed operations to clean out the string, eg. remove curly braces, pseudo-selectors, dots, etc.

command line
#!/bin/bash
grep -hr --include "*.css" --include "*.scss" "^\s*\." ./src > ./tmp/tmp.txt;
# We're doing all kinds of operations with sed to clean our
# newly created list with all found class names
sed -i -e 's/\/\/.*$//' ./tmp/tmp.txt >/dev/null # remove comments
sed -i -e 's/{.*}//' ./tmp/tmp.txt >/dev/null # remove inline declarations
sed -i -e 's/{//g' ./tmp/tmp.txt >/dev/null # remove open brackets
sed -i -e 's/[,>+]\s*/\n/g' ./tmp/tmp.txt >/dev/null # split class names separated by , > +
sed -i -e 's/ /\n/g' ./tmp/tmp.txt >/dev/null # split any children classes e.g. .class .children
sed -i -e 's/\./\n./g' ./tmp/tmp.txt >/dev/null # split multiple selectors e.g. .aclass.active
sed -i -e 's/^[^.].*$//' ./tmp/tmp.txt >/dev/null # remove html tag selectors
sed -i -e 's/[:]\{1,2\}.*$//' ./tmp/tmp.txt >/dev/null # remove pseudo selectors e.g. :hover
sed -i -e 's/[#].*$//' ./tmp/tmp.txt >/dev/null # remove ids after classes e.g. .someclass#someid
sed -i -e '/^$/d' ./tmp/tmp.txt >/dev/null # remove empty lines
sed -i -e 's/^\.//' ./tmp/tmp.txt >/dev/null # remove starting dot
sed -i -e 's/^[ \t]*//;s/[ \t]*$//' ./tmp/tmp.txt >/dev/null # remove whitespace from each line
sort -u ./tmp/tmp.txt > all-classes.txt
rm ./tmp/tmp.txt
for pattern in $(cat ./all-classes.txt); do
grep -hrq --include \*.html --include \*.jsx --include \*.js $pattern ./src || echo $pattern;
done

We're passing the flags -hr to our initial grep and -hrq to the second grep to prevent it from printing out filenames or errors and to make sure it's recursively looking through all sub-folders inside ./src. Inside our loop, we include all html, jsx and js files to check for occurrences of the selectors in our list. Just like with finding images and assets above, we only print those CSS selectors that grep can't find in our codebase, leading us to a list of all unused selectors.

Keep in mind that this is a very naive and simple approach that might not find all nested or complex selectors, especially when dealing with SASS and other preprocessors.form