defmodule Mix.Tasks.Compile.ExWebpack do use Mix.Task @recursive true @moduledoc """ Runs `npm run build` in the current project. ## Configuration This compiler can be configured through the return value of the `project/0` function in `mix.exs`; for example: def project() do [app: :myapp, compilers: [:ex_webpack] ++ Mix.compilers, deps: deps()] end """ @spec run(OptionParser.argv) :: :ok | no_return def run(args) do config = Mix.Project.config() cd = config[:webpack_cd] || File.cwd!() Mix.shell.print_app() if Keyword.get(config, :webpack_watch, false) do Phoenix.Endpoint.Watcher.start_link("npm", ["run", "watch"], [cd: cd]) else build(config, args) end Mix.Project.build_structure() :ok end # This is called by Elixir when `mix clean` is called. def clean() do _config = Mix.Project.config() # TODO(Connor): do web projects have a `clean` step? end defp build(config, task_args) do exec = "npm" env = Keyword.get(config, :node_env, %{}) args = ["run", "build"] # In OTP 19, Erlang's `open_port/2` ignores the current working # directory when expanding relative paths. This means that `:make_cwd` # must be an absolute path. This is a different behaviour from earlier # OTP versions and appears to be a bug. It is being tracked at # http://bugs.erlang.org/browse/ERL-175. cd = config[:webpack_cd] || File.cwd!() cwd = Path.expand(".", cd) error_msg = "Webpack failed" case cmd(exec, args, cwd, env, "--verbose" in task_args) do 0 -> :ok exit_status -> raise_build_error(exec, exit_status, error_msg) end end # Runs `exec [args]` in `cwd` and prints the stdout and stderr in real time, # as soon as `exec` prints them (using `IO.Stream`). defp cmd(exec, args, cwd, env, verbose?) do opts = [ into: IO.stream(:stdio, :line), stderr_to_stdout: true, cd: cwd, env: env ] if verbose? do print_verbose_info(exec, args) end {%IO.Stream{}, status} = System.cmd(find_executable(exec), args, opts) status end defp find_executable(exec) do System.find_executable(exec) || Mix.raise(""" "#{exec}" not found in the path. please make sure it is correct. """) end defp raise_build_error(exec, exit_status, error_msg) do Mix.raise(~s{Could not compile with "#{exec}" (exit status: #{exit_status}).\n} <> error_msg) end defp print_verbose_info(exec, args) do args = Enum.map_join(args, " ", fn(arg) -> if String.contains?(arg, " "), do: inspect(arg), else: arg end) Mix.shell.info "Compiling: #{exec} #{args}" end end