terminal – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Wed, 22 Sep 2021 14:33:12 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 terminal – CSS-Tricks https://css-tricks.com 32 32 45537868 How to Implement Logging in a Node.js Application With Pino-logger https://css-tricks.com/how-to-implement-logging-in-a-node-js-application-with-pino-logger/ https://css-tricks.com/how-to-implement-logging-in-a-node-js-application-with-pino-logger/#comments Wed, 22 Sep 2021 14:33:10 +0000 https://css-tricks.com/?p=352074 Logging, on its own, is a key aspect of any application. Logging helps developers comprehend what it is that their code is doing. It also helps save developers hours of debugging work. This tutorial is about implementing logging in a …


How to Implement Logging in a Node.js Application With Pino-logger originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Logging, on its own, is a key aspect of any application. Logging helps developers comprehend what it is that their code is doing. It also helps save developers hours of debugging work. This tutorial is about implementing logging in a Node.js application using Pino-logger.

With logging, you can store every bit of information about the flow of the application. With Pino as a dependency for a Node.js application, it becomes effortless to implement logging, and even storing these logs in a separate log file. And its 7.8K stars on GitHub are a testament to that.

In this guide:

  • You will study how to configure logging services with different logging levels.
  • You will learn how to prettify the logs in your terminal as well as whether or not to include the JSON response in your logs.
  • You will see how to save these logs in a separate log file.

When you’re done, you’ll be able to implement logging with coding-best practices in your Node.js application using Pino-logger.

Prerequisites

Before following this tutorial make sure you have:

  • Familiarity with using Express for a server.
  • Familiarity with setting up a REST API without any authentication.
  • An understanding of command-line tools or integrated terminals in code editors.

Downloading and installing a tool like Postman is recommended for testing API endpoints.

Step 1: Setting up the project

In this step, you set up a basic Node.js CRUD application using Express and Mongoose. You do this because it is better to implement logging functionality in a codebase that mimics a real-world application.

Since this article is about implementing the logger, you can follow “How To Perform CRUD Operations with Mongoose and MongoDB Atlas” to create your basic CRUD application in Node.js.

After completing that tutorial, you should be ready with a Node.js application that includes create, read, update, and delete routes.

Also, at this point. You can download nodemon so that each time you save changes in your codebase, the server automatically restarts and you don’t have to manually start it again with node server.js.

So, write this command in your terminal:

npm install -g --force nodemon

The -g flag depicts that the dependency is installed globally and, to perform something globally, you are adding the --force flag in the command.

Step 2: Installing Pino

In this step, you install the latest versions of dependencies required for the logging. These include Pino, Express-Pino-logger, and Pino-pretty. You need the following command in your command-line tool from the project’s root directory.

npm install pino@6.11.3 express-pino-logger@6.0.0 pino-pretty@5.0.2

At this point, you are ready to create a logger service with Pino.

Step 3: Creating the logger service

In this step, you create a Pino-logger service with different levels of logs, like warning, error, info, etc.

After that, you configure this logger-service in your app using Node.js middleware. Start by creating a new services directory in the root folder:

mkdir services

Inside of this new directory, create a new loggerService.js file and add the following code:

const pino = require('pino')
module.exports = pino({})

This code defines the most basic logger service that you can create using Pino-logger. The exported pino function takes two optional arguments, options, and destination, and returns a logger instance.

However, you are not passing any options currently because you will configure this logger service in the later steps. But, this can create a little problem with this logger-service: the JSON log that you will see in a minute is not readable. So, to change it into the readable format, you mention the prettyPrint option in the exported pino function and, after that, your loggerService.js file should look something like this:

const pino = require('pino')
module.exports = pino(
  {
    prettyPrint: true,
  },
)

Configuring your loggerService is covered in later steps.

The next step to complete this logger service is to add the following lines of code in your server.js file in the root directory:

const expressPinoLogger = require('express-pino-logger');
const logger = require('./services/loggerService');

In this code, you are importing the logger service that you just made as well as the express-pino-logger npm package that you installed earlier.

The last step is to configure the express-pino-logger with the logger service that you made. Add this piece of code after const app = express(); in the same file:

// ...

const loggerMidlleware = expressPinoLogger({
  logger: logger,
  autoLogging: true,
});

app.use(loggerMidlleware);

// ...

This code establishes a loggerMiddleware creation using the expressPinoLogger. The first option passed in the function is the logger itself that depicts the loggerService that you created earlier. The second option is autoLogging that can take either true or false as value. It specifies whether you want the JSON response in your logs or not. That’s coming up.

Now, finally, to test the loggerService, revisit your foodRoutes.js file. Import the loggerService with this code at the top:

const logger = require('../services/loggerService')

Then, in the GET route controller method that you created earlier, put this line of code at the start of the callback function:

// ...

app.get("/food", async (request, response) => {
  logger.info('GET route is accessed')
  // ...
});

// ...

The info method is one of the default levels that comes with Pino-logger. Other methods are: fatal, error, warn, debug, trace or silent.

You can use any of these by passing a message string as the argument in it.

Now, before testing the logging service, here the complete code for the server.js file up to this point:

const express = require("express");
const expressPinoLogger = require('express-pino-logger');
const logger = require('./services/loggerService');
const mongoose = require("mongoose");
const foodRouter = require("./routes/foodRoutes.js");
const app = express();
// ...
const loggerMidleware = expressPinoLogger({
  logger: logger,
  autoLogging: true,
});
app.use(loggerMidleware);
// ...
app.use(express.json());
mongoose.connect(
  "mongodb+srv://madmin:<password>@clustername.mongodb.net/<dbname>?retryWrites=true&w=majority",
  {
    useNewUrlParser: true,
    useFindAndModify: false,
    useUnifiedTopology: true
  }
);
app.use(foodRouter);

app.listen(3000, () => {
  console.log("Server is running...");
});

Also, don’t forget to restart your server:

nodemon server.js

Now, you can see the log in your terminal. Test this API route endpoint in Postman, or something like that to see it. After testing the API, you should see something like this in your terminal:

Showing a black terminal window with output, including a first line in bright yellow, a second line in green and rest of the information in white. The information indicates the tool is watching files, starting the node server, when the GET route is accessed, and different API endpoints.

This provides a lot of information:

  • The first piece of the information is the log’s timestamp, which is displayed in the default format, but we can change it into something more readable in later steps.
  • Next is the info which is one of the default levels that comes with Pino-logger.
  • Next is a little message saying that the request has been completed.
  • At last, you can see the whole JSON response for that particular request in the very next line.

Step 4: Configuring the logs

In this step, you learn how to configure the Logger service and how to prettify the logs in your terminal using pino-pretty along with built-in options from the pino package you installed earlier.

Custom levels

At this point, you know that the pino-logger comes with default levels of Logging that you can use as methods to display Logs.

You used logger.info in the previous step.

But, pino-logger gives you the option to use custom levels. Start by revisiting the loggerService.js file in your services directory. Add the following lines of code after you have imported the pino package at the top:

// ...
const levels = {
  http: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};
// ...

This code is a plain JavaScript object defining additional logging levels. The keys of this object correspond to the namespace of the log level, and the values should be the numerical value of that level.

Now, to use this, you have to specify all that in the exported Pino function that you defined earlier. Remember that the first argument it takes is an object with some built-in options.

Rewrite that function like this:

module.exports = pino({
  prettyPrint: true,
  customLevels: levels, // our defined levels
  useOnlyCustomLevels: true,
  level: 'http',
})

In the above code:

  • The first option, customLevels: levels, specifies that our custom log levels should be used as additional log methods.
  • The second option, useOnlyCustomLevels: true, specifies that you only want to use your customLevels and omit Pino’s levels.

/explanation To specify second option, useOnlyCustomLevels, Logger’s default level must be changed to a value in customLevels. That is why you specified the third option.

Now, you can again test your loggerService and try using it with one of your customLevels. Try it with something like this in your foodRoutes.js file:

// ...

app.get"/foods", async (request, response) => {
    logger.http('GET route is accessed')
});

// ...

/explanation Don’t forget to make the autoLogging: false in your server.js file because there is no actual need for the irrelevant JSON response that comes with it.

const pino = require('pino')
const levels = {
  http: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};
module.exports = pino(
  {
    prettyPrint: true,
    customLevels: levels, // our defined levels
    useOnlyCustomLevels: true,
    level: 'http',
  },
)

You should get something like this in your terminal:

A black terminal window that shows the node server starting in green, a note that the server is running, and a timestamp for when the GET route is accessed.

And, all the unnecessary information should be gone.

Pretty printing the Logs

Now you can move ahead and prettify the logs. In other words, you are adding some style to the terminal output that makes it easier (or “prettier”) to read.

Start by passing another option in the exported pino function. Your pino function should look something like this once that option is added:

module.exports = pino({
  customLevels: levels, // our defined levels
  useOnlyCustomLevels: true,
  level: 'http',
  prettyPrint: {
    colorize: true, // colorizes the log
    levelFirst: true,
    translateTime: 'yyyy-dd-mm, h:MM:ss TT',
  },
})

You have added another option, prettyPrint, which is a JavaScript object that enables pretty-printing. Now, inside this object, there are other properties as well:

  • colorize: This adds colors to the terminal logs. Different levels of logs are assigned different colors.
  • levelFirst: This displays the log level name before the logged date and time.
  • translateTime: This translates the timestamp into a human-readable date and time format.

Now, try the API endpoint again, but before that, make sure to put more than one logging statement to take a look at different types of logs in your terminal.

// ...

app.get("/foods", async (request, response) => {
  logger.info('GET route is accessed')
  logger.debug('GET route is accessed')
  logger.warn('GET route is accessed')
  logger.fatal('GET route is accessed')

// ...

You should see something like this in your terminal:

A black terminal window with the same information as before, but with colored labels for different lines of information, like a red label for a fatal message.

At this point, you have configured your logger service enough to be used in a production-grade application.

Step 5: Storing logs in a file

In this last step, you learn how to store these logs in a separate log file. Storing logs in a separate file is pretty easy. All you have to do is make use of the destination option in your exported pino-function.

You can start by editing the pino-function by passing the destination option to it like this:

module.exports = pino(
  {
    customLevels: levels, // the defined levels
    useOnlyCustomLevels: true,
    level: 'http',
    prettyPrint: {
      colorize: true, // colorizes the log
      levelFirst: true,
      translateTime: 'yyyy-dd-mm, h:MM:ss TT',
    },
  },
  pino.destination(`${__dirname}/logger.log`)
)

pino.destination takes the path for the log file as the argument. The __dirname variable points to the current directory, which is the services directory for this file.

/explanation You added the logger.log file in your path even though it doesn’t exist yet. That’s because the file is created automatically when saving this file. If, for some reason, it does not create the file, you can create one manually and add it to the folder.

Here is the complete loggerService.js file:

const pino = require('pino')
const levels = {
  http: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};
module.exports = pino(
  {
    customLevels: levels, // our defined levels
    useOnlyCustomLevels: true,
    level: 'http',
    prettyPrint: {
      colorize: true, // colorizes the log
      levelFirst: true,
      translateTime: 'yyyy-dd-mm, h:MM:ss TT',
    },
  },
  pino.destination(`${__dirname}/logger.log`)
)

Test your API again, and you should see your logs in your log file instead of your terminal.

Conclusion

In this article, you learned how to create a logging service that you can use in production-grade applications. You learned how to configure logs and how you can store those logs in a separate file for your future reference.

You can still experiment with various configuring options by reading the official Pino-logger documentation.

Here are a few best practices you can keep in mind when creating a new logging service:

  • Context: A log should always have some context about the data, the application, the time, etc.
  • Purpose: Each log should have a specific purpose. For example, if the given log is used for debugging, then you can make sure to delete it before making a commit.
  • Format: The format for all the logs should always be easy to read.

How to Implement Logging in a Node.js Application With Pino-logger originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-implement-logging-in-a-node-js-application-with-pino-logger/feed/ 1 352074
Converting and Optimizing Images From the Command Line https://css-tricks.com/converting-and-optimizing-images-from-the-command-line/ https://css-tricks.com/converting-and-optimizing-images-from-the-command-line/#comments Mon, 21 Dec 2020 16:26:26 +0000 https://css-tricks.com/?p=330977 Images take up to 50% of the total size of an average web page. And if images are not optimized, users end up downloading extra bytes. And if they’re downloading extra bytes, the site not only takes that much more …


Converting and Optimizing Images From the Command Line originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Images take up to 50% of the total size of an average web page. And if images are not optimized, users end up downloading extra bytes. And if they’re downloading extra bytes, the site not only takes that much more time to load, but users are using more data, both of which can be resolved, at least in part, by optimizing the images before they are downloaded.

Researchers around the world are busy developing new image formats that possess high visual quality despite being smaller in size compared to other formats like PNG or JPG. Although these new formats are still in development and generally have limited browser support, one of them, WebP, is gaining a lot of attention. And while they aren’t really in the same class as raster images, SVGs are another format many of us have been using in recent years because of their inherently light weight.

There are tons of ways we can make smaller and optimized images. In this tutorial, we will write bash scripts that create and optimize images in different image formats, targeting the most common formats, including JPG, PNG, WebP, and SVG. The idea is to optimize images before we serve them so that users get the most visually awesome experience without all the byte bloat.

Our targeted directory of images
Our directory of optimized images

This GitHub repo has all the images we’re using and you’re welcome to grab them and follow along.

Set up

Before we start, let’s get all of our dependencies in order. Again, we’re writing Bash scripts, so we’ll be spending time in the command line.

Here are the commands for all of the dependencies we need to start optimizing images:

sudo apt-get update
sudo apt-get install imagemagick webp jpegoptim optipng
npm install -g svgexport svgo

It’s a good idea to know what we’re working with before we start using them:

OK, we have our images in the original-images directory from the GitHub repo. You can follow along at commit 3584f9b.

Note: It is strongly recommended to backup your images before proceeding. We’re about to run programs that alter these images, and while we plan to leave the originals alone, one wrong command might change them in some irreversible way. So back anything up that you plan to use on a real project to prevent cursing yourself later.

Organize images

OK, we’re technically set up. But before we jump into optimizing all the things, we should organize our files a bit. Let’s organize them by splitting them up into different sub-directories based on their MIME type. In fact, we can create a new bash to do that for us!

The following code creates a script called organize-images.sh:

#!/bin/bash

input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

for img in $( find $input_dir -type f -iname "*" );
do
  # get the type of the image
  img_type=$(basename `file --mime-type -b $img`)

  # create a directory for the image type
  mkdir -p $img_type

  # move the image into its type directory
  rsync -a $img $img_type
done

This might look confusing if you’re new to writing scripts, but what it’s doing is actually pretty simple. We give the script an input directory where it looks for images. the script then goes into that input directory, looks for image files and identifies their MIME type. Finally, it creates subdirectories in the input folder for each MIME type and drops a copy of each image into their respective sub-directory.

Let’s run it!

bash organize-images.sh original-images

Sweet. The directory looks like this now. Now that our images are organized, we can move onto creating variants of each image. We’ll tackle one image type at a time.

Convert to PNG

We will convert three types of images into PNG in this tutorial: WebP, JPEG, and SVG. Let’s start by writing a script called webp2png.sh, which pretty much says what it does: convert WebP files to PNG files.

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.webp" );
do
  dwebp $img -o ${img%.*}.png
done

Here’s what happening:

  • input_dir="$1": Stores the command line input to the script
  • if [[ -z "$input_dir" ]]; then: Runs the subsequent conditional if the input directory is not defined
  • for img in $( find $input_dir -type f -iname "*.webp" );: Loops through each file in the directory that has a .webp extension.
  • dwebp $img -o ${img%.*}.png: Converts the WebP image into a PNG variant.

And away we go:

bash webp2png.sh webp

We now have our PNG images in the webp directory. Next up, let’s convert JPG/JPEG files to PNG with another script called jpg2png.sh:

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

# for each jpg or jpeg in the input directory
for img in $( find $input_dir -type f -iname "*.jpg" -o -iname "*.jpeg" );
do
  convert $img ${img%.*}.png
done

This uses the convert command provided by the ImageMagick package we installed. Like the last script, we provide an input directory that contains JPEG/JPG images. The script looks in that directory and creates a PNG variant for each matching image. If you look closely, we have added -o -iname "*.jpeg" in the find. This refers to Logical OR, which is the script that finds all the images that have either a .jpg or .jpeg extension.

Here’s how we run it:

bash jpg2png.sh jpeg

Now that we have our PNG variants from JPG, we can do the exact same thing for SVG files as well:

#!/bin/bash

# directory containing images
input_dir="$1"

# png image width
width="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$width" ]]; then
  echo "Please specify image width."
  exit 1
fi

# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
  svgexport $img ${img%.*}.png $width:
done

This script has a new feature. Since SVG is a scalable format, we can specify the width directive to scale our SVGs up or down. We use the svgexport package we installed earlier to convert each SVG file into a PNG:

bash svg2png.sh svg+xml

Commit 76ff80a shows the result in the repo.

We’ve done a lot of great work here by creating a bunch of PNG files based on other image formats. We still need to do the same thing for the rest of the image formats before we get to the real task of optimizing them.

Convert to JPG

Following in the footsteps of PNG image creation, we will convert WebP, JPEG, and SVG into JPG. Let’s start by writing a script called png2jpg.sh that converts PNG to SVG:

#!/bin/bash

# directory containing images
input_dir="$1"

# jpg image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each png in the input directory
for img in $( find $input_dir -type f -iname "*.png" );
do
  convert $img -quality $quality% ${img%.*}.jpg
done

You might be noticing a pattern in these scripts by now. But this one introduces a new power where we can set a -quality directive to convert PNG images to JPG images. Rest is the same.

And here’s how we run it:

bash png2jpg.sh png 90

Woah. We now have JPG images in our png directory. Let’s do the same with a webp2jpg.sh script:

#!/bin/bash

# directory containing images
input_dir="$1"

# jpg image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.webp" );
do
  # convert to png first
  dwebp $img -o ${img%.*}.png

  # then convert png to jpg
  convert ${img%.*}.png -quality $quality% ${img%.*}.jpg
done

Again, this is the same thing we wrote for converting WebP to PNG. However, there is a twist. We cannot convert WebP format directly into a JPG format. Hence, we need to get a little creative here and convert WebP to PNG using dwebp and then convert PNG to JPG using convert. That is why, in the for loop, we have two different steps.

Now, let’s run it:

bash webp2jpg.sh jpeg 90

Voilà! We have created JPG variants for our WebP images. Now let’s tackle SVG to JPG:

#!/bin/bash

# directory containing images
input_dir="$1"

# jpg image width
width="$2"

# jpg image quality
quality="$3"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$width" ]]; then
  echo "Please specify image width."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
    svgexport $img ${img%.*}.jpg $width: $quality%
done

You might bet thinking that you have seen this script before. You have! We used the same script for to create PNG images from SVG. The only addition to this script is that we can specify the quality directive of our JPG images.

bash svg2jpg.sh svg+xml 512 90

Everything we just did is contained in commit 884c6cf in the repo.

Convert to WebP

WebP is an image format designed for modern browsers. At the time of this writing, it enjoys roughly 90% global browser support, including with partial support in Safari. WebP’s biggest advantage is it’s a much smaller file size compared to other mage formats, without sacrificing any visual quality. That makes it a good format to serve to users.

But enough talk. Let’s write a png2webp.sh that — you guessed it — creates WebP images out of PNG files:

#!/bin/bash

# directory containing images
input_dir="$1"

# webp image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each png in the input directory
for img in $( find $input_dir -type f -iname "*.png" );
do
  cwebp $img -q $quality -o ${img%.*}.webp
done

This is just the reverse of the script we used to create PNG images from WebP files. Instead of using dwebp, we use cwebp.

bash png2webp.sh png 90

We have our WebP images. Now let’s convert JPG images. The tricky thing is that there is no way to directly convert a JPG files into WebP. So, we will first convert JPG to PNG and then convert the intermediate PNG to WebP in our jpg2webp.sh script:

#!/bin/bash

# directory containing images
input_dir="$1"

# webp image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.jpg" -o -iname "*.jpeg" );
do
  # convert to png first
  convert $img ${img%.*}.png

  # then convert png to webp
  cwebp ${img%.*}.png -q $quality -o ${img%.*}.webp
done

Now we can use it like this to get our WebP variations of JPG files:

bash jpg2webp.sh jpeg 90

Commit 6625f26 shows the result.

Combining everything into a single directory

Now that we are done converting stuff, we’re one step closer to optimize our work. But first, we’re gong to bring all of our images back into a single directory so that it is easy to optimize them with fewer commands.

Here’s code that creates a new bash script called combine-images.sh:

#!/bin/bash

input_dirs="$1"
output_dir="$2"

if [[ -z "$input_dirs" ]]; then
  echo "Please specify an input directories."
  exit 1
elif [[ -z "$output_dir" ]]; then
  echo "Please specify an output directory."
  exit 1
fi

# create a directory to store the generated images
mkdir -p $output_dir

# split input directories comma separated string into an array
input_dirs=(${input_dirs//,/ })

# for each directory in input directory
for dir in "${input_dirs[@]}"
do
  # copy images from this directory to generated images directory
  rsync -a $dir/* $output_dir/
done

The first argument is a comma-separated list of input directories that will transfer images to a target combined directory. The second argument is defines that combined directory.

bash combine-images.sh jpeg,svg+xml,webp,png generated-images

The final output can be seen in the repo.

Optimize SVG

Let us start by optimizing our SVG images. Add the following code to optimize-svg.sh:

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
fi

# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
  svgo $img -o ${img%.*}-optimized.svg
done

We’re using the SVGO package here. It’s got a lot of options we can use but, to keep things simple, we’re just sticking with the default behavior of optimizing SVG files:

bash optimize-svg.sh generated-images
This gives us a 4KB saving on each image. Let’s say we were serving 100 SVG icons — we just saved 400KB!

The result can be seen in the repo at commit 75045c3.

Optimize PNG

Let’s keep rolling and optimize our PNG files using this code to create an optimize-png.sh command:

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

# for each png in the input directory
for img in $( find $input_dir -type f -iname "*.png" );
do
  optipng $img -out ${img%.*}-optimized.png
done

Here, we are using the OptiPNG package to optimize our PNG images. The script looks for PNG images in the input directory and creates an optimized version of each one, appending -optimized to the file name. There is one interesting argument, -o, which we can use to specify the optimization level. The default value is 2 **and values range from 0 to 7. To optimize our PNGs, we run:

bash optimize-png.sh generated-images
PNG optimization depends upon the information stored in the image. Some images can be greatly optimized while some show little to no optimization.

As we can see, OptiPNG does a great job optimizing the images. We can play around with the -o argument to find a suitable value by trading off between image quality and size. Check out the results in commit 4a97f29.

Optimize JPG

We have reached the final part! We’re going to wrap things up by optimizing JPG images. Add the following code to optimize-jpg.sh:

#!/bin/bash

# directory containing images
input_dir="$1"

# target image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each jpg or jpeg in the input directory
for img in $( find $input_dir -type f -iname "*.jpg" -o -iname "*.jpeg" );
do
  cp $img ${img%.*}-optimized.jpg
  jpegoptim -m $quality ${img%.*}-optimized.jpg
done

This script uses JPEGoptim. The problem with this package is that it doesn’t have any option to specify the output file. We can only optimize the image file in place. We can overcome this by first creating a copy of the image, naming it whatever we like, then optimizing the copy. The -m argument is used to specify image quality. It is good to experiment with it a bit to find the right balance between quality and file size.

bash optimize-jpg.sh generated-images 95

The results are shows in commit 35630da.

Wrapping up

See that? With a few scripts, we can perform heavy-duty image optimizations right from the command line, and use them on any project since they’re installed globally. We can set up CI/CD pipelines to create different variants of each image and serve them using valid HTML, APIs, or even set up our own image conversion websites.

I hope you enjoyed reading and learning something from this article as much as I enjoyed writing it for you. Happy coding!


Converting and Optimizing Images From the Command Line originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/converting-and-optimizing-images-from-the-command-line/feed/ 5 330977
Run Gulp as You Open a VS Code Project https://css-tricks.com/run-gulp-as-you-open-a-vs-code-project/ https://css-tricks.com/run-gulp-as-you-open-a-vs-code-project/#comments Mon, 12 Oct 2020 23:04:28 +0000 https://css-tricks.com/?p=322919 When I open my local project for this very site, there is a 100% chance that I need to run this command before anything else: gulp. I set that up fresh less than a year ago so I’m on …


Run Gulp as You Open a VS Code Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
When I open my local project for this very site, there is a 100% chance that I need to run this command before anything else: gulp. I set that up fresh less than a year ago so I’m on the latest-and-greatest stuff and have my workflow just how I like it. I did a few more tweaks a few months later to make things a smidge nicer (even adding a fancy fun little dock icon!).

That’s when I learned about VS Code Tasks. Generically, the can just run command line tasks that you configure whenever you choose to run them by name. But I’m particularly compelled by the idea that they can run when you open a project.

It’s this easy to run Gulp:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Run Gulp",
      "command": "gulp",
      "type": "shell",
      "runOptions": {
        "runOn": "folderOpen"
      }
    }
  ]
}

Except… that started to fail on my machine. I use nvm to manage Node versions, and despite my best effort to nvm alias default to the the correct version of Node that works nicely with Gulp, the Node version was always wrong, and thus running gulp would fail. The trick is to run nvm use first (which sets the correct version from my .nvmrc file), then gulp runs fine.

That works fine in a fresh terminal window, but for some reason, even making the command run two tasks like this (chaining them with a semicolon):

"command": "nvm use; gulp",

…it would still fail. It didn’t know what nvm meant. I don’t know what the heart of the problem is exactly (why one terminal doesn’t know the same things that another terminal does), but I did manage to sort out that the global nvm has a shell script with one job: defining the nvm command. So you “source” that, as they say, and then the nvm command works.

So my final setup is:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Run Gulp",
      "command": ". ~/.nvm/nvm.sh; nvm use; gulp",
      "type": "shell",
      "runOptions": {
        "runOn": "folderOpen"
      }
    }
  ]
}

And that, dear readers, runs Gulp perfectly when I open my CSS-Tricks project, which is exactly what I wanted.

High five to Jen Luker who went on this journey with me and helped me get it to the finish line. 🤚


Run Gulp as You Open a VS Code Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/run-gulp-as-you-open-a-vs-code-project/feed/ 4 322919
One Action, Multiple Terminal Windows Running Stuff https://css-tricks.com/one-action-multiple-terminal-windows-running-stuff/ https://css-tricks.com/one-action-multiple-terminal-windows-running-stuff/#comments Tue, 08 Sep 2020 20:06:48 +0000 https://css-tricks.com/?p=319881 Many development environments require running things in a terminal window. npm run start, or whatever. I know my biggest project requires me to be running a big fancy Docker-based thing in one terminal, Ruby on Rails in another, and …


One Action, Multiple Terminal Windows Running Stuff originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Many development environments require running things in a terminal window. npm run start, or whatever. I know my biggest project requires me to be running a big fancy Docker-based thing in one terminal, Ruby on Rails in another, and webpack in another. I’ve worked on other projects that require multiple terminal windows as well, and I don’t feel like I’m that unusual. I’ve heard from several others in this situation. It’s not a bad situation, it’s just a little cumbersome and annoying. I’ve got to remember all the commands and set up my command line app in a way that feels comfortable. For me, splitting panels is nicer than tabs, although tabs for separate projects seems OK.

I asked the question on Twitter, of course. I figured I’d compile the options here.

  • tmux was the most popular answer. I’m very sure I don’t understand all it can do, but I think I understand that it makes “fake” panes within one terminal session that emulates multiple panes. So, those multiple panes can be configured to open and run different commands simultaneously. I found this interesting because it came literally days later my CodePen co-founder let us all know the new dev environment he’s been working on will use tmux.
  • I was pointed to kitty by a fella who told me it feels like a grown-up tmux to him. It can be configured into layouts with commands that run at startup.
  • There are native apps for all the platforms that can run multiple panels.
    • macOS: I’ve long used iTerm which does split panels nicely. It can also remember window arrangements, which I’ve used, but I don’t see any built-in option for triggering commands in that arrangement. The native terminal can do tabs and splitting, too, but it feels very limited.
    • Linux: Terminator
    • Windows: The default terminal has panes.
  • There are npm things for running multiple scripts, like concurrently and npm-run-all, but (I think?) they are limited to running only npm scripts, rather than any terminal command. Maybe you can make npm scripts for those other commands? But even then, I don’t think you’d see the output in different panels, so it’s probably best for scripts that are run-and-done instead of run-forever.

Being a Mac guy, I was most interested in solutions that would work with iTerm since I’ve used that anyway. In lieu of a built-in iTerm solution, I did learn it was “scriptable.” Apparently, they are sunsetting AppleScript support in favor of Python but, hey, for now it seems to work fine.

It’s basically this:

The Code
tell application "iTerm"
	
  tell current window
		
    create window with default profile
    tell current session of current tab
      set name to "run.sh"
      write text "cd '/Users/chriscoyier/GitHub/CPOR'"
      write text "./run.sh"
    end tell
		
    create tab with default profile
    tell current session of current tab
      set name to "Rails"
      write text "cd '/Users/chriscoyier/GitHub/CPOR'"
      write text "nvm use"
      write text "yarn"
      write text "bundle install"
      write text "yarn run rails"
    end tell
		
    create tab with default profile
    tell current session of current tab
      set name to "webpack"
      write text "cd '/Users/chriscoyier/GitHub/CPOR'"
      write text "nvm use"
      write text "yarn"
      write text "yarn run dev"
    end tell
		
    # split vertically
    # tell application "System Events" to keystroke "d" using command down
    # delay 1
		
    # split horizontally
    # tell application "System Events" to keystroke "d" using {shift down, command down}
    # delay 1
		
    # moving... (requires permission)
    # tell application "System Events" to keystroke "]" using command down
		
    end tell
	
end tell

I just open that script, hit run, and it does the job. I left the comments in there because I’d like to figure out how to get it to do split screen the way I like, rather than tabs, but I got this working and then got lazy again. It felt weird to have to use keystrokes to have to do it, so I figured if I was going to dig in, I’d figure out if their newer Python stuff supports it more directly or what. It’s also funny I can’t like compile it into a little mini app or something. Can’t Automator do that? Shrug.

The other popular answer I got for Mac folks is that they have Alfred do the work. I never got into Alfred, but there clearly is fancy stuff you can do with it.


One Action, Multiple Terminal Windows Running Stuff originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/one-action-multiple-terminal-windows-running-stuff/feed/ 8 319881
A Guide to Console Commands https://css-tricks.com/a-guide-to-console-commands/ https://css-tricks.com/a-guide-to-console-commands/#comments Thu, 20 Feb 2020 14:48:28 +0000 https://css-tricks.com/?p=303621 The developer’s debugging console has been available in one form or another in web browsers for many years. Starting out as a means for errors to be reported to the developer, its capabilities have increased in many ways; such as …


A Guide to Console Commands originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The developer’s debugging console has been available in one form or another in web browsers for many years. Starting out as a means for errors to be reported to the developer, its capabilities have increased in many ways; such as automatically logging information like network requests, network responses, security errors or warnings.

There is also a way for a website’s JavaScript to trigger various commands that output to the console for debugging purposes. These commands are contained in a console object available in almost every browser. Even though these features are mostly consistent between browsers, there are a few differences. Some of these differences are simply visual in nature while others do have slight functional differences to keep in mind.

For the curious, here’s the spec by WHATWG linked from the MDN console docs.

This guide covers what’s available in the console object of Firefox and Chrome as they are often the most popular browsers for development and they do have a few differences in various aspects of the console. The new Chromium-based Edge is essentially the same as Chrome in many ways so, in most cases, the console commands will operate much the same.

Quick Links

The console.log command

The first thing we can do is log the console object itself to see what your browser of choice actually offers.

console.log(console);

This command will output the various properties of the console object as the browser knows them. Most of them are functions and will be rather consistent regardless of browser. If there are differences in the properties of the console object from one browser to another, this way you can see the differences. One such difference I can point out between Firefox and Chrome is that Chrome provides a “memory” property that outputs some basic memory usage stats. Firefox doesn’t provide this property and yet has a “name” property that Chrome does not have.

Thankfully, most of the differences between the browsers tend to be just as trivial. That way, you can be fairly confident that your code will output much the same regardless of the browser in use.

First things first: clear()

With heavy usage of the console comes a very crowded output of text. Sometimes you just want to clear things out and start with a fresh console. Browsers typically provide a button in DevTools that performs this task. However, the console object itself also provides a command to handle this:

console.clear();

This will clear the console and will helpfully inform you of that by outputting a message like “Console was cleared.”

Common usage: debug(), error(), info(), log(), and warn()

There are five commands that at first glance seem to do the exact same thing. And, technically, they do. But browsers provide additional features tied to the five commands to give each their own distinct benefit.

These five commands are:

console.debug();
console.error();
console.info();
console.log();
console.warn();

I’m sure many of you have seen console.log() before (I mean, we just talked about it up top) and have probably used it before. Before we get into what you can log in these five commands, let’s see our first minor difference between Chrome and Firefox.

Chrome console showing debug, error, info, log, and warn

This is an example in Chrome of each command outputting a string, such as console.debug('console.debug()');.  Notice that some of them have a color treatment to give a visual indication of the type of output it is. The error and warn outputs have an additional icon for even easier identification.

Firefox console showing debug, error, info, log, and warn

Here is the same list in Firefox and, while it looks similar, there are three minor differences. For example, console.debug() is not color-coded and console.info() has an additional icon next to it. In Chrome, both console.error() and console.warn() can be expanded to show additional information about the output while Firefox only does this with console.error(). This additional information provides a trace of the lines of code involved to get to where the particular command was called.

One thing that is useful about these five commands is that the browsers provide filtering options to show or hide each type as you wish. Firefox has them right there at the top of the console above the output while Chrome hides them in a dropdown, labeled “All levels” which you can see in the earlier Chrome console screenshot. “All levels” is there because I have all five set to be shown. If you were to choose the “Default” option then the debug output (listed as “Verbose”) is hidden while the others are shown. Unchecking “Info”, “Warnings”, or “Errors” causes the dropdown to display a different title such as “Custom levels” or “Errors only” depending on what is selected.

The intentions for usage of error and warn are easy to determine; how to use the other choices is up to you. If you do make extensive use of the different options then you might consider documenting the expectations of each as to not confuse things late in the project — especially if it is a team project.

Now, let’s discuss what we can actually log inside these commands. Since they all behave the same, I’ll just focus on logging as the example.

The simplest examples involve just passing a string, number, object, or array into the log command. Technically, any of JavaScript’s data types can be used, but for most of them, the output is much the same.

console.log('string');
console.log(42);
console.log({object: 'object'});
console.log(['array', 'array']);
Chrome string, number, object, and array log examples

I’m showing these examples in Chrome with the object and array already expanded. They are normally collapsed but the output next to the arrow is consistent between both states. Firefox displays a little differently but, for the most part, the output is the same. Firefox does tell you whether it is displaying an object or array before expanding, but shows the same as Chrome while expanded.

One interesting thing to add is that you can pass more than one item to the log as parameters and it’ll display them inline.

console.log('string', 'string');
console.log(42, 1138);
console.log({object: 'object'}, {object: 'object'});
console.log(['array', 'array'], ['array', 'array']);
Chrome strings, numbers, objects, and arrays examples

Often when I’m working with x and y coordinates, such as what can be outputted by mouse events, it’s useful to log the two together in one statement.

String substitution

The different console logging commands provide string substitution that allows inserting different values into the string for output. This is useful for describing a variable in the log to make it clear as to what’s being reported.

console.log('This is a string: %s', 'string');
console.log('This is a number: %i', 42);
console.log('This is an object: %o', {object: 'object'});
Chrome string substitution examples

Here is a list of the data types that can substituted into the output string:

Data typeSubstitution symbol
Objects and arrays %o or %O
Integers %d or %i
Strings %s
Floats %f

The first parameter would be the string to output with the symbols placed in the appropriate locations. Then each parameter after that is the value to substitute inside the first parameter’s string. Keep in mind that you’ll have to keep the substitution types and the parameters in the specific order or you’ll get unexpected results.

If your console supports template literals, it’s a bit easier to get similar results as string substitutions.

console.log(`This is a string: ${'string'}`);
console.log(`This is a number: ${42}`);
console.log(`This is an object: ${{object: 'object'}}`);
Chrome template literal examples

Notice that the object is handled a bit better with the string substitution, so pick the appropriate choice for your requirements. Since it’s possible to insert more than one value in the output, let’s compare the two.

console.log('This is a string: %s. This is a number: %i', 'string', 42);
console.log(`This is a string: ${'string'}. This is a number: ${42}`);
Chrome string substitution and template literals

With the string substitution each value is added as a parameter to be inserted into the output. With template literals, on the other hand, you add them wherever they need to be in the output. Also, you can combine them.

console.log(`This is a number: ${42}. This is an object: %o`, {object: 'object'});
Chrome string substitution with template literals

So, there are lots of options to pick and choose from so you can go with the best options for your needs. 

Styling the output

Another potentially useful and fun thing is that you can apply CSS styles to the console’s output. It works just like the string substitution method where you insert a %c variable for styles to be applied from the parameters.

Here’s a simple example:

console.log('%cThis is large red text', 'color: red; font-size: 30px;');
Chrome styling in the console

This time there is a slight difference in the Firefox output:

Firefox styling in the console

Not really that much of a difference, but something to keep in mind.

What essentially happens is that %c reads the strings in the parameters to determine what styling to apply. So, say there’s a second styling being passed, %c moves on to the next parameter, much like with string substitution. An empty string in the parameter list resets the styling back to default.

console.log('This is %cred text %cand this is %cgreen text.', 'color: red;', '', 'color: green;');
Using multiple styles in the Chrome console.

The styling properties available are rather limited when compared to typical CSS styling on a webpage. You can look at it as a sort of inline block of text that allow you to manipulate a limited set of styling properties.

With some work and experimenting, you could create interesting messaging within the console. One idea is to draw extra attention to a particular log, especially an error of some sort.

console.log('%cHello there!', `
  background: white;
  border: 3px solid red;
  color: red;
  font-size: 50px;
  margin: 40px;
  padding: 20px;
`);
Chrome custom styling

In this example, we can see that the CSS is a bit verbose, but there is something we can do to mimic the class system that we leverage in CSS. The values of each parameter for styling can be stored in variables to allow for repeated use without having to duplicate the string of styles in each parameter.

const clearStyles = '';
const largeText = 'font-size: 20px;';
const yellowText = 'color: yellow;';
const largeRedText = 'font-size: 20px; color: red;';
const largeGreenText = 'font-size: 20px; color: green;';


console.log(`This is %clarge red text.
%cThis is %clarge green text.
%cThis is %clarge yellow text.`,
  largeRedText,
  clearStyles,
  largeGreenText,
  clearStyles,
  largeText + yellowText
);
Chrome custom template styling

There are several things going on here, so let’s break it down a bit. First, we have a collection of variables that holds our styling strings. Think of each as a sort of class to be reused in the parameters of the console log.

We are also using a template literal in the log, which means we can have line breaks in our output. Then, for each %c in the text, there’s a corresponding variable used in a parameter to define the styles for that particular part of the output text. In addition to each variable that holds styling, there is also a clearStyles argument that can be used to reset styles to prepare for the next set of styling. You could just use an empty string as in previous examples, but I like the clear intention that comes from using the variable name. The last parameter shows that the variables can be combined, which opens up more possible ways of handling the styles.

Now, that’s a great deal of text covering essentially five console commands that only output text to the console. So, let’s move on to other commands of the console object. Although, some of these can still use many of the features described so far, we won’t focus on that aspect as much with the following commands.

Being assertive: assert()

The console.assert() command is similar to the error command mentioned previously. The difference is that asserting allows for the usage of a boolean condition to determine whether it should output the text to the console.

For example, let’s say you wanted to test the value of a variable and make sure it wasn’t larger than a certain number value. If the variable is below that number and the condition resolves to true, the assert command does nothing. If the condition resolves to false, then the output text is displayed. This way you don’t have to wrap a console.error() command with an if statement to determine if the error message is needed in the first place.

let value = 10;
console.assert(value <= 7, 'The value is greater than 7.');
Chrome assert example

We can see that assert has the same appearance as the error command, except that it also prepends “Assertion failed:” to the output text. Chrome can also expand this output to show a trace of where the assertion came from.

The trace can be quite helpful with common patterns of functions within functions calling other functions and so on. Although, you can see in the example above that the line the assert came from doesn’t tell you how the code got to that line.

let value = 10;


function function_one () {
  function_two();
}


function function_two () {
  function_three();
}


function function_three() {
  console.assert(value < 7, 'This was false.');
}


function_one();
Chrome assert with trace

This sequence is actually in reverse order in terms of the code. The last line shows an anonymous entry (which is an HTML script tag in this case) on line 78. That’s where function_one was called. Inside that function, we have a call for function_two, which, in turn, calls function_three. Inside that last function is where the assert is located. So, in this development world of functions sharing other functions; a description of the path to that point of the assert is quite handy.

Unfortunately, this trace is not provided in Firefox with the assert command, as it is with the error command.

Firefox assert example

Keeping count: count() and countReset()

Ever wonder how many times a certain thing happens in your code? For instance, how many times does a particular function get called during a sequence of events? That’s where the console.count() command can help out.

By itself, the count command is rather simple and has limited use. If you use the command in its default state you only get a simple count. For example, if we call it three times in a row, we get a sequential count.

console.count();
console.count();
console.count();
Chrome default count example

As you can see, we get a simple count from one to three. The default behavior means that count is merely incrementing the output by one each time it runs, no matter where it shows up in the code. You do get the line number in the code where it happened, but the count is a simple total no matter the situation.

To make this command a bit more useful, we can provide a label to keep a separate count for that label.

console.count('label A');
console.count('label B');
console.count('label A');
console.count('label B');
console.count('label A');
console.count('label B');
Chrome label count example

Even though using the count command with labels causes the output to alternate between labels, each one keeps its own count. One scenario where this comes in handy is placing a count inside a function so that every time that function is called, the count is incremented. The label option makes it so that a count can be kept for individual functions to provide for a good idea of how many times each function is being called. That’s great for troubleshooting performance bottlenecks or simply seeing how much work a page is doing.

There’s a way to reset the count. Let’s say we have a loop that gets called multiple times, but the number of iterations of the loop can be dynamic. This is done with the console.countReset() command with the same label from the count command.

console.count();
console.count();
console.countReset();
console.count();


console.count('this is a label');
console.count('this is a label');
console.countReset('this is a label');
console.count('this is a label');
Chrome count reset example

Each count — with and without a label — is called twice and console.countReset() is applied right before another count instance. You can see that Chrome counts up to two, then restarts when it encounters countReset. There’s nothing in DevTools to indicate the reset happened, so an assumption is made that it did happen because the count started over.

And yet, the same code is a bit different in Firefox.

Firefox count reset example

Here, the reset is indicated by the count being set all the way back to zero. That is the indicator that the reset was called, whereas we have no such indication in Chrome.

As for label options, just about anything can be used. I suppose a simple way to describe it is that if you give it anything that can be resolved to a string, it’ll probably work as a label. You could even use a variable that has values that change over time, where count will use the current value of the variable as a label each time it is encountered. So, you could keep count of the values as they change over time.

Describe that thing: dir() and dirxml()

The main idea behind these two commands is to display either properties of a Javascript object with console.dir() or descendant elements of an XML/HTML element with console.dirxml(). It appears Chrome has these implemented as expected, while Firefox just uses both as aliases for console.log().

Let’s give console.log(), console.dir(), and console.dirxml() the same simple object to see what we get. Keep in mind that you normally would not log an object with console.dirxml().

const count = {
  one: 'one',
  two: 'two',
  three: 'three'
};


console.log(count);
console.dir(count);
console.dirxml(count);
Chrome simple dir() and dirxml() example

Firefox gives us much the same, except the console.dir() is automatically expanded.

Firefox simple dir() and dirxml() example

Another simple comparison to console.log() is to repeat the object in the same command.

Chrome dir() and dirxml() double example
Firefox dir() and dirxml() double example

Not really that much different other than that Chrome doesn’t show the second object in console.dir() like Firefox does. Which makes sense because Chrome is trying to display properties of an object (ignoring the second) while Firefox is just aliasing everything to a console.log(). So, for situations like this with objects there is little difference between console.log(), console.dir(), and console.dirxml() in the browsers.

A useful benefit of console.dir() in Chrome that I can point out is how DOM elements are handled. For example, here’s how console.log() displays in Chrome and Firefox when given a DOM element.

Chrome console.log() DOM example.
Firefox console.log() DOM example

Now, I’ve always liked how Firefox outputs a DOM element inside a console.log(), as it gives you all the properties of that DOM element. So, when I wanted to look up a specific property of a DOM element to manipulate with JavaScript, it’s only a console.log() away to find it. Chrome, on the other hand, gives us the HTML code of the DOM element in the console.log() just like it would in console.dirxml().

To get the properties in Chrome, use console.dir() with the DOM element. I was quite happy to find that console.dir() in Chrome provides the properties of a DOM element just as I came to rely on that information in Firefox.

As for console.dirxml() in Chrome, it can be useful for displaying an HTML element and its children outside of the clutter of the DOM Inspector. You can even edit some of the existing HTML live in the console, but you won’t have the same level of abilities as in the DOM Inspector.

Let’s get together: group(), groupCollapsed(), and groupEnd()

Here’s a simple one: Group different console outputs together to show a form of relationship among them. It is somewhat limited in features so its usefulness will depend a great deal on how you plan to use it. This is the console.group() command.

console.group();
console.log('one');
console.log('two');
console.log('three');
console.groupEnd();


console.group('this is a label');
console.log('one');
console.log('two');
console.log('three');
console.groupEnd();
Chrome group() example

In the first block of code we call console.group() in its default state, have three logs, and then finally call console.groupEnd(). The console.groupEnd() simply defines the end of the grouping. The second block has a string as a parameter that essentially becomes the label for that group. Notice that in the first block without a label it just identifies itself as a console.group in Chrome while in Firefox it shows as <no group label>. In most cases, you’ll want a proper label to distinguish between groups.

Also notice the arrow next to the labels. Clicking on that collapses the group. In the code examples above, if we change console.group() to console.groupCollapsed(), they start collapsed and must be opened to see the output.

You can also nest the groups. The console.groupEnd() command simply refers to the last opened group.

console.group('outer group');
console.log('outer one');
console.log('outer two');
console.group('inner group');
console.log('inner one');
console.log('inner two');
console.log('inner three');
console.groupEnd();
console.log('outer three');
console.groupEnd();
Chrome nested group() example

Just as a quick note, if you want the group label to stand out a bit more in a list of output in the console, you can style it just as we did with strings earlier.

console.group('%cstyled group', 'font-size: 20px; color: red;');
console.log('one');
console.log('two');
console.log('three');
console.groupEnd();
Chrome styled group() example

Have a seat at the: table()

In previous examples, we’ve seen what happens when we put an array or object inside a console.log() or console.dir(). There’s another option for these data types for a more structured display, which is console.table().

Here’s a simple example with an array:

let basicArray = [
  'one',
  'two',
  'three'
];
console.table(basicArray);
Chrome basic array table() example

Here’s the same example in Firefox for comparison.

Firefox basic array table() example

A slight visual difference, but pretty much the same. That said, Chrome does still give you the expandable output under the table, much like you’d see in console.log(). Chrome will also provide basic column sorting if you click on the heading.

The output is similar when passing in an object:

let basicObject = {
  one: 'one',
  two: 'two',
  three: 'three'
};
console.table(basicObject);
Chrome basic object table() example

So, that was a pretty simple example with basic outputs. How about something a little more complex and is often used in coding projects? Let’s look at an array of objects.

let arrayOfObjects = [
  {
    one: 'one',
    two: 'two',
    three: 'three'
  },
  {
    one: 'one',
    two: 'two',
    three: 'three'
  },
  {
    one: 'one',
    two: 'two',
    three: 'three'
  }
];
console.table(arrayOfObjects);
Chrome array of objects table() example

As you can see, this gets us a nice layout of objects with repeating keys as column labels. Imagine data along the lines of user information, dates, or whatever might be data often used in loops. Keep in mind that all the keys in each of the objects will be represented as a column, whether there is corresponding keys with data in the other objects. If an object doesn’t have data for a key’s column, it appears as empty.

An array of arrays is similar to the array of objects. Instead of keys being labels for the columns, it uses the index of the inner arrays as column labels. So if an array has more items than the other arrays, then there will be blank items in the table for those columns. Just like with the array of objects.

So far, simple arrays and objects have simple output displayed. Even a slightly more complex array of objects still has a solid, useful structure. Things can get a bit different with mixing the data types though.

For example, an array of arrays where one of the inner array items is an object.

let arrayOfArraysWithObject = [
  ['one', 'two', {three: 'three', four: 'four'}],
  ['one', 'two', {three: 'three', four: 'four'}],
  ['one', 'two', {three: 'three', four: 'four'}]
];

console.table(arrayOfArraysWithObject);
Chrome array of arrays with object table() example

Now, to see what is contained in those objects in the third column, we’ll have to expand that array output below the table. Not that bad, really. Here’s how Firefox handles the same output.

Firefox array of array with object table() example

Firefox just lets us expand the object within the table.

How about mixing the data types the other way, where we have an object with arrays as values for each key? It works much the same as the array of arrays. The difference is that each row is labeled with a key instead of the index. Of course, for each level of data type you add to the mix will result in a more complex looking table. 

This is all about: time(), timeLog(), and timeEnd()

Here we have a simple way to log how long something takes to complete. We call console.time() with a label, call console.timeLog() with the same label for an update, and call console.timeEnd() again with the same label to stop the timer.

console.time('this is a timer');
console.timeLog('this is a timer');
console.timeEnd('this is a timer');

The output for Chrome and Firefox is much the same. Here’s an example output with code that logs the time every second for five seconds and then stops.

Chrome time() example
Firefox time() example

Notice that the reported times are not quite the same, but probably close enough for most requirements. Also, Firefox is nice enough to note that the timer has ended while Chrome requires an assumption once the label stops appearing. The first four lines of output come from the call console.timeLog('this is a timer'); and the last line is from the call to console.timeEnd('this is a timer');.

Dropping breadcrumbs with: trace()

The console.trace() command is actually similar to console.error() and console.warn(). Calling this command will output a stack trace to the console showing the path through the code to that call. We can even pass it a string as a form of label, but other data types such as arrays or objects can be passed. The behavior of passing data like that is the same as what we would get from a console.log() call. It’s a simple way to pass along some information to the console without triggering a more dire looking console.error() or console.warn() call.

debugger

This is a simple command to trigger a pause in the console’s debugger, if it exists. It is similar to placing a breakpoint in the debugger, or the browser’s equivalent, to cause the same type of pause while executing code. Here’s a simple example:

function whatsInHere() {
  debugger;
  // rest of the code
}

In this particular example, the open console’s debugger will pause code execution and the browser will open up the source file to show the line of code as soon as the function is called. It could be useful for easy breakpoints with some complicated projects.

Technically, the debugger command isn’t a part of the console object in the browser. It’s a useful feature that the console will respond to from JavaScript code.

Some additional console utilities

That’s a good look at most of the standard commands available to us in the console object. Each of these will work more-or-less the same across modern browsers. There may be some differences between browsers, as we saw in some of the examples. But there are a few more things I’d like to take a moment to point out, as they might prove useful in various ways.

The following examples can be considered more like console “utilities.” They are not a part of the console object like most of the previous examples. Therefore they are not called with a leading console object reference. These utilities are supported directly by the browsers themselves. They cannot be called from JavaScript code but must be typed directly in the console to be used. In some cases the utility might be unique to a particular browser, in others the utility is supported much the same way in several browsers. Your mileage may vary based on your browser of choice.

$0, $1, $2, $3, $4

These five commands are extremely handy. The first one, $0, represents the currently selected element in the DOM inspector. This essentially provides a shortcut instead of having to use more traditional DOM methods, such as getElementById or a querySelector. You can use it in various ways, within various console commands, or by itself to get information about the currently selected element. For example:

console.log($0);

The other commands in this set represent elements that were previously selected. Think of them as a form of selection history. $1 is the previous element, $2 is the previous before that, and so on. Although the first command is available in Firefox, the commands for previously selected elements are not.

$(‘element’), $$(‘elements’)

If you find yourself typing out document.querySelector('element') in the console repeatedly, there’s a shortcut. You can just type $('element') and it performs the same function. The shortcut might remind many of jQuery, but to select multiple elements reminds me of MooTools. To select multiple elements, you’d use $$('elements') instead of document.querySelectorAll('elements').

$x(‘//element’)

This is a shortcut for XPath that will return an array of elements that match the expression. An easy example is $x('//div'), which will present an array of every div element on the page. This isn’t that much different than using $$('div') like we did with $('element'), but there are many options for writing XPath expressions.

One example of a simple step up in a XPath expression is $x('//div[descendant::span]') (thanks to Neil Erdwien for the correction), which would return the div elements on the page that happen to contain a span element. This is the equivalent of :has in CSS Selectors Level 4 draft, which isn’t supported in browsers yet.

These are just basic examples that only scratch the surface of XPath.

clear()

This is another version of console.clear(), but without the “Console was cleared” message.

getEventListeners(object)

This command, when given a DOM element, will report the event listeners registered to that element. For example, using the $0 example from above we can use getEventListeners($0) to get something like this:

Chrome getEventListeners() example

Expanding each item in the array provides various information about that event listener. This function isn’t supported in Firefox, but it does offer something similar that can be found in the DOM inspector.

Firefox DOM Inspector events information.

Clicking on the “event” badge next to the element provides a list of events registered to the element. Then each event can be expanded to show the code involved with the event.

That’s it for now!

 I’ll end it here, with a large amount of information detailing various commands that can be used in the browser’s console output or with JavaScript. This isn’t everything that is possible — there’s simply too much to cover. In some cases, each browser has its own capabilities or utilities that can be leveraged. We looked at the bulk of what we might find in Chrome and Firefox, but there’s likely more out there. Plus, there will always be new features introduced in the future. I invite you to dig deeper to discover more ways to leverage browser DevTools for your coding projects.


A Guide to Console Commands originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-guide-to-console-commands/feed/ 7 303621
Graphical User Interfaces for Git https://css-tricks.com/graphical-user-interfaces-for-git/ https://css-tricks.com/graphical-user-interfaces-for-git/#comments Tue, 02 Jul 2019 22:41:22 +0000 http://css-tricks.com/?p=288109 Git is command-line-driven software, but that doesn’t mean you have to use the command line to make it work. There are lots of options! Some of the deepest programmer nerds I know prefer to use GUIs for Git (Graphic…


Graphical User Interfaces for Git originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Git is command-line-driven software, but that doesn’t mean you have to use the command line to make it work. There are lots of options! Some of the deepest programmer nerds I know prefer to use GUIs for Git (Graphic
User Interface, or you know, software you can see things and click stuff), and some near pure-designers I know prefer working with the command line for Git. Swear to Git.

Lemme round up what look like the major players for Git GUIs these days.


Tower

I’ve used Tower for ages and it’s the one used the most. I’m not sure the exact release dates of all these, but I feel like Tower was an early player here. They’ve been around a long time and continuously improve, which I always respect.



Fork

It’s free and actively developed, incredibly.



GitHub Desktop

This is a 2.0 of the original GitHub Desktop. I had some gripes with the 1.0 version in that its terminology was weird (to me) and seemed to vastly deviate from Git, which was more confusing than it was worth (again, to me). This version cleans most of that up. It’s deeply integrated into GitHub so it makes GitHubb-y things (e.g. pull requests) feel like first-class citizens, but it will still happily work with any Git repo.



GitKraken

I’m pretty intrigued by this one. Upgrading (monthly cost) to get the in-app merge conflict tool seems worth it, but you also have to upgrade to access private repos. It seems highly feature-rich, but I think my favorite part is the dark-with-rainbow-accent-colors theme.



Sourcetree

You might be compelled by Sourcetree if you’re a big Bitbucket user because they are both Atlassian products. I know it works for any Git repo though. I imagine there is some smooth Bitbucket integration stuff with this, similar to the GitHub/GitHub Desktop connection.



Coda

You don’t really think of Coda as a version control tool (it’s more of a direct-to-FTP thing), and even though I’d argue the support for it is fairly half-baked, it does work! Seems likely the next evolution of Coda will address this.



VS Code

Having version control right in your IDE like this, to me, feels like kind of a tweener between GUI and CLI. There are a lot of features here, but it’s not really a full-blown GUI to me, but you’ve got a terminal built in right there so it almost encourages that. A lot of Git usage is pretty basic pulling, committing, and pushing — so having this right within the app is kinda sweet.

(I imagine there are lots of other IDEs that offer version control features. PHPStorm, etc.)



Atom

I don’t quite know what to make of Atom. It’s certainly pretty popular, but Atom is GitHub’s thing, and now Microsoft owns GitHub, and Microsoft has VS Code which is a direct competitor with way more momentum. Plus GitHub has GitHub Desktop which also seems to have momentum. Still, hey, it’s a great integration and if you love it you love it.



Sublime Merge

From the makers of Sublime Text! Like Sublime Text, you can use it kinda free forever but in this case, for now, you just pay if you want the dark theme.



Gitbox

It looks like the last update was 7 years ago, but it also has great reviews. It looks like you don’t see code difs within the app itself which is a little weird (the “view dif” stuff didn’t work for me, anyway). But functionally otherwise, seemed perfectly working to me.



TortoiseGit

Windows only here so I can’t give even a brief review, but they have a ton of screenshots here.



SmartGit

Cross-platform, and looks very full-featured (even stuff like a file merge tool for conflicts). The UI snob in me squinches my face a little looking at it.



GitUp

Looks to be very big on the visual graphing of git branches and giving you control over all the nodes and what you do with them. I’ll embed their demo video here which shows all that off:

Git Extensions

Windows only here so I can’t give even a brief review, but several people mentioned it so it feels like it’s got legs to me. The 4000+ stars on GitHub (it’s open-source!) is very cool too.




Graphical User Interfaces for Git originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/graphical-user-interfaces-for-git/feed/ 24 288109
Old Timey Terminal Styling https://css-tricks.com/old-timey-terminal-styling/ https://css-tricks.com/old-timey-terminal-styling/#comments Tue, 11 Sep 2018 15:32:16 +0000 http://css-tricks.com/?p=275830 I saw a little demo the other day called HTML5 Terminal. It has some functionality, but it seems like it’s just kinda for fun. That said, I loved the aesthetic of it! Blurry monospace type with visible monitor lines …


Old Timey Terminal Styling originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I saw a little demo the other day called HTML5 Terminal. It has some functionality, but it seems like it’s just kinda for fun. That said, I loved the aesthetic of it! Blurry monospace type with visible monitor lines and a glowing green background — nice!

Let’s re-create that, bit-by-bit.

A radial gradient is perfect for the glowing green background:

body {
  background-color: black;
  background-image: radial-gradient(
    rgba(0, 150, 0, 0.75), black 120%
  );
  height: 100vh;
}

I’m so used to using <pre><code> to display space-formatted text (like code), but you could say terminal text isn’t always code, so I like the use of <pre><output> here.

Might as well use a nice monospace font:

body {
  ...
  color: white;
  font: 1.3rem Inconsolata, monospace;
}

The text on the demo appears a bit blurry. We could go with a slight filter like filter: blur(0.6px);, but it seems blurring that way comes out either too blurry or not blurry enough. Let’s try using text-shadow instead:

body {
  ...
  text-shadow: 0 0 5px #C8C8C8;
}

Now on to those monitor lines! Ideally, they should sit on top of the text, so let’s use an ::after pseudo-element that’s absolutely positioned over the whole area and use a repeating linear gradient for the lines (because it’s always nice to avoid using images if we can):

body::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: repeating-linear-gradient(
    0deg,
    rgba(black, 0.15),
    rgba(black, 0.15) 1px,
    transparent 1px,
    transparent 2px
  );
}

You can see these faint black lines on white here:

And then over our original green burst background to complete the look:

It’s a nice touch to add a fun selection style and remove the blurring for clarity when selecting:

::selection {
  background: #0080FF;
  text-shadow: none;
}

Complete demo:

See the Pen Terminal Output by Chris Coyier (@chriscoyier) on CodePen.


Old Timey Terminal Styling originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/old-timey-terminal-styling/feed/ 16 275830