Packages
surface
0.3.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha.0
A component based library for Phoenix LiveView
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/compile/surface.ex
defmodule Mix.Tasks.Compile.Surface do
@moduledoc """
Generate CSS and JS assets for components.
"""
use Mix.Task
@recursive true
@default_hooks_output_dir "assets/js/_hooks"
@hooks_extension ".hooks.js"
@doc false
def run(_args) do
case generate_files() do
"" -> {:noop, []}
_ -> {:ok, []}
end
end
defp generate_files() do
opts = Application.get_env(:surface, :compiler, [])
hooks_output_dir = Keyword.get(opts, :hooks_output_dir, @default_hooks_output_dir)
js_output_dir = Path.join([File.cwd!(), hooks_output_dir])
index_file = Path.join([js_output_dir, "index.js"])
{js_files, _css_files} = get_colocated_assets()
File.mkdir_p!(js_output_dir)
delete_unused_hooks_files!(js_output_dir, js_files)
index_file_time =
case File.stat(index_file) do
{:ok, %File.Stat{mtime: time}} -> time
_ -> nil
end
update_index? =
for {src_file, dest_file} <- js_files,
{:ok, %File.Stat{mtime: time}} <- [File.stat(src_file)],
!index_file_time or !File.exists?(dest_file) or time > index_file_time,
reduce: false do
_ ->
File.cp!(src_file, Path.join(js_output_dir, dest_file))
true
end
if update_index? do
File.write!(index_file, index_content(js_files))
end
end
defp get_colocated_assets() do
for mod <- Mix.Phoenix.modules(),
Code.ensure_loaded?(mod),
function_exported?(mod, :component_type, 0),
reduce: {[], []} do
{js_files, css_files} ->
base_file = mod.module_info() |> get_in([:compile, :source]) |> Path.rootname()
js_file = "#{base_file}#{@hooks_extension}"
base_name = inspect(mod)
dest_js_file = "#{base_name}#{@hooks_extension}"
css_file = "#{base_file}.css"
dest_css_file = "#{base_name}.css"
js_files =
if File.exists?(js_file), do: [{js_file, dest_js_file} | js_files], else: js_files
css_files =
if File.exists?(css_file), do: [{css_file, dest_css_file} | css_files], else: css_files
{js_files, css_files}
end
end
defp index_content([]) do
"""
/* This file was generated by the Surface compiler */
export default {}
"""
end
defp index_content(js_files) do
files = js_files |> Enum.sort() |> Enum.with_index(1)
{hooks, imports} =
for {{_file, dest_file}, index} <- files, reduce: {[], []} do
{hooks, imports} ->
namespace = Path.basename(dest_file, @hooks_extension)
var = "c#{index}"
hook = ~s[ns(#{var}, "#{namespace}")]
imp = ~s[import * as #{var} from "./#{namespace}.hooks"]
{[hook | hooks], [imp | imports]}
end
hooks = Enum.reverse(hooks)
imports = Enum.reverse(imports)
"""
/* This file was generated by the Surface compiler */
function ns(hooks, nameSpace) {
const updatedHooks = {}
Object.keys(hooks).map(function(key) {
updatedHooks[`${nameSpace}#${key}`] = hooks[key]
})
return updatedHooks
}
#{Enum.join(imports, "\n")}
let hooks = Object.assign(
#{Enum.join(hooks, ",\n ")}
)
export default hooks
"""
end
defp delete_unused_hooks_files!(js_output_dir, js_files) do
used_files = Enum.map(js_files, fn {_, dest_file} -> Path.join(js_output_dir, dest_file) end)
all_files =
js_output_dir
|> Path.join("*#{@hooks_extension}")
|> Path.wildcard()
unsused_files = all_files -- used_files
Enum.each(unsused_files, &File.rm!/1)
end
end