Storehouse.Variants (Storehouse v0.2.0)

Copy Markdown View Source

A file's other sizes and formats, by preset: a photograph's thumbnail, its sizes for a srcset, its WebP. Only the presets' sizes are ever made, so no one can have the server render at a size they choose.

config :storehouse, Storehouse.Variants,
  presets: [
    thumb: [width: 320, height: 320, fit: :cover],
    photo: [widths: [640, 960, 1280, 1920, 2560], format: [:webp, :original]]
  ]

Storehouse.variant_url(file, :thumb)
#=> "/variants/2026/10/0b6…/harbour.jpg/thumb-320-5f3a91c2"
Storehouse.variant_url(file, :photo, width: 1000)
#=> "/variants/2026/10/0b6…/harbour.jpg/photo-1280-e04b77d1"

What renders them is a Storehouse.Renderer: Storehouse.Renderer.Vix (libvips, in the app; the default when Vix is loaded), which stores each variant beside its original once, served by Storehouse.Variants.Plug; or Storehouse.Renderer.Imgproxy, whose URLs an imgproxy behind a CDN renders; or the app's own. See the Variants guide.

Configuration

  • :presets - a keyword list of presets, by name (an atom of lowercase letters, digits and _). See "Presets".
  • :renderer - a module of Storehouse.Renderer, or {module, options}. The default is Storehouse.Renderer.Vix, when Vix is loaded.
  • :url - where Storehouse.Variants.Plug serves variants, for a renderer without URLs of its own. The default is "/variants".
  • :prefix - the prefix of the stored variants' keys: "<prefix><original key>/<preset>-<width>-<fingerprint>.<extension>", so deleting an original deletes its variants with one prefix. The default is "variants/".
  • :timeout - how long a caller waits for a variant another is making, in milliseconds. The default is 60 seconds.

Each can be given to a call, for an app with variants of several kinds.

Presets

  • :width - the width, in pixels; or :widths, several, a ladder for a srcset. A width past the original's gives the original's: a variant is never enlarged.
  • :height - the height of the box, with :width; or :aspect, {width, height}, the box's proportions at every width. The default is any height.
  • :fit - :inside (the default: scaled to fit the box) or :cover (cropped to fill it, which needs a height).
  • :focus - what a crop keeps: :centre (the default), :attention, :entropy or {x, y}, a point in fractions from the top left.
  • :format - :original (the default: the original's, or JPEG for a format browsers do not show), :webp, :jpeg, :png, :avif, :gif; or a list, in order, of which the first the browser takes is given, by its Accept, the last to every browser: [:webp, :original].
  • :quality - 1 to 100. The default is the renderer's.
  • :profile - :srgb (the default: converted to sRGB) or :keep (the photograph's colour profile kept, for wide-gamut screens).
  • :metadata - :none (the default: none of the original's EXIF, where it was taken least of all) or :all.
  • :background - "#rrggbb", under transparency a format without it cannot keep. The default is white.
  • :options - a renderer's own options, by renderer: options: [imgproxy: "sh:0.3"].

A preset changed gives its variants new URLs and keys: a CDN's copy, kept for a year, is never of an older preset. The variants of the old one stay until their original is deleted, or the prefix is.

Summary

Functions

file's variant of preset, found or made: {:ok, %Storehouse.File{}}.

The format of a preset's formats a browser is given, by its Accept header: the first it takes, else the last.

The prefix of the variants of the file with key, or of the files under a prefix: deleting it deletes them.

The URL of file's variant of preset.

Functions

get(file_or_key, preset, opts \\ [])

@spec get(Storehouse.File.t() | String.t(), atom(), keyword()) ::
  {:ok, Storehouse.File.t()} | {:error, term()}

file's variant of preset, found or made: {:ok, %Storehouse.File{}}.

Made once, however many ask for it at the same time, across a cluster's nodes: the first renders it, the others wait for it ({:error, :timeout} past :timeout). Made with a renderer that renders (Storehouse.Renderer.render/4); one of URLs only gives {:error, :not_supported}. A missing original gives {:error, :enoent}.

Its :width and :height are known when it was made by this call.

Options

  • :width - as for url/3.
  • :accept - the browser's Accept header, for a preset of several formats; or :format, one of them. The default is the last.
  • :adapter - the store, of the original and of its variants.
  • The configuration's, for this call.

negotiate(formats, accept)

@spec negotiate([atom()], String.t() | nil) :: atom()

The format of a preset's formats a browser is given, by its Accept header: the first it takes, else the last.

iex> Storehouse.Variants.negotiate([:avif, :webp, :original], "image/webp,*/*")
:webp
iex> Storehouse.Variants.negotiate([:webp, :original], nil)
:original

prefix(key, opts \\ [])

@spec prefix(String.t(), keyword()) :: String.t()

The prefix of the variants of the file with key, or of the files under a prefix: deleting it deletes them.

url(file_or_key, preset, opts \\ [])

@spec url(Storehouse.File.t() | String.t(), atom(), keyword()) :: String.t()

The URL of file's variant of preset.

Options

  • :width - the width wanted: the preset's that is as wide, or the next one up, or its widest. The default is its widest.
  • :focus - what a crop keeps, for this file (a photograph's own focus point): only with a renderer of URLs, which signs it.
  • The configuration's (:presets, :renderer, :url), for this call.