Tailwind + Esbuild for umbrela applications

Hi,

I’ve been trying to setup an umbrella application, that contains 3 apps.

My current big question is how to setup esbuild and tailwind for this. Do I need to create 3 of these in config.exs that point to each one of the apps? This one is pointing to dashboard_app.

config :esbuild,
  version: "0.13.10",
  default: [
    cd: Path.expand("../apps/dashboard_app/assets", __DIR__),
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

Same thing for tailwind. Do I need 3?

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

What is the correct approach. My current problem is that it does not work with all 3 configs… I always need to comment the 2 apps esbuild+tailwind configs that I’m not working at the moment, to make it work for the one I’m building… looks like some race condition of sorts.

Any help from people that have umbrella apps?

thanks

Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns] [dtrace]
Elixir 1.13.4 (compiled with Erlang/OTP 24)
phoenix 1.6.11

I don‘t think you can use the default arguments if you don‘t use a single set of arguments. You‘d want to explicitly provide the arguments in each apps watcher configuration and asserts.deploy mix task separately instead.

1 Like

Yes you need to set 3 different profiles. You can use the default profile for your main app. Here is a working example with two apps: front (the main) and admin.

config :esbuild,
  version: "0.14.29",
  default: [
    args: [
      "js/app.js",
      "--bundle",
      "--target=es2017",
      "--outdir=../priv/static/assets",
      "--external:/fonts/*",
      "--external:/images/*",
      "--external:/favicons/*"
    ],
    cd: Path.expand("../apps/front/assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ],
  admin: [
    args: [
      "js/app.js",
      "--bundle",
      "--target=es2017",
      "--outdir=../priv/static/assets",
      "--external:/fonts/*",
      "--external:/images/*",
      "--external:/favicons/*"
    ],
    cd: Path.expand("../apps/admin/assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

Then you just need to use the good profile name whenever is needed.

For example in the admin mix.exs file, I have:

defp aliases do
    [
      setup: ["deps.get"],
      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
      "assets.deploy": [
        "esbuild admin --minify",
        "sass admin --no-source-map --style=compressed",
        "phx.digest"
      ]
    ]
  end

You can see the line "esbuild admin --minify".

And in config/dev.exs also:

config :admin, Admin.Endpoint,

  .....

  watchers: [
    # Start the esbuild watcher by calling Esbuild.install_and_run(:admin, args)
    esbuild: {Esbuild, :install_and_run, [:admin, ~w(--sourcemap=inline --watch)]},
  .....
  ]

Hope this helps.

5 Likes

Ur my Hero. thanks.

1 Like

Just to enhance the answer for Esbuild + Tailwind + Dart Sass.

Umbrella / Root - config.exs

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

config :dart_sass,
  version: "1.49.11",
  default: [
    args: ~w(css/app.scss ../priv/static/assets/app.tailwind.css),
    cd: Path.expand("../assets", __DIR__)
  ]

config :esbuild,
  version: "0.14.29",
  default: [
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../../../deps", __DIR__)}
  ],
  live_admin: [
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../apps/live_admin/assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ],
  health_admin: [
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../apps/health_admin/assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

Umbrella / Root - mix.exs

  defp aliases do
    [
      ...
      "assets.deploy": [
        "esbuild live_admin --minify",
        "sass live_admin",
        "tailwind live_admin --minify",
        "esbuild health_admin --minify",
        "sass health_admin",
        "tailwind health_admin --minify",
        "phx.digest"
      ]
    ]
  end

Other Apps - config.exs

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

config :dart_sass,
  version: "1.49.11",
  health_admin: [
    args: ~w(css/app.scss ../priv/static/assets/app.tailwind.css),
    cd: Path.expand("../assets", __DIR__)
  ]

Other Apps - dev.exs

config :health_admin, HealthAdminWeb.Endpoint,
  ...
  watchers: [
    esbuild: {Esbuild, :install_and_run, [:health_admin, ~w(--sourcemap=inline --watch)]},
    tailwind: {Tailwind, :install_and_run, [:health_admin, ~w(--watch)]},
    sass: {DartSass, :install_and_run, [:health_admin, ~w(--watch)]}
  ]

Other Apps - mix.exs

  defp aliases do
    [
      "assets.deploy": [
        "esbuild health_admin --minify",
        "sass health_admin",
        "tailwind health_admin --minify",
        "phx.digest"
      ]
    ]
  end
1 Like