diff --git a/.formatter.exs b/.formatter.exs
new file mode 100644
index 0000000..1b61e2d
--- /dev/null
+++ b/.formatter.exs
@@ -0,0 +1,9 @@
+locals_without_parens = [
+ plug: 1,
+ plug: 2
+]
+
+[
+ inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"],
+ locals_without_parens: locals_without_parens
+]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41a8de7..4eb90ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+### Added
+
+* Support for esbuild
+
### Changed
* Updated poolboy, it itself now uses rebar3 to build.
diff --git a/lib/app.ex b/lib/app.ex
new file mode 100644
index 0000000..edb1246
--- /dev/null
+++ b/lib/app.ex
@@ -0,0 +1,36 @@
+defmodule Reaxt.App do
+ use Application
+ require Logger
+
+ def start(_, _) do
+ hot_reload_processes =
+ if Reaxt.Utils.is_hot?() do
+ hot_processes(Reaxt.Utils.bundler())
+ else
+ []
+ end
+
+ base_processes = [
+ Reaxt.PoolsSup
+ ]
+
+ children = Enum.concat(base_processes, hot_reload_processes)
+
+ result = Supervisor.start_link(children, name: __MODULE__, strategy: :one_for_one)
+ if Reaxt.Utils.is_webpack?(), do: Reaxt.Index.Generator.build_webpack_stats()
+
+ result
+ end
+
+ def hot_processes(:webpack) do
+ [
+ WebPack.Hot.Events,
+ WebPack.Hot.EventManager,
+ WebPack.Hot.Compiler
+ ]
+ end
+
+ def hot_processes(_) do
+ raise "[Reaxt] Hot reload is not supported for the bundler #{inspect(Reaxt.Utils.bundler())}"
+ end
+end
diff --git a/lib/esbuild/esbuild.ex b/lib/esbuild/esbuild.ex
new file mode 100644
index 0000000..0a955a3
--- /dev/null
+++ b/lib/esbuild/esbuild.ex
@@ -0,0 +1,9 @@
+defmodule Reaxt.Esbuild do
+ @moduledoc """
+ Utilities functions to fetch specific esbuild configs
+ """
+
+ def esbuild_config do
+ Application.get_env(:reaxt, :esbuild_config, "build.js")
+ end
+end
diff --git a/lib/esbuild/tasks.esbuild.ex b/lib/esbuild/tasks.esbuild.ex
new file mode 100644
index 0000000..6725be6
--- /dev/null
+++ b/lib/esbuild/tasks.esbuild.ex
@@ -0,0 +1,53 @@
+defmodule Mix.Tasks.Esbuild.Compile do
+ use Mix.Task
+
+ @shortdoc "Compiles Esbuild"
+
+ def run(_) do
+ {_logs, 0} = compile()
+ :ok
+ end
+
+ def compile() do
+ config = "./" <> Reaxt.Esbuild.esbuild_config()
+
+ System.cmd(
+ "node",
+ [config],
+ into: "",
+ cd: Reaxt.Utils.web_app(),
+ env: [{"MIX_ENV", "#{Mix.env()}"}]
+ )
+ end
+end
+
+defmodule Mix.Tasks.Compile.ReaxtEsbuild do
+ use Mix.Task.Compiler
+
+ def run(args) do
+ IO.puts("[Reaxt] Running Esbuild compiler...")
+ Mix.Task.run("reaxt.validate", args ++ ["--reaxt-skip-compiler-check"])
+
+ if !File.exists?(Path.join(Reaxt.Utils.web_app(), "node_modules")) do
+ Mix.Task.run("npm.install", args)
+ else
+ installed_version =
+ Poison.decode!(File.read!("#{Reaxt.Utils.web_app()}/node_modules/reaxt/package.json"))[
+ "version"
+ ]
+
+ current_version =
+ Poison.decode!(File.read!("#{:code.priv_dir(:reaxt)}/commonjs_reaxt/package.json"))[
+ "version"
+ ]
+
+ if installed_version !== current_version, do: Mix.Task.run("npm.install", args)
+ end
+
+ if !Reaxt.Utils.is_hot?() do
+ Mix.Task.run("esbuild.compile", args)
+ else
+ {:ok, []}
+ end
+ end
+end
diff --git a/lib/pool.ex b/lib/pool.ex
new file mode 100644
index 0000000..02fabc3
--- /dev/null
+++ b/lib/pool.ex
@@ -0,0 +1,51 @@
+defmodule Reaxt.PoolsSup do
+ @moduledoc """
+ Supervision of multiple :poolboy instances for the Server Side Rendering
+ """
+ alias :poolboy, as: Pool
+
+ use Supervisor
+ require Logger
+
+ def transaction(pool_name, fct) do
+ Pool.transaction(pool_name, fct)
+ end
+
+ def start_link(arg) do
+ Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
+ end
+
+ def init(_) do
+ pool_size = Reaxt.Utils.pool_size()
+ pool_overflow = Reaxt.Utils.max_pool_overflow()
+ server_dir = "#{Reaxt.Utils.web_priv()}/#{Reaxt.Utils.server_dir()}"
+ server_files = Path.wildcard("#{server_dir}/*.js")
+
+ if server_files == [] do
+ Logger.error(
+ "#server JS not yet compiled in #{server_dir}, compile it before with `mix webpack.compile`"
+ )
+
+ throw({:error, :serverjs_not_compiled})
+ else
+ children =
+ Enum.map(server_files, fn server ->
+ parsed_js_name =
+ server |> Path.basename(".js") |> String.replace(~r/[0-9][a-z][A-Z]/, "_")
+
+ pool_name = :"react_#{parsed_js_name}_pool"
+
+ args = [
+ worker_module: Reaxt.Render,
+ size: pool_size,
+ max_overflow: pool_overflow,
+ name: {:local, pool_name}
+ ]
+
+ Pool.child_spec(pool_name, args, server)
+ end)
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+ end
+end
diff --git a/lib/reaxt.ex b/lib/reaxt.ex
deleted file mode 100644
index 02e036a..0000000
--- a/lib/reaxt.ex
+++ /dev/null
@@ -1,110 +0,0 @@
-defmodule ReaxtError do
- defexception [:message,:args,:js_render,:js_stack]
- def exception({:handler_error,module,submodule,args,error,stack}) do
- params=%{
- module: module,
- submodule: submodule,
- args: args
- }
- %ReaxtError{message: "JS Handler Exception for #{inspect params}: #{error}", args: params, js_stack: (stack && parse_stack(stack))}
- end
- def exception({:render_error,params,error,stack,js_render}) do
- %ReaxtError{message: "JS Render Exception : #{error}", args: params, js_render: js_render, js_stack: (stack && parse_stack(stack))}
- end
- defp parse_stack(stack) do
- Regex.scan(~r/at (.*) \((.*):([0-9]*):[0-9]*\)/,stack)
- |> Enum.map(fn [_,function,url,line]->
- if String.contains?(url,"/priv") and !(function in ["Port.next_term","Socket.read_term"]) do
- {line,_} = Integer.parse(line)
- [_,after_priv] = String.split(url,"/priv/",parts: 2)
- {JS,:"#{function}",0,file: '#{WebPack.Util.web_priv}/#{after_priv}', line: line}
- end
- end)
- |> Enum.filter(&!is_nil(&1))
- end
-end
-defmodule Reaxt do
- alias :poolboy, as: Pool
- require Logger
-
- def render_result(chunk,module,data,timeout) when not is_tuple(module), do:
- render_result(chunk,{module,nil},data,timeout)
- def render_result(chunk,{module,submodule},data,timeout) do
- Pool.transaction(:"react_#{chunk}_pool",fn worker->
- GenServer.call(worker,{:render,module,submodule,data,timeout},timeout+100)
- end)
- end
-
- def render!(module,data,timeout \\ 5_000, chunk \\ :server) do
- case render_result(chunk,module,data,timeout) do
- {:ok,res}->res
- {:error,err}->
- try do raise(ReaxtError,err)
- rescue ex->
- [_|stack] = __STACKTRACE__
- reraise ex, ((ex.js_stack || []) ++ stack)
- end
- end
- end
-
- def render(module,data, timeout \\ 5_000) do
- try do
- render!(module,data,timeout)
- rescue
- ex->
- case ex do
- %{js_render: js_render} when is_binary(js_render)->
- Logger.error(Exception.message(ex))
- %{css: "",html: "", js_render: js_render}
- _ ->
- reraise ex, __STACKTRACE__
- end
- end
- end
-
- def reload do
- WebPack.Util.build_stats
- Supervisor.terminate_child(Reaxt.App, Reaxt.App.PoolsSup)
- Supervisor.restart_child(Reaxt.App, Reaxt.App.PoolsSup)
- end
-
- def start_link(server_path) do
- init = Poison.encode!(Application.get_env(:reaxt,:global_config,nil))
- Exos.Proc.start_link("node #{server_path}",init,[cd: '#{WebPack.Util.web_priv}'])
- end
-
- defmodule App do
- use Application
- def start(_,_) do
- result = Supervisor.start_link(
- [App.PoolsSup] ++ List.wrap(if Application.get_env(:reaxt,:hot) do [
- WebPack.Events,
- WebPack.EventManager,
- WebPack.Compiler,
- #{WebPack.StartBlocker,:infinity} # choice : wait for build or "mix webpack.compile" before launch
- ] end), name: __MODULE__, strategy: :one_for_one)
- WebPack.Util.build_stats
- result
- end
- defmodule PoolsSup do
- use Supervisor
- def start_link(arg) do Supervisor.start_link(__MODULE__,arg, name: __MODULE__) end
- def init(_) do
- pool_size = Application.get_env(:reaxt,:pool_size)
- pool_overflow = Application.get_env(:reaxt,:pool_max_overflow)
- server_dir = "#{WebPack.Util.web_priv}/#{Application.get_env(:reaxt,:server_dir)}"
- server_files = Path.wildcard("#{server_dir}/*.js")
- if server_files == [] do
- Logger.error("#server JS not yet compiled in #{server_dir}, compile it before with `mix webpack.compile`")
- throw {:error,:serverjs_not_compiled}
- else
- Supervisor.init(
- for server<-server_files do
- pool = :"react_#{server |> Path.basename(".js") |> String.replace(~r/[0-9][a-z][A-Z]/,"_")}_pool"
- Pool.child_spec(pool,[worker_module: Reaxt,size: pool_size, max_overflow: pool_overflow, name: {:local,pool}], server)
- end, strategy: :one_for_one)
- end
- end
- end
- end
-end
diff --git a/lib/reaxt_error.ex b/lib/reaxt_error.ex
new file mode 100644
index 0000000..b78459e
--- /dev/null
+++ b/lib/reaxt_error.ex
@@ -0,0 +1,50 @@
+defmodule Reaxt.Error do
+ @moduledoc """
+ Exception representing a Reaxt Error
+ """
+ defexception [:message, :args, :js_render, :js_stack]
+
+ def exception({:handler_error, module, submodule, args, error, stack}) do
+ params = %{
+ module: module,
+ submodule: submodule,
+ args: args
+ }
+
+ %Reaxt.Error{
+ message: "JS Handler Exception for #{inspect(params)}: #{error}",
+ args: params,
+ js_stack: stack && parse_stack(stack)
+ }
+ end
+
+ def exception({:render_error, params, error, stack, js_render}) do
+ %Reaxt.Error{
+ message: "JS Render Exception : #{error}",
+ args: params,
+ js_render: js_render,
+ js_stack: stack && parse_stack(stack)
+ }
+ end
+
+ def exception(rest) do
+ %Reaxt.Error{
+ message: "JS Render Exception : #{inspect(rest)}",
+ args: "",
+ js_render: "",
+ js_stack: ""
+ }
+ end
+
+ defp parse_stack(stack) do
+ Regex.scan(~r/at (.*) \((.*):([0-9]*):[0-9]*\)/, stack)
+ |> Enum.filter(fn [_, function, url, _line] ->
+ String.contains?(url, "/priv") and function not in ["Port.next_term", "Socket.read_term"]
+ end)
+ |> Enum.map(fn [_, function, url, line] ->
+ {line, _} = Integer.parse(line)
+ [_, after_priv] = String.split(url, "/priv/", parts: 2)
+ {JS, :"#{function}", 0, file: ~c"#{Reaxt.Utils.web_priv()}/#{after_priv}", line: line}
+ end)
+ end
+end
diff --git a/lib/reaxt_index.ex b/lib/reaxt_index.ex
new file mode 100644
index 0000000..f58ac7c
--- /dev/null
+++ b/lib/reaxt_index.ex
@@ -0,0 +1,64 @@
+defmodule Reaxt.Index.Generator do
+ @moduledoc """
+ Utils functions to overlaod the Reaxt.Index module for the WebPack configuration
+ """
+
+ def build_webpack_stats do
+ if File.exists?("#{Reaxt.Utils.web_priv()}/webpack.stats.json") do
+ all_stats = Poison.decode!(File.read!("#{Reaxt.Utils.web_priv()}/webpack.stats.json"))
+ stats_array = all_stats["children"]
+
+ stats =
+ Enum.map(stats_array, fn stats ->
+ %{
+ assetsByChunkName: stats["assetsByChunkName"],
+ errors: stats["errors"],
+ warnings: stats["warnings"]
+ }
+ end)
+
+ _ = Code.compiler_options(ignore_module_conflict: true)
+
+ defmodule Elixir.Reaxt.Index do
+ @stats stats
+ def stats, do: @stats
+
+ def file_of(name) do
+ r =
+ Enum.find_value(WebPack.stats(), fn %{assetsByChunkName: assets} ->
+ assets["#{name}"]
+ end)
+
+ case r do
+ [f | _] -> f
+ f -> f
+ end
+ end
+
+ @header_script if(Reaxt.Utils.is_hot?(),
+ do: ~s()
+ )
+ @header_global Poison.encode!(Reaxt.Render.get_global_config())
+
+ def header do
+ "\n#{@header_script}"
+ end
+ end
+
+ _ = Code.compiler_options(ignore_module_conflict: false)
+ end
+ end
+end
+
+defmodule Elixir.Reaxt.Index do
+ @moduledoc """
+ Functions to help construct the index.html to serve
+ """
+ def stats, do: %{assetsByChunkName: %{}}
+ def file_of(_), do: nil
+
+ def header do
+ global_config = Poison.encode!(Reaxt.Render.get_global_config())
+ ""
+ end
+end
diff --git a/lib/render.ex b/lib/render.ex
new file mode 100644
index 0000000..7f408db
--- /dev/null
+++ b/lib/render.ex
@@ -0,0 +1,65 @@
+defmodule Reaxt.Render do
+ require Logger
+
+ def get_global_config do
+ Application.get_env(:reaxt, :global_config, %{})
+ end
+
+ def set_global_config(config) do
+ Application.put_env(:reaxt, :global_config, config)
+ end
+
+ def render_result(chunk, module, data, timeout) when not is_tuple(module) do
+ render_result(chunk, {module, nil}, data, timeout)
+ end
+
+ def render_result(chunk, {module, submodule}, data, timeout) do
+ Reaxt.PoolsSup.transaction(:"react_#{chunk}_pool", fn worker ->
+ GenServer.call(worker, {:render, module, submodule, data, timeout}, timeout + 100)
+ end)
+ end
+
+ def render!(module, data, timeout \\ 5_000, chunk \\ :server) do
+ case render_result(chunk, module, data, timeout) do
+ {:ok, res} ->
+ res
+
+ {:error, err} ->
+ try do
+ raise(Reaxt.Error, err)
+ rescue
+ ex ->
+ [_ | stack] = __STACKTRACE__
+ # stack = List.wrap(ex[:js_stack]) |> Enum.concat(stack)
+ reraise ex, stack
+ end
+ end
+ end
+
+ def render(module, data, timeout \\ 5_000) do
+ try do
+ render!(module, data, timeout)
+ rescue
+ ex ->
+ case ex do
+ %{js_render: js_render} when is_binary(js_render) ->
+ Logger.error(Exception.message(ex))
+ %{css: "", html: "", js_render: js_render}
+
+ _ ->
+ reraise ex, __STACKTRACE__
+ end
+ end
+ end
+
+ def reload do
+ if Reaxt.Utils.is_webpack?(), do: Reaxt.Index.Generator.build_webpack_stats()
+ :ok = Supervisor.terminate_child(Reaxt.App, Reaxt.PoolsSup)
+ Supervisor.restart_child(Reaxt.App, Reaxt.PoolsSup)
+ end
+
+ def start_link(server_path) do
+ init = Poison.encode!(Application.get_env(:reaxt, :global_config, nil))
+ Exos.Proc.start_link("node #{server_path}", init, cd: ~c"#{Reaxt.Utils.web_priv()}")
+ end
+end
diff --git a/lib/tasks.ex b/lib/tasks.ex
deleted file mode 100644
index bcf608a..0000000
--- a/lib/tasks.ex
+++ /dev/null
@@ -1,148 +0,0 @@
-defmodule Mix.Tasks.Npm.Install do
- use Mix.Task
-
- @shortdoc "`npm install` in web_dir + npm install server side dependencies"
- def run(_args) do
- System.cmd("npm",["install"], into: IO.stream(:stdio, :line), cd: WebPack.Util.web_app)
- # TOIMPROVE- did not found a better hack to avoid npm install symlink : first make a tar gz package, then npm install it
- reaxt_tgz = "#{System.tmp_dir}/reaxt.tgz"
- System.cmd("tar", ["zcf",reaxt_tgz,"commonjs_reaxt"],into: IO.stream(:stdio, :line), cd: "#{:code.priv_dir(:reaxt)}")
- System.cmd("npm",["install","--no-save",reaxt_tgz], into: IO.stream(:stdio, :line), cd: WebPack.Util.web_app)
- end
-end
-
-defmodule Mix.Tasks.Webpack.Analyseapp do
- use Mix.Task
-
- @shortdoc "Generate webpack stats analysing application, resulting priv/static is meant to be versionned"
- def run(_args) do
- File.rm_rf!("priv/static")
- {_,0} = System.cmd("git",["clone","-b","ajax-sse-loading","https://github.com/awetzel/analyse"], into: IO.stream(:stdio, :line))
- {_,0} = System.cmd("npm",["install"], into: IO.stream(:stdio, :line), cd: "analyse")
- {_,0} = System.cmd("grunt",[], into: IO.stream(:stdio, :line), cd: "analyse")
- File.cp_r!("analyse/dist", "priv/static")
- File.rm_rf!("analyse")
- end
-end
-
-defmodule Mix.Tasks.Webpack.Compile do
- use Mix.Task
-
- @shortdoc "Compiles Webpack"
- @webpack "./node_modules/webpack/bin/webpack.js"
-
- def run(_) do
- case compile() do
- {json, 0} ->
- File.write!("priv/webpack.stats.json", json)
- {:ok, []}
-
- {ret, x} when x in [1,2] ->
- require Logger
- ret
- |> Poison.decode!()
- |> Map.fetch!("errors")
- |> Enum.map(fn
- bin when is_binary(bin) -> Logger.error(bin)
- %{"message" => bin} when is_binary(bin) -> Logger.error(bin)
- end)
- {:error,[]}
- end
- end
-
- def compile() do
- config = "./"<>WebPack.Util.webpack_config
- webpack = @webpack
- System.cmd(
- "node",
- [webpack, "--config", config, "--json"],
- into: "",
- cd: WebPack.Util.web_app(),
- env: [{"MIX_ENV", "#{Mix.env()}"}]
- )
- end
-end
-
-defmodule Mix.Tasks.Reaxt.Validate do
- use Mix.Task
-
- @shortdoc "Validates that reaxt is setup correct"
- def run(args) do
- if Enum.all?(args, &(&1 != "--reaxt-skip-validation")) do
- validate(args)
- end
- end
-
- def validate(args) do
- if WebPack.Util.web_priv == :no_app_specified, do:
- Mix.raise """
- Reaxt :otp_app is not configured.
- Add following to config.exs
-
- config :reaxt, :otp_app, :your_app
-
- """
-
- packageJsonPath = Path.join(WebPack.Util.web_app, "package.json")
- if not File.exists?(packageJsonPath), do:
- Mix.raise """
- Reaxt could not find a package.json in #{WebPack.Util.web_app}.
- Add package.json to #{WebPack.Util.web_app} or configure a new
- web_app directory in config.exs:
-
- config :reaxt, :web_app, "webapp"
-
- """
-
- packageJson = Poison.decode!(File.read!(packageJsonPath))
- if packageJson["devDependencies"]["webpack"] == nil, do:
- Mix.raise """
- Reaxt requires webpack as a devDependency in #{packageJsonPath}.
- Add a dependency to 'webpack' like:
-
- {
- devDependencies: {
- "webpack": "^1.4.13"
- }
- }
- """
-
- if (Enum.all?(args, &(&1 != "--reaxt-skip-compiler-check"))
- and Enum.all? (Mix.Project.get!).project[:compilers], &(&1 != :reaxt_webpack)), do:
- Mix.raise """
- Reaxt has a built in compiler that compiles the web app.
- Remember to add it to the list of compilers in mix.exs:
-
- def project do
- [...
- app: :your_app,
- compilers: [:reaxt_webpack] ++ Mix.compilers,
- ...]
- end
- """
- end
-end
-
-defmodule Mix.Tasks.Compile.ReaxtWebpack do
- use Mix.Task.Compiler
-
- def run(args) do
- IO.puts("[Reaxt] Running compiler...")
- Mix.Task.run("reaxt.validate", args ++ ["--reaxt-skip-compiler-check"])
-
- if !File.exists?(Path.join(WebPack.Util.web_app, "node_modules")) do
- Mix.Task.run("npm.install", args)
- else
- installed_version = Poison.decode!(File.read!("#{WebPack.Util.web_app}/node_modules/reaxt/package.json"))["version"]
- current_version = Poison.decode!(File.read!("#{:code.priv_dir(:reaxt)}/commonjs_reaxt/package.json"))["version"]
- if installed_version !== current_version, do:
- Mix.Task.run("npm.install", args)
- end
-
- if !Application.get_env(:reaxt,:hot) do
- Mix.Task.run("webpack.compile", args)
- else
- {:ok, []}
- end
- end
-end
diff --git a/lib/tasks.reaxt.ex b/lib/tasks.reaxt.ex
new file mode 100644
index 0000000..2960083
--- /dev/null
+++ b/lib/tasks.reaxt.ex
@@ -0,0 +1,103 @@
+defmodule Mix.Tasks.Npm.Install do
+ @moduledoc """
+ Mix task to install npm dependencies and the reaxt js server
+ """
+ use Mix.Task
+
+ @shortdoc "`npm install` in web_dir + npm install server side dependencies"
+ def run(_args) do
+ System.cmd("npm", ["install"], into: IO.stream(:stdio, :line), cd: Reaxt.Utils.web_app())
+
+ # TOIMPROVE- did not found a better hack to avoid npm install symlink : first make a tar gz package, then npm install it
+ reaxt_tgz = "#{System.tmp_dir()}/reaxt.tgz"
+
+ System.cmd("tar", ["zcf", reaxt_tgz, "commonjs_reaxt"],
+ into: IO.stream(:stdio, :line),
+ cd: "#{:code.priv_dir(:reaxt)}"
+ )
+
+ System.cmd("npm", ["install", "--no-save", reaxt_tgz],
+ into: IO.stream(:stdio, :line),
+ cd: Reaxt.Utils.web_app()
+ )
+ end
+end
+
+defmodule Mix.Tasks.Reaxt.Validate do
+ use Mix.Task
+
+ @shortdoc "Validates that reaxt is setup correct"
+ def run(args) do
+ if Enum.all?(args, &(&1 != "--reaxt-skip-validation")) do
+ validate(args)
+ end
+ end
+
+ def validate(args) do
+ if Reaxt.Utils.bundler() not in [:webpack, :esbuild],
+ do:
+ Mix.raise("""
+ Reaxt :bundler is not configured.
+ Add following to config.exs
+
+ config :reaxt, :bundler, :webpack
+ OR
+ config :reaxt, :bundler, :esbuild
+ """)
+
+ if Reaxt.Utils.web_priv() == :no_app_specified,
+ do:
+ Mix.raise("""
+ Reaxt :otp_app is not configured.
+ Add following to config.exs
+
+ config :reaxt, :otp_app, :your_app
+
+ """)
+
+ packageJsonPath = Path.join(Reaxt.Utils.web_app(), "package.json")
+
+ if not File.exists?(packageJsonPath),
+ do:
+ Mix.raise("""
+ Reaxt could not find a package.json in #{Reaxt.Utils.web_app()}.
+ Add package.json to #{Reaxt.Utils.web_app()} or configure a new
+ web_app directory in config.exs:
+
+ config :reaxt, :web_app, "webapp"
+
+ """)
+
+ packageJson = Poison.decode!(File.read!(packageJsonPath))
+
+ if packageJson["devDependencies"]["webpack"] == nil,
+ do:
+ Mix.raise("""
+ Reaxt requires webpack as a devDependency in #{packageJsonPath}.
+ Add a dependency to 'webpack' like:
+
+ {
+ devDependencies: {
+ "webpack": "^1.4.13"
+ }
+ }
+ """)
+
+ good_compilers = [:reaxt_webpack, :reaxt_esbuild]
+
+ if Enum.all?(args, &(&1 != "--reaxt-skip-compiler-check")) and
+ not Enum.any?(Mix.Project.get!().project[:compilers], &(&1 in good_compilers)),
+ do:
+ Mix.raise("""
+ Reaxt has a built in compiler that compiles the web app.
+ Remember to add it to the list of compilers in mix.exs:
+
+ def project do
+ [...
+ app: :your_app,
+ compilers: [:reaxt_webpack] ++ Mix.compilers,
+ ...]
+ end
+ """)
+ end
+end
diff --git a/lib/utils.ex b/lib/utils.ex
new file mode 100644
index 0000000..46beeb9
--- /dev/null
+++ b/lib/utils.ex
@@ -0,0 +1,50 @@
+defmodule Reaxt.Utils do
+ @moduledoc """
+
+ """
+
+ @doc "Return the priv directory of the otp_app using Reaxt"
+ def web_priv do
+ case Application.get_env(:reaxt, :otp_app, :no_app_specified) do
+ :no_app_specified ->
+ :no_app_specified
+
+ web_app ->
+ :code.priv_dir(web_app)
+ end
+ end
+
+ @doc "Return the name of the directory containing the Web application"
+ def web_app do
+ Application.get_env(:reaxt, :web_app, "web")
+ end
+
+ @doc "Return the bundler used by Reaxt. Default is :webpack"
+ def bundler() do
+ Application.get_env(:reaxt, :bundler, :webpack)
+ end
+
+ @doc "Return true if the configured bundler is webpack"
+ def is_webpack?() do
+ bundler() == :webpack
+ end
+
+ @doc "Return true if the Hot reload capacity is enabled"
+ def is_hot?() do
+ Application.get_env(:reaxt, :hot, false)
+ end
+
+ @doc "Return the path to the react_servers directory"
+ def server_dir() do
+ Application.get_env(:reaxt, :server_dir, "react_servers")
+ end
+
+ @doc "Return the size of the Poolboy pool for SSR"
+ def pool_size() do
+ Application.get_env(:reaxt, :pool_size, 1)
+ end
+
+ def max_pool_overflow() do
+ Application.get_env(:reaxt, :pool_max_overflow, 5)
+ end
+end
diff --git a/lib/webpack.ex b/lib/webpack.ex
deleted file mode 100644
index aec51d3..0000000
--- a/lib/webpack.ex
+++ /dev/null
@@ -1,226 +0,0 @@
-defmodule WebPack.Events do
- def child_spec(_) do
- Registry.child_spec(keys: :duplicate, name: __MODULE__)
- end
-
- @dispatch_key :events
- def register! do
- {:ok, _} = Registry.register(__MODULE__,@dispatch_key,nil)
- end
- def dispatch(event) do
- Registry.dispatch(__MODULE__, @dispatch_key, fn entries ->
- for {pid, nil} <- entries, do: send(pid,{:event,event})
- end)
- end
-
- import Plug.Conn
- def stream_chunks(conn) do
- register!()
- conn = Stream.repeatedly(fn-> receive do {:event,event}-> event end end)
- |> Enum.reduce_while(conn, fn event, conn ->
- io = "event: #{event.event}\ndata: #{Poison.encode!(event)}\n\n"
- case chunk(conn,io) do {:ok,conn}->{:cont,conn};{:error,:closed}->{:halt,conn} end
- end)
- halt(conn)
- end
-end
-
-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: :reaxt
- 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(:reaxt,:hot) do
- try do
- :ok = GenServer.call(WebPack.EventManager,:wait?,30_000)
- catch
- :exit,{:timeout,_} -> :ok
- end
- 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(:reaxt,:hot)
- if hot? == :client, do: chunk(conn, "event: hot\ndata: nothing\n\n")
- if hot? do WebPack.Events.stream_chunks(conn) else 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
-
-end
-
-defmodule WebPack.StartBlocker do
- @moduledoc """
- this process blocks application start to ensure that when the next application starts
- the reaxt render is ready (js is compiled)
- """
- def child_spec(arg) do
- %{id: __MODULE__, start: {__MODULE__, :start_link, [arg]}, restart: :temporary}
- end
- def start_link(timeout) do :proc_lib.start_link(__MODULE__,:wait, [timeout]) end
- def wait(timeout) do
- :ok = GenServer.call(WebPack.EventManager,:wait?,timeout)
- :proc_lib.init_ack({:ok,self()})
- end
-end
-
-defmodule WebPack.EventManager do
- use GenServer
- require Logger
- def start_link(_) do GenServer.start_link(__MODULE__,[], name: __MODULE__) end
-
- def init([]) do
- WebPack.Events.register!()
- {:ok,%{init: true,pending: [], compiling: false, compiled: false}}
- end
-
- def handle_call(:wait?,_from,%{compiling: false}=state), do:
- {:reply,:ok,state}
- def handle_call(:wait?,from,state), do:
- {:noreply,%{state|pending: [from|state.pending]}}
-
- def handle_info({:event,%{event: "client_done"}=ev},state) do
- Logger.info("[reaxt-webpack] client done, build_stats")
- WebPack.Util.build_stats
- if(!state.init) do
- Logger.info("[reaxt-webpack] client done, restart servers")
- :ok = Supervisor.terminate_child(Reaxt.App, Reaxt.App.PoolsSup)
- {:ok,_} = Supervisor.restart_child(Reaxt.App, Reaxt.App.PoolsSup)
- end
- _ = case ev do
- %{error: "soft fail", error_details: details} ->
- _ = Logger.error("[reaxt-webpack] soft fail compiling server_side JS")
- _ = Enum.each(details.errors, fn
- bin when is_binary(bin) -> Logger.error(bin)
- %{message: bin} when is_binary(bin) -> Logger.error(bin)
- end)
-
- %{error: other} ->
- _ = Logger.error("[reaxt-webpack] error compiling server_side JS : #{other}")
- _ = System.halt(1)
-
- _ -> :ok
- end
- for {_idx,build}<-WebPack.stats, error<-build.errors, do: Logger.warning(error)
- for {_idx,build}<-WebPack.stats, warning<-build.warnings, do: Logger.warning(warning)
- {:noreply,done(state)}
- end
-
- def handle_info({:event,%{event: "client_invalid"}},%{compiling: false}=state) do
- Logger.info("[reaxt-webpack] detect client file change")
- {:noreply,%{state|compiling: true, compiled: false}}
- end
- def handle_info({:event,%{event: "done"}},state) do
- Logger.info("[reaxt-webpack] both done !")
- {:noreply,state}
- end
- def handle_info({:event,ev},state) do
- Logger.info("[reaxt-webpack] event : #{ev[:event]}")
- {:noreply,state}
- end
-
- def done(state) do
- for from<-state.pending do GenServer.reply(from,:ok) end
- WebPack.Events.dispatch(%{event: "done"})
- %{state| pending: [], init: false, compiling: false, compiled: true}
- end
-end
-
-defmodule WebPack.Compiler do
- def child_spec(arg) do
- %{id: __MODULE__, start: {__MODULE__, :start_link, [arg]} }
- end
- def start_link(_) do
- cmd = "node ./node_modules/reaxt/webpack_server #{WebPack.Util.webpack_config}"
- hot_arg = if Application.get_env(:reaxt,:hot) == :client, do: " hot",else: ""
- Exos.Proc.start_link(cmd<>hot_arg,[],[cd: WebPack.Util.web_app],[name: __MODULE__],&WebPack.Events.dispatch/1)
- end
-end
-
-defmodule WebPack.Util do
- def webpack_config do
- Application.get_env(:reaxt,:webpack_config,"webpack.config.js")
- end
-
- def web_priv do
- case Application.get_env :reaxt, :otp_app, :no_app_specified do
- :no_app_specified -> :no_app_specified
- web_app -> :code.priv_dir(web_app)
- end
- end
-
- def web_app do
- Application.get_env :reaxt, :web_app, "web"
- end
-
- def build_stats do
- if File.exists?("#{web_priv()}/webpack.stats.json") do
- all_stats = Poison.decode!(File.read!("#{web_priv()}/webpack.stats.json"))
- stats_array = all_stats["children"]
- stats = Enum.map(stats_array,fn stats->
- %{assetsByChunkName: stats["assetsByChunkName"],
- errors: stats["errors"],
- warnings: stats["warnings"]}
- end)
- _ = Code.compiler_options(ignore_module_conflict: true)
- defmodule Elixir.WebPack do
- @stats stats
- def stats, do: @stats
- def file_of(name) do
- r = Enum.find_value(WebPack.stats, fn %{assetsByChunkName: assets}-> assets["#{name}"] end)
- case r do
- [f|_]->f
- f -> f
- end
- end
- @header_script if(Application.get_env(:reaxt,:hot), do: ~s())
- @header_global Poison.encode!(Application.get_env(:reaxt,:global_config))
- def header, do:
- "\n#{@header_script}"
- end
- _ = Code.compiler_options(ignore_module_conflict: false)
- end
- end
-end
-
-defmodule Elixir.WebPack do
- def stats, do: %{assetsByChunkName: %{}}
- def file_of(_), do: nil
- def header, do: ""
-end
diff --git a/lib/webpack/tasks.webpack.ex b/lib/webpack/tasks.webpack.ex
new file mode 100644
index 0000000..5612c29
--- /dev/null
+++ b/lib/webpack/tasks.webpack.ex
@@ -0,0 +1,90 @@
+defmodule Mix.Tasks.Webpack.Analyseapp do
+ use Mix.Task
+
+ @shortdoc "Generate webpack stats analysing application, resulting priv/static is meant to be versionned"
+ def run(_args) do
+ File.rm_rf!("priv/static")
+
+ {_, 0} =
+ System.cmd("git", ["clone", "-b", "ajax-sse-loading", "https://github.com/awetzel/analyse"],
+ into: IO.stream(:stdio, :line)
+ )
+
+ {_, 0} = System.cmd("npm", ["install"], into: IO.stream(:stdio, :line), cd: "analyse")
+ {_, 0} = System.cmd("grunt", [], into: IO.stream(:stdio, :line), cd: "analyse")
+ File.cp_r!("analyse/dist", "priv/static")
+ File.rm_rf!("analyse")
+ end
+end
+
+defmodule Mix.Tasks.Webpack.Compile do
+ use Mix.Task
+
+ @shortdoc "Compiles Webpack"
+ @webpack "./node_modules/webpack/bin/webpack.js"
+
+ def run(_) do
+ case compile() do
+ {json, 0} ->
+ File.write!("priv/webpack.stats.json", json)
+ {:ok, []}
+
+ {ret, x} when x in [1, 2] ->
+ require Logger
+
+ ret
+ |> Poison.decode!()
+ |> Map.fetch!("errors")
+ |> Enum.map(fn
+ bin when is_binary(bin) -> Logger.error(bin)
+ %{"message" => bin} when is_binary(bin) -> Logger.error(bin)
+ end)
+
+ {:error, []}
+ end
+ end
+
+ def compile() do
+ config = "./" <> Reaxt.Webpack.webpack_config()
+ webpack = @webpack
+
+ System.cmd(
+ "node",
+ [webpack, "--config", config, "--json"],
+ into: "",
+ cd: Reaxt.Utils.web_app(),
+ env: [{"MIX_ENV", "#{Mix.env()}"}]
+ )
+ end
+end
+
+defmodule Mix.Tasks.Compile.ReaxtWebpack do
+ use Mix.Task.Compiler
+
+ def run(args) do
+ IO.puts("[Reaxt] Running Webpack compiler...")
+ Mix.Task.run("reaxt.validate", args ++ ["--reaxt-skip-compiler-check"])
+
+ if !File.exists?(Path.join(Reaxt.Utils.web_app(), "node_modules")) do
+ Mix.Task.run("npm.install", args)
+ else
+ installed_version =
+ Poison.decode!(File.read!("#{Reaxt.Utils.web_app()}/node_modules/reaxt/package.json"))[
+ "version"
+ ]
+
+ current_version =
+ Poison.decode!(File.read!("#{:code.priv_dir(:reaxt)}/commonjs_reaxt/package.json"))[
+ "version"
+ ]
+
+ if installed_version !== current_version, do: Mix.Task.run("npm.install", args)
+ end
+
+ if !Application.get_env(:reaxt, :hot) do
+ Mix.Task.run("webpack.compile", args)
+ else
+ {:ok, []}
+ end
+ end
+end
diff --git a/lib/webpack/webpack.ex b/lib/webpack/webpack.ex
new file mode 100644
index 0000000..e513195
--- /dev/null
+++ b/lib/webpack/webpack.ex
@@ -0,0 +1,238 @@
+defmodule Reaxt.Webpack do
+ @moduledoc """
+ Utilities functions to fetch specific Webpack configs
+ """
+
+ def webpack_config do
+ Application.get_env(:reaxt, :webpack_config, "webpack.config.js")
+ end
+end
+
+defmodule WebPack.Hot.Events do
+ def child_spec(_) do
+ Registry.child_spec(keys: :duplicate, name: __MODULE__)
+ end
+
+ @dispatch_key :events
+ def register! do
+ {:ok, _} = Registry.register(__MODULE__, @dispatch_key, nil)
+ end
+
+ def dispatch(event) do
+ Registry.dispatch(__MODULE__, @dispatch_key, fn entries ->
+ for {pid, nil} <- entries, do: send(pid, {:event, event})
+ end)
+ end
+
+ import Plug.Conn
+
+ def stream_chunks(conn) do
+ register!()
+
+ conn =
+ Stream.repeatedly(fn ->
+ receive do
+ {:event, event} -> event
+ end
+ end)
+ |> Enum.reduce_while(conn, fn event, conn ->
+ io = "event: #{event.event}\ndata: #{Poison.encode!(event)}\n\n"
+
+ case chunk(conn, io) do
+ {:ok, conn} -> {:cont, conn}
+ {:error, :closed} -> {:halt, conn}
+ end
+ end)
+
+ halt(conn)
+ end
+end
+
+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: :reaxt
+ 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(:reaxt, :hot) do
+ try do
+ :ok = GenServer.call(WebPack.Hot.EventManager, :wait?, 30_000)
+ catch
+ :exit, {:timeout, _} -> :ok
+ end
+ 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, "#{Reaxt.Utils.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? = Reaxt.Utils.is_hot?()
+ if hot? == :client, do: chunk(conn, "event: hot\ndata: nothing\n\n")
+
+ if hot? do
+ WebPack.Hot.Events.stream_chunks(conn)
+ else
+ conn
+ end
+ end
+
+ get "/webpack/client.js" do
+ conn
+ |> put_resp_content_type("application/javascript")
+ |> send_file(200, "#{Reaxt.Utils.web_app()}/node_modules/reaxt/webpack_client.js")
+ |> halt
+ end
+
+ match(_, do: conn)
+end
+
+defmodule WebPack.StartBlocker do
+ @moduledoc """
+ this process blocks application start to ensure that when the next application starts
+ the reaxt render is ready (js is compiled)
+ """
+ def child_spec(arg) do
+ %{id: __MODULE__, start: {__MODULE__, :start_link, [arg]}, restart: :temporary}
+ end
+
+ def start_link(timeout) do
+ :proc_lib.start_link(__MODULE__, :wait, [timeout])
+ end
+
+ def wait(timeout) do
+ :ok = GenServer.call(WebPack.EventManager, :wait?, timeout)
+ :proc_lib.init_ack({:ok, self()})
+ end
+end
+
+defmodule WebPack.Hot.EventManager do
+ use GenServer
+ require Logger
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ def init([]) do
+ WebPack.Hot.Events.register!()
+ {:ok, %{init: true, pending: [], compiling: false, compiled: false}}
+ end
+
+ def handle_call(:wait?, _from, %{compiling: false} = state), do: {:reply, :ok, state}
+ def handle_call(:wait?, from, state), do: {:noreply, %{state | pending: [from | state.pending]}}
+
+ def handle_info({:event, %{event: "client_done"} = ev}, state) do
+ Logger.info("[reaxt-webpack] client done, build_stats")
+ _ = Reaxt.Index.Generator.build_webpack_stats()
+
+ if(!state.init) do
+ Logger.info("[reaxt-webpack] client done, restart servers")
+ :ok = Supervisor.terminate_child(Reaxt.App, Reaxt.PoolsSup)
+ {:ok, _} = Supervisor.restart_child(Reaxt.App, Reaxt.PoolsSup)
+ end
+
+ _ =
+ case ev do
+ %{error: "soft fail", error_details: details} ->
+ _ = Logger.error("[reaxt-webpack] soft fail compiling server_side JS")
+
+ _ =
+ Enum.each(details.errors, fn
+ bin when is_binary(bin) -> Logger.error(bin)
+ %{message: bin} when is_binary(bin) -> Logger.error(bin)
+ end)
+
+ %{error: other} ->
+ _ = Logger.error("[reaxt-webpack] error compiling server_side JS : #{other}")
+ _ = System.halt(1)
+
+ _ ->
+ :ok
+ end
+
+ for {_idx, build} <- Reaxt.Index.stats(), error <- build.errors, do: Logger.warning(error)
+
+ for {_idx, build} <- Reaxt.Index.stats(),
+ warning <- build.warnings,
+ do: Logger.warning(warning)
+
+ {:noreply, done(state)}
+ end
+
+ def handle_info({:event, %{event: "client_invalid"}}, %{compiling: false} = state) do
+ Logger.info("[reaxt-webpack] detect client file change")
+ {:noreply, %{state | compiling: true, compiled: false}}
+ end
+
+ def handle_info({:event, %{event: "done"}}, state) do
+ Logger.info("[reaxt-webpack] both done !")
+ {:noreply, state}
+ end
+
+ def handle_info({:event, ev}, state) do
+ Logger.info("[reaxt-webpack] event : #{ev[:event]}")
+ {:noreply, state}
+ end
+
+ def done(state) do
+ for from <- state.pending do
+ GenServer.reply(from, :ok)
+ end
+
+ WebPack.Hot.Events.dispatch(%{event: "done"})
+ %{state | pending: [], init: false, compiling: false, compiled: true}
+ end
+end
+
+defmodule WebPack.Hot.Compiler do
+ def child_spec(arg) do
+ %{id: __MODULE__, start: {__MODULE__, :start_link, [arg]}}
+ end
+
+ def start_link(_) do
+ cmd = "node ./node_modules/reaxt/webpack_hot_server #{Reaxt.Webpack.webpack_config()}"
+ hot_arg = if Application.get_env(:reaxt, :hot) == :client, do: " hot", else: ""
+
+ Exos.Proc.start_link(
+ cmd <> hot_arg,
+ [],
+ [cd: Reaxt.Utils.web_app()],
+ [name: __MODULE__],
+ &WebPack.Hot.Events.dispatch/1
+ )
+ end
+end
diff --git a/mix.exs b/mix.exs
index cb84dd6..9636eec 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,7 +1,7 @@
defmodule Reaxt.Mixfile do
use Mix.Project
- def version, do: "4.1.0"
+ def version, do: "5.0.0"
defp description do
"""
@@ -18,29 +18,22 @@ defmodule Reaxt.Mixfile do
elixir: "~> 1.12",
deps: deps(),
docs: docs(),
- source_url: git_repository(),
+ source_url: git_repository()
]
end
def application do
- [applications: [:logger, :poolboy, :exos, :plug, :poison],
- mod: {Reaxt.App,[]},
- env: [
- otp_app: :reaxt, #the OTP application containing compiled JS server
- hot: false, # false | true | :client hot compilation and loading
- pool_size: 1, #pool size of react renderes
- webpack_config: "webpack.config.js",
- server_dir: "react_servers",
- pool_max_overflow: 5 #maximum pool extension when the pool is full
- ]]
+ [applications: [:logger, :poolboy, :exos, :plug, :poison], mod: {Reaxt.App, []}]
end
defp deps do
- [{:exos, "~> 2.0"},
- {:poolboy, "~> 1.5"},
- {:plug, "~> 1.15"},
- {:poison,"~> 5.0"},
- {:ex_doc, "~> 0.31", only: :dev, runtime: false}]
+ [
+ {:exos, "~> 2.0"},
+ {:poolboy, "~> 1.5"},
+ {:plug, "~> 1.15"},
+ {:poison, "~> 5.0"},
+ {:ex_doc, "~> 0.31", only: :dev, runtime: false}
+ ]
end
defp package do
@@ -48,9 +41,9 @@ defmodule Reaxt.Mixfile do
licenses: ["The MIT License (MIT)"],
links: %{
"GitHub" => git_repository(),
- "Changelog" => "https://hexdocs.pm/reaxt/changelog.html",
+ "Changelog" => "https://hexdocs.pm/reaxt/changelog.html"
},
- maintainers: ["Arnaud Wetzel"],
+ maintainers: ["Arnaud Wetzel"]
]
end
@@ -58,12 +51,12 @@ defmodule Reaxt.Mixfile do
[
extras: [
"CHANGELOG.md": [title: "Changelog"],
- "README.md": [title: "Overview"],
+ "README.md": [title: "Overview"]
],
api_reference: false,
main: "readme",
source_url: git_repository(),
- source_ref: "v#{version()}",
+ source_ref: "v#{version()}"
]
end
diff --git a/priv/commonjs_reaxt/client_entry_addition.js b/priv/commonjs_reaxt/client_entry_addition.js
index 449bb4c..ebf9649 100644
--- a/priv/commonjs_reaxt/client_entry_addition.js
+++ b/priv/commonjs_reaxt/client_entry_addition.js
@@ -6,7 +6,7 @@ function default_client_render(props,render,param){
}
window.reaxt_render = function(module,submodule,props,param){
- return import(`./../../components/${module}`).then((module)=>{
+ return import(`./../../components/${module}.js`).then((module)=>{
module = module.default
submodule = (submodule) ? module[submodule] :module
submodule.reaxt_client_render = submodule.reaxt_client_render || default_client_render
diff --git a/priv/commonjs_reaxt/react_server.js b/priv/commonjs_reaxt/react_server.js
index a5ad7dc..8fe71f1 100644
--- a/priv/commonjs_reaxt/react_server.js
+++ b/priv/commonjs_reaxt/react_server.js
@@ -57,7 +57,7 @@ Server(function(term,from,state,done){
done("reply",Bert.tuple(Bert.atom("error"),Bert.tuple(Bert.atom("handler_error"),module,submodule,args,"timeout",Bert.atom("nil"))))
},timeout)
- import(`./../../components/${module}`).then((handler)=>{
+ import(`./../../components/${module}.js`).then((handler)=>{
handler = handler.default
submodule = (submodule == "nil") ? undefined : submodule
handler = (!submodule) ? handler : handler[submodule]
diff --git a/priv/commonjs_reaxt/webpack_server.js b/priv/commonjs_reaxt/webpack_hot_server.js
similarity index 77%
rename from priv/commonjs_reaxt/webpack_server.js
rename to priv/commonjs_reaxt/webpack_hot_server.js
index 3b11749..574c980 100644
--- a/priv/commonjs_reaxt/webpack_server.js
+++ b/priv/commonjs_reaxt/webpack_hot_server.js
@@ -4,15 +4,6 @@ var webpack = require("webpack"),
var multi_config = require(process.cwd()+"/"+process.argv[2])
-//if(process.argv[2] === "hot"){
-// // add hotmodule plugin to client
-// client_config.plugins = (client_config.plugins || []).concat([new webpack.HotModuleReplacementPlugin()])
-// // add reloading code to entries
-// client_config.add_to_entries(client_config,"webpack/hot/dev-server")
-// // remove external which cause conflicts in hot loading
-// client_config.externals = {}
-//}
-
var client_stats,client_err
function maybe_done() {
if(client_err) port.write({event: "client_done", error: JSON.stringify(client_err)})