Liam_or

Liam_or

Hello Everyone! :blush:

I’m currently exploring tailwind and its ability to create custom utilities and animations. I don’t know if i’m wrong here, so please correct me if so, but it seems that phoenix is not using the JIT (Just In Time Engine) within Tailwind. Or perhaps my understanding of the JIT engine is flawed.

I’m creating my custom animations in the tailwind.config.js file as outlined in the Tailwind docs animation - Transitions & Animation - Tailwind CSS. I then use these inline within the html tags inside my LiveView.
It was my understanding that to avoid a large app.css file, the JIT engine processes and sends the css only when the user is on the page where that inline styling resides. However, even when on the landing page the app.css along with all the custom animations (and other TW utilites) are present and being shipped to the client even though they are not interacting with or on the page where those animations are being used. This concerns me because I plan on making many custom animations and I neither want to ship a giant app.css file nor let the user see my custom animations unless auth’d and on that specific page.

If anyone can enlighten me as to why this is, or highlight why i’m wrong and steer me in the right direction on this one, it would be greatly appreciated!

Many thanks,

Liam : )

Showing Posts 1 to 8

tcoopman

tcoopman

Tailwind is creating one css file for your whole project. It doesn’t create separate bundles for every file.

The css file should also be really small even if you use it accross pages. You can read more about it here: Optimizing for Production - Tailwind CSS

cmo

cmo

There is a project, twind, that ships the compiler instead of the CSS and compiles on the fly.

thomas.fortes

thomas.fortes

This is not necessarily true, is pretty easy to split files, I do it for separate admin and main site css bundles (also do it with esbuild which is dramatically more impactful since js libs can make the bundle grow up very fast).

@Liam_or what tailwind JIT does is inspect your source files for tailwind classes and generate a css file on the fly, but this is during development only, when deploying you run tailwind minifying it and then deploy the css generated file.

Also, css files can’t be obscured, every major modern browser has introspection tools that allow you to see exactly which properties are being applied to any element in the page, if you want to restrict a logged out user of the animations you should handle this in the logic of your code, not in your css.

tcoopman

tcoopman

You mean there are workarounds?
Tailwind only produces one css file. So either you’re using multiple tailwind configs or you use esbuild to split the CSS (which I don’t know if it can?). Or I’m missing something else?

thomas.fortes

thomas.fortes

You’re right, multiple tailwind configs (and esbuild ones, where the admin loads everything all at once but the main bundle for the app load big dependencies on demand).

# config.exs
config :tailwind,
  version: "3.3.2",
  default: [
    args: ~w(
      --config=tailwind.config.js
      --input=css/app.css
      --output=../priv/static/assets/app.css
    ),
    cd: Path.expand("../assets", __DIR__)
  ],
  admin: [
    args: ~w(
      --config=tailwind-admin.config.js
      --input=css/admin-app.css
      --output=../priv/static/assets/admin-app.css
    ),
    cd: Path.expand("../assets", __DIR__)
  ]

# dev.exs
  watchers: [
    tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]},
    tailwind: {Tailwind, :install_and_run, [:admin, ~w(--watch)]}
  ]

# aliases in mix.exs
 "assets.deploy": [
        "tailwind default --minify",
        "tailwind admin --minify",
 ]

IIRC this is all that is needed.

Liam_or

Liam_or OP

Hello! Thanks so much for all your comments. I Really appreciate you sharing your knowledge on this :blush:

@thomas.fortes how do you conditionally serve those tailwind configs so that they are only served to either an admin or a default user? Could you also go into a little bit more detail about how you conditionally serve esbuild bundles? I would like to do a mini experiment with this approach in a LiveView app.

I would love for phoenix LiveView to one day have easy code splitting and bundle serving like how SveltKit uses vite to only serve JS and CSS when needed.

thomas.fortes

thomas.fortes

I don’t, I use different root layouts for the admin dashboard and the frontend and include the correct css in each layout, if I really had to serve different files to the same root layout I would probably use a Plug or on_mount to know if the user is an admin or a default user and serve it conditionally like any other conditional.

As for the esbuild, in config.exs:

config :esbuild,
  version: "0.18.0",
  default: [
    args: ~w(js/app.js js/video-player.js
      --chunk-names=chunks/[name]-[hash] --splitting --format=esm --bundle --target=es2017
      --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

You can see that I’m passing two files, app.js and video-player.js to the esbuild command, then in the video-player.js you can import your big deps normally and import it dynamically in a hook in the app.js file.

// app.js
Hooks.VideoPlayer = {
    mounted() {
        import("./video-player").then(
            ({ setupVideoPlayer }) => {
                setupVideoPlayer(this.el);
            }
        )
    }
}

This way the big video player dependency will be loaded only when you hit a page that uses the hook.

Liam_or

Liam_or OP

Awesome! :blush: Thanks @thomas.fortes, really appreciate you sharing your knowledge.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews