kccarter
Can you minimize Javascript inside a heex template?
I’m experimenting with different ways to incorporate a bit of Javascript into some of my pages. (Not using LiveView.)
Consider the following heex template:
<div>
<button>MyButton</button>
</div>
<script>
// Some Javascript goes in here....
</script>
Is there anywhere in the “build process” that I could minimize the Javascript that is within the script tags?
We’re aware we can put code in app.js, and even import other files into app.js, but we’re just curious if there’s a way to inline it like we have. For some pages, we’re only sprinkling a very small amount of Javascript and would rather keep that Javascript code as close to where it’s being used.
Discussions headed down the path of whether the script tag could be a functional component that, at compile time, could take the string and “call out” to something like esbuild for minification, though we have no idea if that’s possible within the Elixir build steps.
The goal here is to see if there’s any merrit in a design pattern that is mostly heex, but with a bit of JS “inlined” in the heex templates, rather than having to fallback to using Javascript files and imports / included.
Most Liked
kccarter
Inspired by this blog post showing how to compile Markdown formatted blog posts into a Module, we came up with the following for minifying Javascript. Curious if there are obvious ways we could improve this as we’re not Elixir experts.
Minification is done by the Terser library and installed as a node module inside our assets folder.
Our HTML Module:
defmodule DemoWeb.RecordsHTML do
use DemoWeb, :html
@inline_js Path.expand(__DIR__)
|> Path.join("templates_html/**/*.js")
|> Path.wildcard()
|> Enum.into(%{}, fn file ->
{output, _} =
System.cmd(
"npx",
["terser", "--toplevel", "--compress", "--mangle", "--", file],
cd: Path.join(File.cwd!(), "assets")
)
{Path.basename(file), output}
end)
embed_templates "templates_html/*"
@doc """
embed_js
"""
attr :filename, :string, doc: "The filename of the Javascript file to embed."
def embed_js(assigns) do
assigns = assign(assigns, files: @inline_js)
~H"""
<script>
<%= raw(Map.fetch!(@files, @filename)) %>
</script>
"""
end
end
A HEEX template:
<div>
<div>Something that needs JS</div>
</div>
<.embed_js filename="form.js" />
Embedding JS with the script tag directly in HEEX template was problematic. Basic code formatting wasn’t working properly, for example. Using a JS “sidecar” file could be an acceptable trade-off.
Questions:
- Are there ways we could convert this to a Macro along the lines of
embed_templates? - Can we clean up the existing block of code for
@inline_jsin anyway?
Naturally, we might want to embed local Javascript files from other Modules.
kccarter
What if we slightly relaxed our requirement of it being entirely inline and instead did something like a “Javascript sidecar” file.
Template:
form.html.heex
<div>
<button>Hello World</button>
</div>
<.inline_js 'form.js' />
Sidecar:
form.js
function helloWorld() {
console.log("Hello World");
}
And then have some custom variant of embed_templates that looks for the JS sidecar files and creates functional components out of them?
And yes, this is getting a bit into the weeds, so consider this more a discussion of what’s possible and not necessarily what’s best-practice.
kccarter
For anyone coming across this in the future, we had fun building a working prototype that came close to achieving our original idea with the caveat that the JavaScript was in a sidecar file.
But in the end, it became obvious that the obscurity of such an implementation just wasn’t worth it.
Our JavaScript is now where it should be and our heex templates now just invoke a single line of inline JavaScript to pass a DOM element to an “enhancing” function, somewhat akin to how JS frameworks render a root component into a given element.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








