kccarter

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

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_js in anyway?

Naturally, we might want to embed local Javascript files from other Modules.

kccarter

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

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.

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&amp;query=perfume&amp;page=2, I would like to get: ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; somethi...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

We're in Beta

About us Mission Statement