elixir alpinejs on fly fail to install

I am struggling with Alpine and fly. The app works well locally and does not produce any errors.

flyctl launch produces the following:

Downloading esbuild from https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.29.tgz
#22 1.625 ✘ [ERROR] Could not resolve “alpinejs”
#22 1.625
#22 1.625 js/app.js:23:19:
#22 1.625 23 │ import Alpine from “alpinejs”

Alpine is in dependencies

when I remove the import line the fly app deploys and seems to work. However, all the css code does not as one would expect.

If you have pointers to try I would appreciate it.

1 Like

are you using npm? The default Dockerfile generated by phoenix does not include npm. If you need npm deps, you need to modify your docker file to do your node things:

# Initial setup
$ mix deps.get --only prod
$ MIX_ENV=prod mix compile

# Install / update  JavaScript dependencies
$ npm install --prefix ./assets

# Compile assets
$ npm run deploy --prefix ./assets
$ mix phx.digest

ref: Deploying with Releases — Phoenix v1.5.13

1 Like

Thank you. I will play around with this. Since fly builds the docker file I will need to figure out how to add these commands with the correct image.

1 Like

Thank you. Figure it out with your hint and your previous help to Andrew Barr at
https://andrewbarr.io/posts/removing-npm/show

I appreciate your help immensely

1 Like

hi @semdinsp could you please share the Dockerfile you used to do this successfully ? My Dockerfile generated by Fly does not have npm so the great tutorial by Andrew Barr to set up tailwind did not work for me. I’ve tried adding npm and nodejs to docker image but still end up with

#24 4.928 ✘ [ERROR] Could not resolve "alpinejs"
#24 4.928
#24 4.928     js/app.js:27:19:
#24 4.928       27 │ import Alpine from "alpinejs"
#24 4.928          ╵                    ~~~~~~~~~~
#24 4.928
#24 4.928   You can mark the path "alpinejs" as external to exclude it from the bundle, which will remove this error.```

Both tailwind and alpine are working fine in dev however i cant `fly deploy` successfully :/
thank you!!!

@johnsonlincoln What did you try in your Dockerfile? The lines to use npm are included in the file on the tutorial, they are just commented out.

Although, are you using Phoenix 1.6 with esbuild? And are you using npm for anything besides Alpine? If you are on 1.6 and only using npm for alpine, another option is to skip npm completely and just vendor the JS directly in your assets folder. This is what I do and it works fine for me locally and in prod on fly, with no changes to the vanilla Dockerfile.

  1. visit unpkg.com/alpinejs to get source code of the latest version of Alpine
  2. create a file assets/vendor/alpine.js and paste the Alpine code
  3. in assets/js/app.js add the line import Alpine from "../vendor/alpine"

That’s it!

3 Likes

Sorry. I was able to get it working.
I used mr mccords and mr Barr suggestions to get rid of npm…

https://andrewbarr.io/posts/removing-npm/show

I then added alpine to my root.html.heex file

Obviously you could download this file and put it into your directory structure.
This all seems to work and seems simpler than using npm

Your mileage will vary day by day. :slight_smile:

S

1 Like

hey! This worked !!! thank you so much!!
just for posterity:

  • using phoenix {:phoenix, "~> 1.6.10"},
  • not using npm for anything besides alpine
  • no changes Dockerfile created by Fly

thank you!!!

1 Like

thank you @semdinsp . @msimonborg 's solution worked perfectly for me!

just for my own edification though; what do you mean by adding alpine to root.html.heex , do you mean in the header as script from cdn? would you mind posting that part of your your root.html.heex file please :slight_smile: ?

i ended up just saving alpine source to assets/vendor/alpine.js and adding import Alpine from "../vendor/alpine" in app.js and all good :slight_smile:

I think it would be exactly that, following the snippet from the Alpine landing page

<script src="//unpkg.com/alpinejs" defer></script>

I started off with this but quickly switched to vendoring for a few reasons:

  1. Version control. unpkg.com/alpinejs always returns the latest version, so if you’re pulling it in on every page load eventually something could break. Vendoring locks the version until you decide to hit that url yourself to copy and replace the code
  2. Bundles everything with your response to avoid the extra http call on page load to fetch the JS
  3. Vendoring is more secure than hitting a CDN

As @semdinsp said, YMMV of course!

1 Like

Yes your explanation for my poor communication skills is exactly correct.
I added a script tag to root.html.heex to pull in alpine in the header section

I was also trying to say that if you did not like that approach (ie pulling from the web) you could install the same file ‘alpinejs’ in your project file system and pull it in from there

Here is header of root.html.heex
The body section is normal

<%= live_title_tag assigns[:page_title] || “Mwcweb”, suffix: " · Phoenix Framework" %>

2 Likes

thank you for all your help!!!

awesome, thank you!!

1 Like

@johnsonlincoln I tried to save the alpine source to assets/vendor/alpine.js and added this to my app.js file.

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)
      }
    }
  }
})

Alpine seems to load, but I get an error in the console that breaks form bindings LiveView. Removing the alpine import fixes the form binding issue, but of course, breaks alpine.

Have you experienced the same thing?

Here’s the error I get

Uncaught TypeError: import_alpine.default.start is not a function

Looks like this might be an issue with Alpine.

I removed these lines and things appear to be working fine now…

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)
      }
    }
  }
})

it’s not an issue with Alpine, you just don’t need those lines on the latest versions!

your app.js looks good :+1:

1 Like

I found myself here with a project that needed Node/NPM to install other packages in addition to Alpine. If you’re in a similar situation, you can add Node/NPM as dependencies in your Dockerfile, in between COPY assets assets and RUN mix assets.deploy. I understand these deps were recently removed from the default Dockerfile with the advent of esbuild, but should you need Node/NPM for any reason, the below advice will work.

I followed a StackOverflow answer on how to add Node to my Dockerfile using NVM:

COPY assets assets

ENV NODE_VERSION=16.17.0
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
ENV NVM_DIR=/root/.nvm
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
RUN node --version
RUN npm --version

# install npm deps before running esbuild
RUN npm install --prefix ./assets

# compile assets
RUN mix assets.deploy

You’ll also need to add curl as a dependency to your package for this installation script to work, so the RUN line at line 25 needs to be modified slightly:

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git curl \
    && apt-get clean && rm -f /var/lib/apt/lists/*_*

That being said, if you’re just using Alpine and don’t expect the JS footprint of your app to grow significantly, inlining Alpine as per @msimonborg’s answer works too, with fewer modifications required.