Embed images as Data URLs

A demo of how to customize embeds/files to generate data URLs instead of raw constants instead.

It uses the file_type Elxir library to detect the mime types of files, so it requires Elixir to be installed.

The img/lucypride.png is a downscaled PNG based on the original SVG from the the Gleam website repository.

You can check out the full example project here.

Example source code

Running this project will produce a images/lucy.gleam module!

NOTE: In a real project, you would add a seperate build path dev dependency. This avoids issues when regenerating files that your project already depends on.

import embeds/files
import embeds/generate
import gleam/bit_array
import gleam/io
import gleam/option
import gleam/result
import gleam/string

pub fn main() {
  let result =
    files.generate(files.Options(
      // where to get files from?
      sources: [
        files.Source(
          // take all files from ./img (relative to the project root)
          src: "./img",
          // generate a module called "images.gleam"
          module: "images",
          // respect the directory structure
          flatten: files.DontFlatten,
          // regardless of how deep the directory tree is
          max_depth: option.None,
          // and filter out hidden files
          filter: files.default_filter,
        ),
      ],
      // how to convert a file into Gleam source code?
      print: fn(path, data) {
        // skip files if we can't detect the mime type,
        // generate a Gleam constant otherwise
        generate.skip_or_const({
          use #(_extension, mime_type) <- result.map(get_file_type(path))
          // build a data url
          let bits = generate.data_to_bits(data)
          let base64 = bit_array.base64_url_encode(bits, True)
          let data_url = "data:" <> mime_type <> ";base64," <> base64
          // convert the string to Gleam source code
          string.inspect(data_url)
        })
      },
      header: generate.default_header,
      // Always regenerate
      force: True,
    ))

  case result {
    Ok(Nil) -> Nil
    Error(errs) -> io.println_error(generate.describe_errors(errs))
  }
}

// -- EXTERNALS

fn get_file_type(path: String) -> Result(#(String, String), Nil) {
  // make sure we throw away the Elixir error value
  do_get_file_type(path) |> result.nil_error
}

// we use the file_type elixir library to detect mime types.
// NOTE: the error type is wrong here, be careful!
@external(erlang, "Elixir.FileType", "from_path")
fn do_get_file_type(path: String) -> Result(#(String, String), Nil)
Search Document