axelson

axelson

Scenic Core Team

Is it possible to have page-specific javascript with LiveView?

One of my LiveView view’s has a very heavy javascript dependency so I want to avoid loading it for every page on the site, while still using live_redirect or similar between the two (avoiding a full-page load). However, I’m running into
https://github.com/patrick-steele-idem/morphdom/issues/178
This means that any <script> tag that I put into the page will not be executed. Of course there are a plethora of work-arounds: https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml But I figured that someone else has probably tried to solve this before so I am curious what other people have tried or are using.

First Post!

Bumppoman

Bumppoman

Any chance you might be able to load it with a hook?

Most Liked

rhcarvalho

rhcarvalho

This is one of the two best threads that come up from search on the topic, so reviving it with an up-to-date solution for the benefit of all.

Just like the OP, I wanted to use a heavy JS library in a single page without compromising the bundle size for the rest of the app.

Nowadays Phoenix projects use esbuild by default, and modern browsers support ESM modules and dynamic import.

To my surprise, asking Gemini 3 produced a reasonable output, hinting at the right things. In a nutshell:

  • Update the esbuild config in config/config.exs to output the bundle as an ESM module and enable code-splitting:
      --format=esm
      --splitting
  • Update the root template to load the app.js as a module:
- <script defer phx-track-static type="text/javascript" src={~p"/assets/js/app.js"}>
+ <script phx-track-static type="module" src={~p"/assets/js/app.js"}>
  • Within a JS hook, import the dependency in the mount() callback.
Hooks.MyHook = {
  async mounted() {
    await import("...")
  }
}
  • Alternatively, import the dependency within an event handler.
window.addEventListener("myapp:action-with-heavy-dependency", event => {
  import("...").then(...)
})

With that setup, esbuild should produce a small bundle for app.js, loaded on all pages, and additional files that are only imported/downloaded/executed when needed.

Resources

dbern

dbern

I’m using Webpack 4.43.0. I didn’t notice the docs are for v5, but the dynamic imports are certainly working for me.

I’m going to open-source YouMeet when it launches, so I don’t have the full repo open to browse yet, but here are some snippets that I think covers it.

Here’s an example hook to load a fancy WYSIWYG editor that’s using Vue:

hooks.Editor = {
  mounted() {
    const phoenix = this;
    const target = this.el.getAttribute('phx-target');
    const Editor = () => import(/* webpackChunkName: "editor" */ "./components/Editor.vue");
    () => import(/* webpackChunkName: "highlight" */ "./syntax-highlighter");

    new Vue({
      el: this.el,
      provide: function() {
        return {
          publish: this.publish
        };
      },
      methods: {
        publish: payload => phoenix.pushEventTo(target, "editor-update", payload)
      },
      render: createElement =>
        createElement(Editor, {
          props: {
            initialValue: phoenix.el.dataset.initialValue,
            placeholder: phoenix.el.dataset.placeholder,
          }
        })
    });
  }
};

On the LiveView EEX to load it (actually a LiveComponent but should be about the same)

<div phx-update="ignore" id="Editor-<%= @id %>">
  <div data-placeholder="<%= html_escape(@description_placeholder) %>" 
       data-initial-value="<%= html_escape(input_value(f, :description)) %>" 
       phx-target="<%= @myself %>" 
       phx-hook="Editor" 
       class="container flex h-screen m-4">
  </div>
</div>

and in my Webpack config:

// all the configs plus this:
  output: {
    filename: "[name].js",
    chunkFilename: "[name].bundle.js",  // <-- this line
    path: path.resolve(__dirname, "../priv/static/js"),
    publicPath: "/js/"
  },
// ... no optimizations key
// this may imply that you need to split code yourself.

EDIT: There should be a GIF right here, but it doesn’t seem to load? Sorry, it’s just illustrating how navigating from one LiveView without the script, and then clicking a link to another liveview does trigger the JS fetch and import.
dynamic-import-via-hook

Hopefully I didn’t miss anything!

polypush135

polypush135

What I’ve learned is that if you are using webpack you should be able to dynamically load bundles per hook.
This is how I’m currently doing it.

By using this style in importing.

let Hooks = {
  PageIndex: {
    mounted() {
      import('@mojs/core').then(mojs => {})
...

It only works if you import this way.

Where Next?

Popular in Questions Top

lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 131117 1222
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42716 114
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

We're in Beta

About us Mission Statement