In this post I describe a .NET Core CLI global tool I created that can be used to compress images using the TinyPNG developer API. I'll give some background on .NET Core CLI tools, describe the changes to tooling in .NET Core 2.1, and show some of the code required to build your own global tools. You can find the code for the tool in this post at https://github.com/andrewlock/dotnet-tinify.
The code for my global tool was heavily based on the
dotnet-serve
tool by Nate McMaster. If you're interested in global tools, I strongly suggest reading his post on them, as it provides background, instructions, and an explanation of what's happening under the hood. He's also created a CLI template you can install to get started.
.NET CLI tools prior to .NET Core 2.1
The .NET CLI (which can be used for .NET Core and ASP.NET Core development) includes the concept of "tools" that you can install into your project. This includes things like the EF Core migration tool, the user-secrets tool, and the dotnet watch
tool.
Prior to .NET Core 2.1, you need to specifically install these tools in every project where you want to use them. Unfortunately, there's no tooling for doing this either in the CLI or in Visual Studio. Instead, you have to manually edit your .csproj file and add a DotNetCliToolReference
:
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
The tools themselves are distributed as NuGet packages, so when you run a dotnet restore
on the project, it will restore the tool at the same time.
Adding tool references like this to every project has both upsides and downsides. On the one hand, adding them to the project file means that everyone who clones your repository from source control will automatically have the correct tools installed. Unfortunately, having to manually add this line to every project means that I rarely bother installing non-essential-but-useful tools like dotnet watch
anymore.
.NET Core 2.1 global tools
In .NET Core 2.1, a feature was introduced that allows you to globally install a .NET Core CLI tool. Rather than having to install the tool manually in every project, you install it once globally on your machine, and then you can run the tool from any project.
You can think of this as synonymous with
npm -g
global packages
The intention is to expose all the first-party CLI tools (such as dotnet-user-secrets
and dotnet-watch
) as global tools, so you don't have to remember to explicitly install them into your projects. Obviously this has the downside that all your team have to have the same tools (and potentially the same version of the tools) installed already.
You can install a global tool using the .NET Core 2.1 SDK). For example, to install Nate's dotnet serve
tool, you just need to run:
dotnet install tool --global dotnet-serve
You can then run dotnet serve
from any folder.
In the next section I'll describe how I built my own global tool dotnet-tinify
that uses the TinyPNG api to compress images in a folder.
Compressing images using the TinyPNG API
Images make up a huge proportion of the size of a website - a quick test on the Amazon home page shows that 94% of the page's size is due to images. That means it's important to make sure your images aren't using more data than they need too, as it will slow down your page load times.
Page load times are important when you're running an ecommerce site, but they're important everywhere else too. I'm much more likely to abandon a blog if it takes 10 seconds to load the page, than if it pops in instantly.
Before I publish images on my blog, I always wake sure they're as small as they can be. That means resizing them as necessary, using the correct format (.png for charts etc, .jpeg for photos), but also squashing them further.
Different programs will save images with different quality, different algorithms, and different metadata. You can often get smaller images without a loss in quality by just stripping the metadata and using a different compression algorithm. When I as using a Mac, I typically used ImageOptim; now I typically use the TinyPNG website.
To improve my workflow, rather than manually uploading and downloading images, I decided a global tool would be perfect. I could install it once, and run dotnet tinify .
to squash all the images in the current folder.
Creating a .NET Core global tool
Creating a .NET CLI global tool is easy - it's essentially just a console app with a few additions to the .csproj file. Create a .NET Core Console app, for example using dotnet new console
, and update your .csproj to add the IsPackable
and PackAsTool
elements:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsPackable>true</IsPackable>
<PackAsTool>true</PackAsTool>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>
It's as easy as that!
You can add NuGet packages to your project, reference other projects, anything you like; it's just a .NET Core console app! In the final section of this post I'll talk briefly about the dontet-tinify
tool I created.
dotnet-tinify
: a global tool for squashing images
To be honest, creating the tool for dotnet-tinify
really didn't take long. Most of the hard work had already been done for me, I just plugged the bits together.
TinyPNG provides a developer API you can use to access their service. It has an impressive array of client libraries to choose from (e.g HTTP, Ruby, PHP, Node.js, Python, Java and .NET), and is even free to use for the first 500 compressions per month. To get started, head to https://tinypng.com/developers and signup (no credit card) to get an API key:
Given there's already an official client library (and it's .NET Standard 1.3 too!) I decided to just use that in dotnet-tinify
. Compressing an image is essentially a 4 step process:
1. Set the API key on the static Tinify
object:
Tinify.Key = apiKey;
2. Validate the API key
await Tinify.Validate();
3. Load a file
var source = Tinify.FromFile(file);
4. Compress the file and save it to disk
await source.ToFile(file);
There's loads more you can with the API: resizing images, loading and saving to buffers, saving directly to s3. For details, take a look at the documentation.
With the functionality aspect of the tool sorted, I needed a way to pass the API key and path to the files to compress to the tool. I chose to use Nate McMaster's CommandLineUtils fork, McMaster.Extensions.CommandLineUtils
, which is one of many similar libraries you can use to handle command-line parsing and help message generation.
You can choose to use either the builder API or an attribute API with the CommandLineUtils package, so you can choose whichever makes you happy. With a small amount of setup I was able to get easy command line parsing into strongly typed objects, along with friendly help messages on how to use the tool with the --help
argument:
> dotnet tinify --help
Usage: dotnet tinify [arguments] [options]
Arguments:
path Path to the file or directory to squash
Options:
-?|-h|--help Show help information
-a|--api-key <API_KEY> Your TinyPNG API key
You must provide your TinyPNG API key to use this tool
(see https://tinypng.com/developers for details). This
can be provided either as an argument, or by setting the
TINYPNG_APIKEY environment variable. Only png, jpeg, and
jpg, extensions are supported
And that's it, the tool is finished. It's very basic at the moment (no tests 😱!), but currently that's all I need. I've pushed an early package to NuGet and the code is on GitHub so feel free to comment / send issues / send PRs.
You can install the tool using
dotnet install tool --global dotnet-tinify
You need to set your tiny API key in the TINYPNG_APIKEY
environment for your machine (e.g. by executing setx TINYPNG_APIKEY abc123
in a command prompt), or you can pass the key as an argument to the dotnet tinify
command (see below)
Typical usage might be
dotnet tinify image.png
- compress image.png in the current directorydotnet tinify .
- compress all the png and jpeg images in the current directorydotnet tinify "C:\content"
- compress all the png and jpeg images in the "C:\content" pathdotnet tinify image.png -a abc123
- compress image.png , providing your API key as an argument
So give it a try, and have a go at writing your own global tool, it's probably easier than you think!
Summary
In this post I described the upcoming .NET Core global tools, and how they differ from the existing .NET Core CLI tools. I then described how I created a .NET Core global tool to compress my images using the TinyPNG developer API. Creating a global tool is as easy as setting a couple of properties in your .csproj file, so I strongly suggest you give it a try. You can find the dotnet-tinify
tool I created on NuGet or on GitHub. Thanks to Nate McMaster for (heavily) inspiring this post!