Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Data URI function #20

Merged
merged 4 commits into from
Dec 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
erlang 20.2.2
elixir 1.6.1
erlang 20.2.2
Copy link
Collaborator Author

@dbernheisel dbernheisel Dec 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I flipped the order because there's a bug in asdf-erlang where the install processes ends in a non-0 code (even though it successfully installed), which causes asdf to stop installing the rest of the tools. Moving it to the end will avoid the issue.

20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ name. For example, The WidgetsController#show action would produce:

`widgets widgets-show`

### Data URI

To inline an image, you may use the `Brady.data_uri/1` function. Pass in the
path relative to `priv/static` and Brady will read the file, base64 encode it, and
return the data uri that is compatible to use within an `<img>` tag.

For example:

`<%= img_tag(Brady.data_uri("images/placeholder.gif"), alt: "Celery Man" %>`

It's not recommended to inline images that are more than a few kilobytes in
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

size. By default, Brady will emit a warning when inlining an image more than
2kb. You can configure this yourself with Mix config:

```elixir
config :brady,
otp_app: :my_app,
inline_threshold: 10_240
```

### Inline SVG

The inline_svg function works by passing in your SVG file name and, optionally,
Expand Down
40 changes: 40 additions & 0 deletions lib/brady.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule Brady do
alias Phoenix.Controller
require Logger

@doc """
Returns the controller name and controller-action name as a lowercase,
Expand Down Expand Up @@ -38,6 +39,45 @@ defmodule Brady do
end
end

@doc """
Encodes an image to base64-encoded data uri, compatible for img src attributes. Only recommended
for files less than 2kb. This threshold is configurable with mix config:

config :brady, inline_threshold: 10_240

Ex:
Brady.data_uri("placeholder.gif")
# => "data:image/gif;base64,iVBORw0KGgoAAAA"
"""
def data_uri(path) do
app_dir = Application.app_dir(Application.get_env(:brady, :otp_app))
base64 =
[app_dir, "priv/static", path]
|> Path.join()
|> Path.expand()
|> File.read!()
|> Base.encode64()
|> maybe_warn_about_size(path)

mime = MIME.from_path(path)

"data:#{mime};base64,#{base64}"
end

defp maybe_warn_about_size(base64, path) do
limit = Application.get_env(:brady, :inline_threshold, 2048)

if String.length(base64) > limit do
Logger.warn("""
Warning: The file "#{path}" is large and not recommended for inlining in templates. Please reconsider inlining this image, or increase the inline threshold by setting:

config :brady, inline_threshold: size_in_bytes
""")
end

base64
end

defp render_with_options(markup, []), do: {:safe, markup}
defp render_with_options(markup, options) do
markup
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ defmodule Brady.Mixfile do
{:earmark, "~>0.1", only: :dev},
{:ex_doc, "~> 0.11", only: :dev},
{:floki, "~> 0.13"},
{:phoenix, "~> 1.2"},
{:mime, "~> 1.2"},
{:phoenix, "~> 1.2"}
]
end

Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.11.4", "a064bdb720594c3745b94709b17ffb834fd858b4e0c1f48f37c0d92700759e02", [:mix], [{:earmark, "~> 0.1.17 or ~> 0.2", [hex: :earmark, optional: true]}]},
"floki": {:hex, :floki, "0.13.1", "b3b287e02914cb41a66285071dade287165ed1915ab07903e18fb454fe961bad", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, optional: false]}]},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], []},
"phoenix": {:hex, :phoenix, "1.2.0", "1bdeb99c254f4c534cdf98fd201dede682297ccc62fcac5d57a2627c3b6681fb", [:mix], [{:cowboy, "~> 1.0", [repo: "hexpm", hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [repo: "hexpm", hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [repo: "hexpm", hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [repo: "hexpm", hex: :poison, optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.0", "c31af4be22afeeebfaf246592778c8c840e5a1ddc7ca87610c41ccfb160c2c57", [:mix], [], "hexpm"},
Expand Down
40 changes: 40 additions & 0 deletions test/brady_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule BradyTest do
use ExUnit.Case
import ExUnit.CaptureLog
alias Plug.Conn
doctest Brady

Expand Down Expand Up @@ -87,4 +88,43 @@ defmodule BradyTest do
~s(<svg class="foo" data-role="bar" height="100" width="100"><desc>This is a test svg</desc><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"></circle></svg>)}
end
end

describe "data_uri/1" do
test "it returns a base64 encoded string of the given image" do
image = test_asset_path("test/support/test-small.png")

result = Brady.data_uri(image)

assert result == "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="
end

test "it emits a warning when the file is more than 2kb by default" do
path = test_asset_path("test/support/test-large.png")

assert capture_log(fn ->
Brady.data_uri(path)
end) =~ """
Warning: The file "#{path}" is large and not recommended for inlining in templates. Please reconsider inlining this image, or increase the inline threshold by setting:

config :brady, inline_threshold: size_in_bytes
"""
end

test "it does not emit a warning when the file is less than configured inline threshold" do
Application.put_env(:brady, :inline_threshold, 99999999)
path = test_asset_path("test/support/test-large.png")

refute capture_log(fn ->
Brady.data_uri(path)
end) =~ """
Warning: The file "#{path}" is large and not recommended for inlining in templates. Please reconsider inlining this image.
"""

Application.delete_env(:brady, :inline_threshold)
end
end

defp test_asset_path(path) do
"../../../../../../#{path}"
end
end
Binary file added test/support/test-large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/support/test-small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.