As I recently migrated this blog from Middleman to Bridgetown, I thought I could document one of my Bridgetown customizations: on-demand or build-time image resizing.
Notes
- In this approach, the originals are stored in the
frontend/
directory and should therefore be also accessible via the Bridgetown built-inasset_path
helper or through esbuild imports. - The resized images are stored in
src/images/resized
so that they don’t need to be processed by esbuild - The resized images contain a digest of the original (CRC32 by default) ensuring that they get reprocessed if the original changes.
- The CRC hash should be sufficient for detecting changes in the original file in a non-adversarial environment, and it is very fast to compute.
- The CRC code can be easily replaced with a more robust, though slower, cryptographic hash function if desired
- On the other hand, I’ve chosen not to add a hash of the generated image to the filename in order to avoid having to keep track of the originals and generated files in a manifest file.
- I’ve chosen not to commit the generated files to Git, but you might make a different choice based on your needs. If you do push the resized images to version control, make sure to also clean up any versions that are no longer needed.
Prerequisites and assumptions
For this tutorial, I’m assuming the following:
- Bridgetown v1.3
- Ruby 3.3
- ERB templates instead of Liquid
- ERB is going to be the default in Bridgetown 2.0
- I prefer ERB to Liquid in any case
- It should be possible to adapt these steps for Liquid templates, but I haven’t explored those details
- You’re somewhat familiar with Ruby and Bridgetown already
- You should have libvips installed
- macOS:
brew install vips
- Debian/Ubuntu:
sudo apt install libvips
- macOS:
Steps
-
If you don’t have a Bridgetown site yet, create one:
bridgetown new --templates=erb image-resizing-demo cd image-resizing-demo
-
Add the ImageProcessing gem to the
Gemfile
:gem 'image_processing', '~> 1.12', '>= 1.12.2'
-
Add a test image to
frontend/images
:mkdir -p frontend/images # You can, of course, use any image you like as your test subject… curl --output frontend/images/blue-marble.jpg \ https://www.randomerrata.com/_bridgetown/static/blue-marble-T6POMCUA.jpg
-
Create
plugins/builders/asset_helpers.rb
:require "image_processing/vips" require "zlib" # If you want to use a cryptographic digest, you can uncomment following: # # require "digest/sha2" class Builders::AssetHelpers < SiteBuilder def build helper :resized_image_path do |src, width: nil, height: nil| basename = File.basename(src, ".*") ext = File.extname(src) # Find images from the frontend directory frontend_path = Pathname.new(site.root_dir).join("frontend") image_path = frontend_path.join(src) # Calculate a digest based on the original image file # digest = Zlib.crc32(File.read(image_path)).to_s(16) # # It's also possible to use a cryptographically secure digest but # calculating it will be slower and it's not necessarily necessary for # this use case. # # To use SHA256, uncomment the following and remove the CRC digest line: # # digest = Digest::SHA256.file(image_path).hexdigest # # Put resized images inside the src/images/resized directory static_images_dir = Pathname.new(site.root_dir).join("src", "images") destination_dir = Pathname.new(static_images_dir).join("resized") # Generate the resized filename based on the original filename, digest, and dimensions resized_filename = "#{basename}-#{digest}-#{width}x#{height}#{ext}" destination_path = destination_dir.join(resized_filename) # Don't do any processing if the file already exists if !destination_path.exist? Bridgetown.logger.info "Resizing image: #{src} to #{width}×#{height}" FileUtils.mkdir_p(destination_dir) pipeline = ImageProcessing::Vips.source(image_path).resize_to_limit(width, height) pipeline.call(destination: destination_path.to_s) else Bridgetown.logger.debug "Resized image exists: #{destination_path}" end # Calculate the relative path to the resized image in the src/images folder resized_path = destination_path.relative_path_from(static_images_dir) # Make sure that Bridgetown knows about the resized image site.static_files << ::Bridgetown::StaticFile.new( site, site.source, "images", resized_path ) # Return the path to the resized image "/images/#{resized_path}" end end end
-
Add the test image to
src/index.md
:--- layout: default --- # Welcome to your new Bridgetown website. ![Blue Marble](<%= resized_image_path("images/blue-marble.jpg", width: 1024) %>) ... the rest of the file continues
-
Ignore the generated images in Git by adding
resized
directory to.gitignore
(optional):# Ignore generated images /src/images/resized
-
Start the Bridgetown server:
bin/bridgetown start
-
You should see the resized image when you visit http://localhost:4000