defmodule Mix.Tasks.DuskmoonBundler.Dev do
@shortdoc "Start the DuskmoonBundler dev watcher with HMR"
@moduledoc """
Start the DuskmoonBundler file watcher for development.
Reads configuration from `config :duskmoon_bundler` and `config :duskmoon_bundler, :server`.
Pass a profile name as the first argument to use a named profile.
CLI flags override config values.
mix duskmoon_bundler.dev
mix duskmoon_bundler.dev my_app_web
## Options
* `--root` — asset source directory (default from config or `"assets"`)
* `--watch-dir` — additional directory to watch (repeatable)
* `--tailwind` — enable Tailwind CSS rebuilds
* `--tailwind-css` — custom Tailwind input CSS file
* `--tailwind-outdir` — directory to write rebuilt CSS (default: `"priv/static/assets/css"`)
* `--target` — JS target (default: `es2020`)
"""
use Mix.Task
alias DuskmoonBundler.Config
alias DuskmoonBundler.Paths
@impl true
def run(args) do
Mix.Task.run("app.start")
{parsed, argv, _invalid} =
OptionParser.parse(args,
strict: [
root: :string,
watch_dir: [:string, :keep],
tailwind: :boolean,
tailwind_css: :string,
tailwind_outdir: :string,
target: :string
]
)
profile = parse_profile(argv)
config = Config.build(profile)
server_config = Config.server(profile)
tailwind_config = Config.tailwind(profile)
root = Keyword.get(parsed, :root) || to_string(config.root)
target = Keyword.get(parsed, :target) || to_string(config.target)
tailwind? =
Keyword.get(parsed, :tailwind) ||
(tailwind_config != [] and Keyword.get(parsed, :tailwind, true))
cli_watch_dirs = Keyword.get_values(parsed, :watch_dir)
watch_dirs =
case cli_watch_dirs do
[] -> server_config.watch_dirs
list -> list
end
watch_dirs = if tailwind? and watch_dirs == [], do: [Paths.lib()], else: watch_dirs
tailwind_css = Keyword.get(parsed, :tailwind_css) || tailwind_config[:css]
tailwind_outdir = Keyword.get(parsed, :tailwind_outdir, Path.join(config.outdir, "css"))
opts = [
root: root,
watch_dirs: watch_dirs,
tailwind: tailwind?,
tailwind_css: tailwind_css,
tailwind_outdir: tailwind_outdir,
target: target,
name: watcher_name(profile)
]
{:ok, _pid} = DuskmoonBundler.Watcher.start_link(opts)
Mix.shell().info("[DuskmoonBundler] Watching #{opts[:root]}...")
if tailwind? do
Mix.shell().info(
"[DuskmoonBundler] Tailwind CSS enabled (watching #{Enum.join(watch_dirs, ", ")})"
)
end
unless iex_running?() do
Process.sleep(:infinity)
end
end
defp parse_profile(args), do: DuskmoonBundler.Config.Profile.from_args(args)
defp watcher_name(nil), do: DuskmoonBundler.Watcher
defp watcher_name(profile) do
Module.concat(DuskmoonBundler.Watcher, profile |> Atom.to_string() |> Macro.camelize())
end
@dialyzer {:nowarn_function, iex_running?: 0}
defp iex_running? do
Code.ensure_loaded?(IEx) and IEx.started?()
end
end