defmodule WebPack.Plug.Static do @moduledoc """ This plug API is the same as plug.static, but wrapped to : - wait file if compiling before serving them - add server side event endpoint for webpack build events - add webpack "stats" JSON getter, and stats static analyser app """ use Plug.Router plug :match plug :dispatch plug Plug.Static, at: "/webpack/static", from: :lyn plug :wait_compilation def init(static_opts), do: Plug.Static.init(static_opts) def call(conn, opts) do conn = plug_builder_call(conn, opts) if !conn.halted, do: static_plug(conn,opts), else: conn end def wait_compilation(conn,_) do if Application.get_env(:lyn, :hot) && :wait == GenEvent.call(WebPack.Events,WebPack.EventManager,{:wait?,self}) do receive do :ok->:ok after 30_000->:ok end # if a compil is running, wait its end before serving asset end conn end def static_plug(conn,static_opts) do Plug.Static.call(conn,static_opts) end get "/webpack/stats.json" do conn |> put_resp_content_type("application/json") |> send_file(200,"#{WebPack.Util.web_priv}/webpack.stats.json") |> halt end get "/webpack", do: %{conn|path_info: ["webpack","static","index.html"]} get "/webpack/events" do conn=conn |> put_resp_header("content-type", "text/event-stream") |> send_chunked(200) hot? = Application.get_env(:lyn, :hot) if hot? == :client, do: Plug.Conn.chunk(conn, "event: hot\ndata: nothing\n\n") if hot?, do: GenEvent.add_mon_handler(WebPack.Events,{WebPack.Plug.Static.EventHandler,make_ref},conn) receive do {:gen_event_EXIT,_,_} -> halt(conn) end end get "/webpack/client.js" do conn |> put_resp_content_type("application/javascript") |> send_file(200,"#{WebPack.Util.web_app}/../node_modules/reaxt/webpack_client.js") |> halt end match _, do: conn defmodule EventHandler do use GenEvent def handle_event(ev,conn) do #Send all builder events to browser through SSE Plug.Conn.chunk(conn, "event: #{ev.event}\ndata: #{Poison.encode!(ev)}\n\n") {:ok, conn} end end end defmodule WebPack.EventManager do use GenEvent require Logger def start_link do res = GenEvent.start_link(name: WebPack.Events) GenEvent.add_handler(WebPack.Events,__MODULE__,%{init: true,pending: [], compiling: false}) receive do :server_ready-> :ok end res end def handle_call({:wait?,_reply_to},%{compiling: false}=state), do: {:ok,:nowait,state} def handle_call({:wait?,reply_to},state), do: {:ok,:wait,%{state|pending: [reply_to|state.pending]}} def handle_event(%{event: "done"}=ev,state) do WebPack.Util.build_stats case {ev,state.init} do {%{error: "soft fail"},true}-> Logger.error("Compilation Error: see /webpack#errors") send(Process.whereis(Reaxt.App.Sup),:server_ready) {%{error: error},true}-> Logger.error("cannot compile server_side renderer") Logger.error(error) System.halt(1) {_,true}-> send(Process.whereis(Reaxt.App.Sup),:server_ready) {_,false}-> Supervisor.terminate_child(Reaxt.App.Sup,:react) Supervisor.restart_child(Reaxt.App.Sup,:react) end for pid<-state.pending, do: send(pid,:ok) {:ok,%{state| pending: [], init: false, compiling: false}} end def handle_event(%{event: "invalid"},%{compiling: false}=state) do spawn fn-> #async compile server on invalid file Mix.Tasks.Webpack.Compile.compile_server GenServer.cast(WebPack.Compiler,:server_build) end {:ok,%{state|compiling: true}} end def handle_event(_ev,state), do: {:ok,state} end defmodule WebPack.Compiler do def start_link do cmd = "node ../node_modules/reaxt/webpack_server" hot_arg = if Application.get_env(:lyn, :hot) == :client, do: " hot",else: "" Exos.Proc.start_link(cmd<>hot_arg,[],[cd: WebPack.Util.web_app],[name: __MODULE__],WebPack.Events) end end defmodule WebPack.Util do def web_priv do :code.priv_dir(:lyn) end def web_app do Application.get_env :lyn, :web_app, "web" end def build_stats do if File.exists?("#{web_priv}/webpack.stats.json") do stats = Poison.Parser.parse!(File.read!("#{web_priv}/webpack.stats.json"), keys: :atoms) stats = %{assetsByChunkName: stats.assetsByChunkName} defmodule Elixir.WebPack do @stats stats def stats, do: @stats def file_of(name) do case WebPack.stats.assetsByChunkName[name] do [f|_]->f f -> f end end @header_script if(Application.get_env(:lyn, :hot), do: ~s()) @header_global Poison.encode!(Application.get_env(:lyn, :global_config)) def header, do: "\n#{@header_script}" end end end end