Alpine.js loaded 2 times

I have been trying to solve this issue with Alpine in Phoenix 1.6 LiveView:

and it now works.

But the problem is that I am loading alpine.js twice.

Once inside the <head></head> in root.html.heex file and the second time importing like:

<script defer src="https://unpkg.com/alpinejs@3.7.0/dist/cdn.min.js"></script>

and the second time in app.js because I have downloaded the file in the script tag and saves as alpine.js inside assets/vendor/alpine.js in the assets/js/app.js file:

// We import the CSS which is extracted to its own file by esbuild.
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
import "../css/main.css";

// Alpine
import Alpine from "../vendor/alpine";
window.Alpine = Alpine;
Alpine.start();

// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"

or as suggested by the user in the linked post like:


import { LiveSocket } from "phoenix_live_view";
import topbar from "../vendor/topbar";
import Alpine from "../vendor/alpine";

// Alpine
window.Alpine = Alpine;
Alpine.start();

let csrfToken = document
  .querySelector("meta[name='csrf-token']")
  .getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
  dom: {
    onBeforeElUpdated(from, to) {
      if (from._x_dataStack) {
        window.Alpine.clone(from, to);
      }
    },
  },
});

// Show progress bar on live navigation and form submits
topbar.config({

And it works. But it is loaded twice, at least I think it is leaded twice.

The reason why I have dowloaded the file is that I don’t want to use npm in PHX1.6.

If you suggest to remove the script inside the head tags - well then it will stop working.

The same with removing the import from app.js - it will stop working.

Any idea how to solve that?

Do any of you use Alpine js without using npm? How do you setup your app.js and root.html.heex and other files than?

Now, when I think about it, perhaps it has to be like that in LiveView (and loaded twice) because of the first load static html and then only use websocket principle? Any comments on that?

Or perhaps I am importing it wrongly in app.js? And when I import it correctly, I will be able to remove the script import inside head tags in root.html.heex?

Thanks for any help.

1 Like

Don’t load any javascript off the internet. You will never know if any of the CDN site is compromised, or DNS hijacked. I’d suggest you to use npm. You don’t have to use webpack and can stick to esbuild.

So cdn can be hijacked but npm can’t?
:smiley:
No, thank you. Never will I use npm again. I hate it.
If there is no way to use Alpine with Phoenix 1.6 in LiveView then I will abandon it and go for something else or even vanilla JS but I won’t ever use npm.

So cdn can be hijacked but npm can’t?

With a CDN your users are downloading every time with no verification. With NPM, you download the package yourself once, and verify the checksum through the tool. Nothing is bullet proof, but which one do you think is safer?

There is a way to use any javascript lib in phoenix liveview; you only need to figure it out. I would assume importing the correct ES module build of Alpine from my app.js, adding the required initialization and hooks, shall do the job.

It’s possible that some resources are loaded twice in your development environment due to the way live reloads are implemented. Try running your application in prod and this behavior will probably go away.

Also, AlpineJS works very well with LiveView :slight_smile:

This isn’t quite true. You can use Subresource Integrity to make sure your users get a verified version of a given resource from a CDN.
It works by adding an SHA384 hash to the tag that includes the resource.

This is what it would look like for the most recent version of Alpine.

<script src="https://unpkg.com/alpinejs@3.7.0/dist/cdn.min.js" integrity="sha384-RdtRRt1iDhfyBdfZIXH83/BU+0bhkpr746r/OHT0eRgZB0W7gD8bDfabHV42kyyA" crossorigin="anonymous"></script>

You can easily generate these hashes here: https://www.srihash.org/

4 Likes

The authors care about Phoenix. :+1:

I don’t use alpine myself, but there were people posting about their setup before, such as:

Its as easy as

npm i alpinejs

init

window.Alpine = Alpine
...
Alpine.start() // you may want to do this after other things have initialized

and then hook it in the live socket like

let liveSocket = new LiveSocket('/live', Socket, {
  ...
  dom: {
    onBeforeElUpdated(from, to) {
      if (from._x_dataStack) {
        window.Alpine.clone(from, to)
      }
    },
  },
})

change config.exs esbuild config to target es2017.

2 Likes

I DO NOT WANT TO USE NPM!!!

Is it clear now? Thank you.

I want to download a file from CDN and save it as alpine.js and use it like you suggest:

import topbar from "../vendor/topbar";
import Alpine from "../vendor/alpine";

window.Alpine = Alpine;
Alpine.start();

let csrfToken = document
  .querySelector("meta[name='csrf-token']")
  .getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
  dom: {
    onBeforeElUpdated(from, to) {
      if (from._x_dataStack) {
        window.Alpine.clone(from, to);
      }
    },
  },
});

But it doesn’t work at all unless I add this line in between the head tags in root.html.heex file:

   <script defer src="https://unpkg.com/alpinejs@3.7.0/dist/cdn.min.js"></script>
  </head>

And now it works great. However it’s probably loaded twice.

The insane thing is that if I remove either one of these scripts Alpine is not working on my site. I don’t get it at all.

By the way, in VS Code I am getting this error when I hover over Alpine in window.Alpine:

the message was for Derek, and you are free to completely ignore it. And maybe take some time off coding. Maybe clean your car or do some knee bends. :wink:

3 Likes

I finally solved it. And I haven’t used any npm install bullshit as you both suggested.
The problem was in esbuild/Phoenix and not in Alpine.

Not for you, who instead of helping were attacking and bullying me by trying to make me look bad, but for others who because of the lack of up to date docs will struggle, I will offer this solution:

If you hate node package manager and you wanted to escape it and not to go back to it do this:

  1. download the JavaScript file here: https://unpkg.com/alpinejs@3.7.0/dist/cdn.min.js
  2. rename this file to alpine.js or add the version number to keep track
  3. put this file inside assets/vendor folder (where the topbar.js file is)
  4. in assets/js/app.js file make these changes:
import topbar from "../vendor/topbar";
import Alpine from "../vendor/alpine"; <-- add this line

then:

import Alpine from "../vendor/alpinejs_3_7_0";

window.Alpine = Alpine; <-- add this line
Alpine.start(); <-- add this line

let csrfToken = document

and:

let csrfToken = document
  .querySelector("meta[name='csrf-token']")
  .getAttribute("content");

let hooks = {}; <-- add this line
let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
  hooks: hooks, <-- add this line
  dom: { <-- add this line
    onBeforeElUpdated(from, to) { <-- add this line
      if (from._x_dataStack) { <-- add this line
        window.Alpine.clone(from, to); <-- add this line
      } <-- add this line
    }, <-- add this line
  }, <-- add this line
});
  1. open config/config.exs file and find there:
    --target=es2016 and change it to --target=es2017
  2. restart the server
  3. Alpine is now working
2 Likes

Interesting. The only thing new is this, so esbuild’s es2016 transpiling was the culprit?

quote from the guide you posted above:

Alpine ^3.5.0 now targets es2017 so make sure you update config/config.exs . Check the last commit in the repo for a quick fix.

Thanks. I actually did not read the guide carefully. I am using es2017 too, but for a different reason: es2016 does not even support async function, which sucks badly.

btw if you want to express a diff, the forum system has a facility for that: put the diff word after the triple backticks like so: ```diff

So your code can look like this:

let csrfToken = document
  .querySelector("meta[name='csrf-token']")
  .getAttribute("content");

+ let hooks = {};
let liveSocket = new LiveSocket("/live", Socket, {
  params: { _csrf_token: csrfToken },
+  hooks: hooks,
+  dom: {
+    onBeforeElUpdated(from, to) {
+      if (from._x_dataStack) {
+        window.Alpine.clone(from, to);
+      }
+    },
+  },
});
5 Likes