I have a Mix task that I’ve built for running Webpack alongside my Plug server to build the static assets. The task looks like:
defmodule Mix.Tasks.Server do
use Mix.Task
require Logger
@watchers webpack: [bin: "/usr/local/bin/yarn",
opts: ["start"]]
@shortdoc "Starts applications and their servers"
@moduledoc """
Starts the application
## Command line options
This task accepts the same command-line arguments as `run`.
For additional inforamtion, refer to the documentation for
`Mix.Tasks.Run`
The `--no-halt` flag is automatically added.
"""
def run(args) do
if MyApplication.watch_assets?() , do: spawn(&start_watcher/0)
Mix.Task.run "run", run_args() ++ args
end
defp start_watcher do
validate_watcher()
Logger.info "Starting asset watcher"
System.cmd watcher_bin(),
watcher()[:opts]
end
defp validate_watcher do
unless watcher_exists?() do
Logger.error "Could not start watcher because #{watcher_bin()} could " <>
"not be found. Your dashboard server is running, but " <>
"assets won't be compiled."
exit(:shutdown)
end
end
defp watcher_exists?, do: File.exists? watcher_bin()
defp watcher, do: @watchers[:webpack]
defp watcher_bin, do: watcher()[:bin] |> Path.expand
defp run_args, do: if iex_running?(), do: [], else: ["--no-halt"]
defp iex_running?, do: Code.ensure_loaded?(IEx) && IEx.started?
end
When I quit the server run (with Ctrl-C x 2) the Yarn process is left running. I’ve tried using spawn_link instead of spawn which has no effect. How do I ensure that the yarn start process is stopped with the application?
When you start that yarn stuff directly, does it shut down when you close stdin? If not, you need to create a wrapper script which starts yarn, remembers it’s (OS) PID and kills that PID when it receives EOF on stdin.
Somewhere there was a generic shell-wrapper for such situations, but I can’t find it right now.
I’m not aware of any solution to this problem on windows except using a POSIX-wrapper.
What @NobbZ said yep. If yarn is not closing then it’s not using the unix style method of either forking to persist, or closing when stdin closes, that sounds like bad programming (which I’m irritatingly getting used to from the horrid javascript ecosystem…)…
And yep, I made it, somewhere on these forums (I should probably put it on my blog or something), let me see if I can find it in my history…
You may be able to tweak it to your needs. It is run with elixir apprunner.exs command-to-run. It listens to its STDIN to determine if the parent BEAM was closed. If so, it uses kill to terminate the child process. Child process output is sent back to the parent BEAM. If the child process stops on its own, the wrapper also stops.
Because its to late now… You know how hard it is to introduce changes that break existing software. Its often not even done when a major jump is done, just to scare no one away…