crbelaus/bun

tailwindcss --watch doesn't work when used with elixir bun

Sgoettschkes opened this issue · 6 comments

I am in the process of replacing the elixir tailwindcss package with bun. To do this, I added tailwindcss as a dev dependency to my package.json. I then altered the bun config like this:

config :bun,
  version: "1.1.21",
  js: [
    args:
      ~w(build js/app.js --outdir=../priv/static/assets --external /fonts/* --external /images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{}
  ],
  css: [
    args: ~w(run tailwindcss --input css/app.css --output ../priv/static/assets/app.css),
    cd: Path.expand("../assets", __DIR__),
    env: %{}
  ]

and my dev config like this:

config :lctr, LctrWeb.Endpoint,
  watchers: [
    bun_js: {Bun, :install_and_run, [:js, ~w(--sourcemap=inline --watch)]},
    bun_css: {Bun, :install_and_run, [:css, ~w(--watch)]}
  ]

The following commands work as expected:

mix bun css: Builds the css correctly
cd assets && ./../_build/bun tailwindcss --input css/app.css --output ../priv/static/assets/app.css --watch builds the css correctly and watches for changes in the CSS

But running mix bun css --watch does nothing. No output, no css generation. The watch task also does nothing (no ouptut, no css generation). Add the --watch command the config results in the same issue.

The watch paramter works for js.

I investigated a bit more, and the following watcher works:

watchers: [
    bun_js: {Bun, :install_and_run, [:js, ~w(--sourcemap=inline --watch)]},
    "/absolute/path/to/project/_build/bun": [
      "run",
      "tailwindcss",
      "--input",
      "css/app.css",
      "--output",
      "../priv/static/assets/app.css",
      "--watch",
      cd: Path.expand("../assets", __DIR__),
      env: %{}
    ]
  ]

Given that the watcher works when not using the library, I now tried removing the wrapper.js by changing line 170 of the Bun module to:

|> System.cmd(args ++ extra_args, opts)

and now the watcher works. This confirms that the bug is in the wrapper. I'll investigate further as soon as I have more time!

Thanks for the report and investigation @Sgoettschkes. I will take a look as well as soon as I have some time.

When you run Bun without the wrapper do you get zombie processes after killing the Phoenix process? We added the wrapper because after killing the Phoenix process the Bun process kept running and had to be manually killed, but that may have been solved in latest versions of Bun.

Thanks for your reply. I did not get any zombie processes, I checked after I read the comment in the Bun module.

I also tried debugging the wrapper, but did not get anywhere. It exists with exitCode 0 and no other information. This only happens with bun run, not with bun build...

Correction, I now got a zombie process, but I only get it for the bun build command, not for the bun run! There seems to be a difference in how those two execute.

I have a few ideas how to solve this:

  1. Fix the wrapper.js so bun run works as well
  2. Make the wrapping optional but done by default, so you can pass an option to not have the process wrapper in the wrapper.js
  3. Split the run function into one that wraps the command and one that doesn't
  4. Check which command is run, and only wrap the bun build (and others, if it's discovered that others have the same issue with zombie processes)
  5. Try fixing the zombie process issue another way

I'm happy to help, but given my time available and existing knowledge, I think I can only help with 2/3/4 but I'm happy to test patches if you have some ideas on 1/5. Let me know if you want me to work on 2,3 or 4 (and which one you prefer).

I've been doing some investigation and found out that bun run runs the script command in a subshell, ensuring that it is killed properly when the parent process dies. This matches your observation that bun run does not leave zombie processes while bun build does.

I made a few changes in #21 that should fix your problem. Changes match your proposed point 3: the run function wraps the bun process only when it is running the build command.

If you can check that PR and confirm that it works well I will publish a new release with the fix.


I created a new Phoenix app, replaced esbuild with bun build and tailwind with bun run tailwindcss and copied your configuration.

It now seems to work just fine. I can mix bun css to build the css one time and mix bun css --watch to keep the watcher running. Starting the Phoenix server starts both the JS and CSS watchers and kills them properly without zombie process.

Version 1.3.1 has just been released with a fix for this problem.

Thanks for the report and conversation @Sgoettschkes. Glad that you find this package useful.

BTW your approach of using Bun for js and css is great. If you want to go for it I think that it would be amazing to have a section in the README that explains how to replace the default Phoenix stack (esbuild + tailwindcss pacakges) with just bun (#22).