My images still too big. I converted the images in one post and saw good results, but the rest of the posts are filled up with huge images in non-web efficient formats. What to do? Script a solution!
My Images Are Too Big; or, Google Page Speed is Your Friend Part 2
Google Page Speed
Let’s pick up where we left off:
Good, but we can do better.
I need to address the images on my site that are not using webp, which is all of them.
To do this by hand would suck, and why would anyone do that when scripting up a solution would be faster, repeatable, and more fun?
Scripting a Solution
I’ve never done a lot of scripting in my career. Up until recently, I’d been strictly a coder (not that scripting isn’t coding), working my lane, on a small piece of something larger that involved infrastructure, security, and the scripts that go hand-in-hand with it all; or, designing pieces that fit together.
That changed when I worked at an agency. Given the people count was low, development was done much in the spirit of the original meaning of DevOps
DevOps is a combination of software developers (dev) and operations (ops). It is defined as a software engineering methodology which aims to integrate the work of software development and software operations teams by facilitating a culture of collaboration and shared responsibility. https://about.gitlab.com/topics/devops/
Devs were expected to know about all areas that were touched by a piece of software, come up with solutions for maintaining and administering them, and they were given the power to implement those solutions. Developers were trusted, and barriers were removed to get things done quickly.
Back to the task at hand: my first thought was to use bash, as I like digging around in Linux. I like Linux in general, despite being a beginner.
Work, however, is a Microsoft shop with a few exceptions; that means Powershell.
I don’t know Powershell.
Time to get to know Powershell.
The Plan
Time to write a set of steps to get me where I want to go. Tech is secondary, the problem is everything.
1. Get all the images in my repository that aren't webp.
2. For each image, convert it to webp.
3. Find all references in the markdown files, and change each reference to the new filename (.webp)
Bob’s your uncle.
Time to Powershell up this bad boy and get my blog performing like a thoroughbred instead of an old nag.
Powershelling Like a Boss
Since I knew nothing, it’s more like Googling like a boss, amiright?
How to get all files/filenames?
Learning Powershell
Going in blind? Have a look at the Learning Powershell guide at Microsoft.
It’s what you want.
Getting Files
How to get files?
Get-ChildItem is your guy.
The Get-ChildItem cmdlet gets the items in one or more specified locations. If the item is a container, it gets the items inside the container, known as child items. You can use the Recurse parameter to get items in all child containers and use the Depth parameter to limit the number of levels to recurse. Get-ChildItem doesn’t display empty directories. When a Get-ChildItem command includes the Depth or Recurse parameters, empty directories aren’t included in the output. Locations are exposed to Get-ChildItem by PowerShell providers. A location can be a file system directory, registry hive, or a certificate store. For more information, see about_Providers.
For Each
How do I loop? How do I for each?
This is how: https://devblogs.microsoft.com/scripting/basics-of-powershell-looping-foreach/
Look pretty straightforward.
Two pieces of the puzzle down.
Looping then would be like this:
// find all assets that are non-webp images
foreach ($asset in $assets) {
// convert asset to webp
}
Converting Images
Webp, where for art thou?
Here thou art: https://developers.google.com/speed/webp
A command-line tool to convert images, now we’re getting somewhere.
What this tells me is that I need to add a parameter for passing in the path of the webp converter. I could add the path to system path, but better to assume I might not want to or have done that.
Param(
[Parameter(Mandatory = $true)]
[string]$webpPath
)
I’ve made the parameter mandatory, because I want the user to always have to tell the script where the webp converter is located.
Changing Text in the Markdown Files
Once the new webp files are in place, I need to find all the references to the old files in the markdown and change their extensions to .webp, otherwise all the links will break.
function Find-And-Replace-In-Files {
param (
[string]$textToFind,
[string]$textToReplace,
[string[]]$filesToSearch
)
Write-Host Finding> $textToFind
Write-Host Replacing> $textToReplace
foreach ($fileToSearch in $filesToSearch) {
$content = get-content $fileToSearch
$fullTextToFind = "assets/img/posts/" + $textToFind;
if ($content -match $fullTextToFind) {
$content | ForEach-Object { $_ -replace $textToFind, $textToReplace } | Set-Content $fileToSearch
}
}
}
Putting it All Together
All the pieces should be in place for a messy first draft of a script. Here goes!
Param(
[Parameter(Mandatory = $true)]
[string]$webpPath
)
function Find-And-Replace-In-Files {
param (
[string]$textToFind,
[string]$textToReplace,
[string[]]$filesToSearch
)
Write-Host Finding> $textToFind
Write-Host Replacing> $textToReplace
foreach ($fileToSearch in $filesToSearch) {
$content = get-content $fileToSearch
$fullTextToFind = "assets/img/posts/" + $textToFind;
if ($content -match $fullTextToFind) {
$content | ForEach-Object { $_ -replace $textToFind, $textToReplace } | Set-Content $fileToSearch
}
}
}
# set path to webp converter
$webpExeFullPath = $webpPath + "\cwebp.exe";
Write-Host "Path to webp converter: " $webpExeFullPath;
$markdownPosts = Get-ChildItem .\_posts -Include ('*.md', '*.markdown') -recurse;
# get all jpg and png files
$assets = Get-ChildItem .\* -Include ('*.jpg', '*.jpeg', '*.png') -recurse;
foreach ($asset in $assets) {
# replace the file extension with webp for the new file
$assetWebpFilenameFullPath = $asset.Directory.ToString() + "\" + $asset.Name.Substring(0, $asset.Name.Length - $asset.Extension.Length) + '.webp';
$assetWebpFilename = "$($asset.Name.Substring(0, $asset.Name.Length - $asset.Extension.Length)).webp";
# create the arguments for the command in the format:
# cwebp [options] -q quality input.png -o output.webp
# quality defaults to 75
# image files must be in doublequotes in windows.
Write-Host `n****************`nProcessing $asset...
Write-Host New Filename: $assetWebpName;
$arguments = "`"$($asset)`" -o `"$assetWebpFilenameFullPath`""
Write-Host $arguments
Start-Process -Wait $webpExeFullPath -NoNewWindow -PassThru $arguments
Write-Host Done Processing $asset.
Find-And-Replace-In-Files -textToFind $asset.Name -textToReplace $assetWebpFilename -filesToSearch $markdownPosts
Write-Host ****************`n
}
Time to let it rip, and then clean up later!
The End
It worked!
Everything converted! I checked in all the changes, deleted the non-webp images, and pushed it up.
Phew!
It wasn’t pretty, but it was fun, and it got the job done.
There’s lots more to dig into, like accessibility, the gifs being massive, and much more.
Until then, keep improving every day!