axelson
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.
Most Liked
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.exsto output the bundle as an ESM module and enable code-splitting:
--format=esm
--splitting
- Update the root template to load the
app.jsas 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
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.
![]()
Hopefully I didn’t miss anything!
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.
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








