How to get DaisyUI and Phoenix to work?

Hello @henning

From the error npm: not found, I suspect that it’s either you don’t have npm installed in your docker environment or docker doesn’t know the path to where it’s located

Check your Dockerfile. Is there any command like RUN apt-get install -y nodejs or RUN apt-get install -y npm?

If there isn’t, add RUN apt-get install -y nodejs before the RUN npm command that gave this error

@kingdomcoder
Thanks for your feedback. I tried your suggestion and am not successful yet.

My current error looks like:

#14 0.608 npm ERR! The `npm ci` command can only install with an existing package-lock.json or
#14 0.609 npm ERR! npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or
#14 0.609 npm ERR! later to generate a package-lock.json file, then try again.
#14 0.616 
#14 0.616 npm ERR! A complete log of this run can be found in:
#14 0.616 npm ERR!     /root/.npm/_logs/2022-06-16T15_11_58_766Z-debug.log
------
Error failed to fetch an image or build from source: error building: executor failed running [/bin/sh -c npm --prefix ./assets ci --progress=false --no-audit --loglevel=error]: exit code: 1

With Dockerfile:

# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian instead of
# Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
# https://hub.docker.com/_/ubuntu?tab=tags
#
#
# This file is based on these images:
#
#   - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
#   - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20210902-slim - for the release image
#   - https://pkgs.org/ - resource for finding needed packages
#   - Ex: hexpm/elixir:1.13.4-erlang-24.3.3-debian-bullseye-20210902-slim
#
ARG ELIXIR_VERSION=1.13.4
ARG OTP_VERSION=24.3.3
ARG DEBIAN_VERSION=bullseye-20210902-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM ${BUILDER_IMAGE} as builder



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

RUN apt-get update && apt-get install -y nodejs 
RUN apt-get update && apt-get install npm -y
#RUN npm install npm@8.11.0
#RUN rm -rf /usr/local/lib/node_modules/npm
#RUN mv node_modules/npm /usr/local/lib/node_modules/npm


RUN npm --prefix ./assets ci --progress=false --no-audit --loglevel=error

# prepare build dir
WORKDIR /app

# install hex + rebar
RUN mix local.hex --force && \
    mix local.rebar --force

# set build ENV
ENV MIX_ENV="prod"

# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config

# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY priv priv

COPY lib lib

COPY assets assets

# compile assets
RUN mix assets.deploy

# Compile the release
RUN mix compile

# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/

COPY rel rel
RUN mix release

# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \
  && apt-get clean && rm -f /var/lib/apt/lists/*_*

# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

WORKDIR "/app"
RUN chown nobody /app

# set runner ENV
ENV MIX_ENV="prod"

# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/my_test_app ./

USER nobody

CMD ["/app/bin/server"]
# Appended by flyctl
ENV ECTO_IPV6 true
ENV ERL_AFLAGS "-proto_dist inet6_tcp"

Hi @henning

I suspect that your error comes from the fact that you executed RUN npm --prefix ./assets ci --progress=false --no-audit --loglevel=error before you ran COPY assets assets further down.

If you move the RUN npm... line after the COPY assets assets line, that should fix it.

The reason this is necessary is that your command to RUN npm --prefix **./assets**... requires that your assets folder, along with its package-lock.json file should have been copied over prior to this command.

That’s not the case currently in your Dockerfile.

Please, check. I hope this resolves it.

Indeed I have liftoff. Thank you very much, @kingdomcoder

1 Like

I just realized that tailwindcss-cli is working with 3rd party plugins also in the latest version. That would mean that the best way to integrate daisyui into phoenix application is:

  1. create a new phoenix application.
  2. Use phoenixframework/tailwind: An installer for tailwind (github.com) to integrate tailwindcss into the project
  3. cd assets
  4. yarn add daisyui
  5. In your tailwind.config.js file add the line plugins: [require("daisyui")],

That is it. There is no 6th step. :slight_smile:

4 Likes

For 1.7-rc1 that is

cd assets
npm i -g yarn
yarn add daisyui

tailwind config:

  • add require("daisyui") to plugins
  • optionally: add the daisyui config

A thin layer (function components) around the daisy-ui-components would be nice.
Should be easy as daisyui is css only (however they pull that off).

1 Like

Just ran into this issue:

You may need to remove the tailwind-forms plugin as it adds styles to form elements by default, which break daisyui (eg: collapse with checkbox).

1 Like

There is a fix for this issue: Tailwindcss/forms and other plugin compatibility issue (DaisyUI) · Issue #134 · tailwindlabs/tailwindcss-forms · GitHub

1 Like

Hio!! We just deployed an app to production that uses Phoenix, Tailwind, and DaisyUI!

We opted not to use Phoenix’s built-in assets manifest and phx.digest and all that. Instead, our CI system (GitHub Actions) has a job that takes our Elixir image and builds the assets and uploads them to S3 under a commit SHA path.

Then we configure local dev endpoint to use :static_url with [host: "localhost"], but prod with something like [host: "your.cdn.com", path: "$build_sha/"].

I should write a Medium post about this and become famous.

2 Likes

Anything that makes the tooling around web-projects more complicated is always welcome, so I can guarantee success here! :grin:

1 Like

That’s exactly what this setting is there for :smiley:

1 Like

LMAO! :joy:

I know! But I guess I was trying to get at not using mix phx.digest. And I love that Phoenix makes it easy for you to do this, if you so choose!

We use containers to deploy our app, so we (i.e. me) are very uptight about small image sizes. I don’t really want tailwind, esbuild, npm/yarn (much less node_modules) installed on our deployed image. That’s why we go through this whole rigmarole.

So our CI builds the Elixir app image. Then we have a CI sub-job that uses that image to build and upload static assets to CDN under a $BUILD_SHA path.

Anyway way, long story short… the people behind Phoenix are just amazing at striking a balance between ease of use and flexibility.

The default docker image phoenix creates uses a multi stage build, so all that stuff can be done done in the build image and is not included in the runner image.

3 Likes

you should, and I will help become famous so you can share the million, LoL

@LostKobrakai would like to learn more on this.

Just a heads up that the :tailwind library comes with a precompiled binary of Tailwind that does not include third-party libraries like DaisyUI. I’m not sure whether or when this changed, but to use DaisyUI, you now have to install Tailwind using npm instead. This is what worked for me:

  1. Remove the :tailwind library and its configuration from config.exs and dev.exs (the line in watchers:)
  2. Install tailwindcss and DaisyUI using npm with cd assets && npm install -D tailwindcss daisyui.
  3. Add a deploy script in your package.json. My package.json looks like this:
{
  "scripts": {
    "deploy": "NODE_ENV=production npx tailwindcss --minify --input=css/app.css --output=../priv/static/assets/app.css"
  },
  "devDependencies": {
    "tailwindcss": "^3.3.3",
    "@tailwindcss/forms": "^0.5.4",
    "@tailwindcss/typography": "^0.5.9",
    "daisyui": "^3.2.1"
  }
  1. In your mix.exs update the aliases like this:
    [
      setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"],
      "assets.setup": ["esbuild.install --if-missing"],
      "assets.build": ["esbuild default", "cmd --cd assets npm run deploy"],
      "assets.deploy": ["esbuild default --minify", "cmd --cd assets npm run deploy", "phx.digest"]
    ]

Make sure that the cmd --cd assets npm run deploy comes after the esbuild default step, because (that’s my understanding but I’ve got no clue about any of this frankly) esbuild will install your npm packages (at least locally). So, you need to run esbuild default to install the npm packages first.

Now, if you run your server with mix phx.server, it should work. At least it works for me locally and after deleting my _build and node_modules folder.

Deploying to Fly.io

Now, the next hurdle is to deploy all this do Fly (if you use them). You have to modify your Dockerfile a bit to make it work (again, I have no clue about how any of this works, but this worked for me. Suggestions and explanations are much appreciated though)

ARG ELIXIR_VERSION=1.14.4
ARG OTP_VERSION=25.3.2
ARG DEBIAN_VERSION=bullseye-20230227-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM ${BUILDER_IMAGE} as builder

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

# Install something so that you can apt-get install nodejs below
# Make sure to select the version of node you want to install.
# This one will install node v20.something
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash

# Install node and npm
RUN apt-get install -y nodejs

# A BUNCH OF OTHER STUFF

COPY assets assets

# Install TailwindCSS and DaisyUI
# I've got no clue why esbuild doesn't do this for us but here we are
RUN npm install --prefix assets

# compile assets
RUN mix assets.deploy

# MORE OTHER STUFF

I hope this helps! If you know how to install the packages without npm/node and only using esbuild I’d love to know!

4 Likes

Just a quick addendum, while the :tailwind library doesn’t include DaisyUI (or some other plugins), you can still use it by simply installing and including the plugins in your tailwind.config.js and keep the build process a little simpler that way.

The easiest way I’ve found is just adding daisyui as an npm dependency:

"dependencies": {
    "daisyui": "^3.2.1"
  }

and extending the existing(?) tailwind.conig.js file plugin section with require("daisyui")

That way, we don’t need an extra deploy script and can stay closer to the phoenix defaults.

2 Likes

You are overcomplicating things. I just tested this with a fresh Phoenix 1.7.7 project:

Setup DaisyUI

  • Create project:

    • mix phx.new your_project
  • Initialize an npm project in the directory assets/:

    • cd assets && npm init -y
  • Add npm dependency daisyui in the directory assets/:

    • cd assets && npm i -D daisyui@latest
  • Add daisyUI to Tailwind plugins list (in assets/tailwind.config.js):

    • require("daisyUI")
  plugins: [
+    require('daisyui'),
    require("@tailwindcss/forms"),

  • DaisyUI is now installed! Let’s try an example component:

lib/your_project_web/controllers/page_html/home.html.heex

    <h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
      Phoenix Framework
      <small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
        v<%= Application.spec(:phoenix, :vsn) %>
      </small>
    </h1>

+    <div class="btn btn-primary">
+      Hello daisyUI!
+    </div>

    <p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900">
      Peace of mind from prototype to production.
    </p>

It worked (for me, at least :grin:)!

At this point, daisyUI is installed and everything should be working as expected.


Now for the deployment:

Deploy to Fly.io

  • Create Fly.io app:

    • ‘flyctl launch’
  • Fix issues with the default Dockerfile generated when we ran fly launch:

    • Use apt-get to install nodejs and npm
    • Install our project’s npm dependencies
FROM ${BUILDER_IMAGE} as builder

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

COPY priv priv

COPY lib lib

COPY assets assets
 
+# install npm dependencies
+RUN cd assets && npm install
+
# compile assets
RUN mix assets.deploy

Bonus: Prevent daisyUI from spamming the console when tailwind.config.js is modified

By default, daisyUI will print a message like this to the console whenever tailwind.config.js is modified:

🌼 daisyUI 3.5.0 https://daisyui.com
╰╮
 ╰─ ✔︎ [ 2 ] themes are enabled. You can add more themes or make your own theme:
      https://daisyui.com/docs/themes

    ❤︎ Support daisyUI: https://opencollective.com/daisyui

That’s annoying. Let’s disable that:

assets/tailwind.config.js

module.exports = {
  content: ["./js/**/*.js", "../lib/*_web.ex", "../lib/*_web/**/*.*ex"],
+  daisyui: {
+    logs: false,
+  },
  plugins:  [

Bonus: Remove tailwind/forms to avoid conflicts with daisyUI form components

The Tailwind forms package will cause conflicts with daisyUI forms. Let’s get rid of it:

assets/tailwind.config.js

  plugins: [
    require("daisyui"),
-    require("@tailwindcss/forms"),
    require("@tailwindcss/typography"),
9 Likes

Thanks for the suggestion. That’s what I had at first but I always got the error “Cannot find “taildwindcss/plugin”. When I removed the DaisyUI plugin from my tailwind.config.js, everything worked fine. But when I added it, it broke. That’s why I went down the npm route.

1 Like