Add page-specific js files on Phoenix 1.7

Hey,
I’m trying to have some javascript to only load on a certain pages; specifically I’m trying to load code that operates with stripe.js bindings only on a page with Stripe Checkout information, rather than loading the js file everywhere.

I’ve found some old tutorials from the olden days when Phoenix had views, but with the new organisational model from 1.7 I can’t seem to crack it.

NB: this is not liveview specific.

Does anyone have some blueprint I can follow to get this done?

1 Like

There is the hardway and the easy way.

The hardway involves creating some injection of CSS / JS assigns into the app root template.

If you are only interested in JS, there is an easy way using Hooks and esbuild lazy loading.

Imagine you have an admin route, you mount the hook at the template root, let’s call it phx-hook=“Admin”.

Then in your admin Hook:

export const Admin = {
  mounted() {
      import("../admin.js").then(() => {});
  },
};

I’m assuming you are using the default esbuild config in a greenfield Phoenix 1.7 project, so in your config.exs you need to add the splitting option as well as tell esbuild to use the ESM format.

# Configure esbuild (the version is required)
config :esbuild,
  version: "0.14.41",
  default: [
    args:
      ~w(js/app.js --bundle --target=es2017 --splitting --format=esm --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

When visiting the route esbuild should dynamically load that chunk of code that you lazy loaded in the Phoenix JS Hook.

7 Likes

Personally I’m interested to know if/how it can be done with LiveView (since the template is rendered outside of it, if I understood the work done for favicons?)

Hooks work outside of LiveViews, so the solution I have is not specific to LiveView and it will work on traditional controller views.

Yes it can be done to work with LiveView.
BeaconCMS uses something like what I described above as the “hard” solution:

1 Like

What about CSS though?

For that the only solution I’ve found is the using the assigns I described above.

We do use this solution in the website for my company (closed source sorry):

This on root.html.heex template:

2 Likes

Not sure if that’s actually take into account assign changed while you’re navigating with LiveView though? I see @BartOtten created GitHub - BartOtten/phoenix_live_head: HTML Head element manipulation for Phoenix Live View to power the favicon lib (which uses JS hooks)

Our solution is a bit naive and since the website is mostly traditional views it works well, for heavy LiveView, can’t say, but I think the BeaconCMS solutions works with LiveView, but not sure.

I do think this would be something nice to have by default on Phoenix, Live Style and Script injection, even though like I showed above, Scripts are pretty easy, styles not so much.

1 Like

Right, and btw if phoenix_live_head doesn’t yet support dynamic scripts it’d be useful to add your solution to it so it’s all in one place even if not upstream.

Thanks so much @greven, this helped me import MapLibre without causing my app.js file to grow to 5mb! Some additional notes for anyone who stumbles onto this thread:

  • Update the app.js script tag; change type="text/javascript" to type="module"
  • Wherever you import your hooks, add:
    const { MyHook } = require("./my-hook");
    Hooks.MyHook = MyHook;
2 Likes