From c733b373898977cd0b63600a793a6274363b1a89 Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Sat, 21 Jan 2017 23:01:39 +0200 Subject: [PATCH] Use totally new build system written in Elixir This allows for fewer npm dependencies and fewer packages to handle (some of the currently installed babel packages are for possible future use). Hopefully the new build style is more explicit even though it is more verbose. Both building in dev and prod mode and watching is supported. --- .babelrc | 7 + .gitignore | 2 +- README.md | 2 +- gulpfile.js | 76 ----- lib/mebe_web/endpoint.ex | 1 + lib/mix/tasks/apprunner.exs | 72 +++++ lib/mix/tasks/frontend.build.assets.ex | 14 + lib/mix/tasks/frontend.build.css.compile.ex | 31 ++ lib/mix/tasks/frontend.build.css.ex | 15 + lib/mix/tasks/frontend.build.css.minify.ex | 30 ++ lib/mix/tasks/frontend.build.ex | 16 + lib/mix/tasks/frontend.build.js.bundle.ex | 37 +++ lib/mix/tasks/frontend.build.js.ex | 15 + lib/mix/tasks/frontend.build.js.minify.ex | 38 +++ lib/mix/tasks/frontend.build.js.transpile.ex | 23 ++ lib/mix/tasks/frontend.clean.ex | 11 + lib/mix/tasks/frontend.watch.ex | 33 ++ lib/mix/tasks/frontend_confs.ex | 34 ++ lib/mix/tasks/utils.ex | 311 +++++++++++++++++++ package.json | 35 +-- rollup.config.js | 7 + web/static/js/app.js | 7 + web/static/js/old_hash_redirector.js | 14 +- web/templates/layout/app.html.eex | 1 - 24 files changed, 727 insertions(+), 105 deletions(-) create mode 100644 .babelrc delete mode 100644 gulpfile.js create mode 100644 lib/mix/tasks/apprunner.exs create mode 100644 lib/mix/tasks/frontend.build.assets.ex create mode 100644 lib/mix/tasks/frontend.build.css.compile.ex create mode 100644 lib/mix/tasks/frontend.build.css.ex create mode 100644 lib/mix/tasks/frontend.build.css.minify.ex create mode 100644 lib/mix/tasks/frontend.build.ex create mode 100644 lib/mix/tasks/frontend.build.js.bundle.ex create mode 100644 lib/mix/tasks/frontend.build.js.ex create mode 100644 lib/mix/tasks/frontend.build.js.minify.ex create mode 100644 lib/mix/tasks/frontend.build.js.transpile.ex create mode 100644 lib/mix/tasks/frontend.clean.ex create mode 100644 lib/mix/tasks/frontend.watch.ex create mode 100644 lib/mix/tasks/frontend_confs.ex create mode 100644 lib/mix/tasks/utils.ex create mode 100644 rollup.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..e77bf0b --- /dev/null +++ b/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": [ + ["es2015", {"modules": false}], + "es2016", + "es2017" + ] +} diff --git a/.gitignore b/.gitignore index 81fba74..bdcd46f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ config/*.exs # we ignore priv/static/{css,js}. You may want to # comment this depending on your deployment strategy. /priv/static/ +/.tmp/ # Custom templates should be ignored /web/templates/custom/ - diff --git a/README.md b/README.md index a892a70..75445ef 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The engine consists of two parts: * `git clone` * Copy `config/*.exs.dist`, removing the `.dist` ending and go through the configs. -* `npm install && gulp` to build the frontend. +* `npm install && mix frontend.build` to build the frontend. * Put some content into the data path you specified, at least a `menu` file. * `mix phoenix.server` to run the development server. diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 0afee07..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,76 +0,0 @@ -// Node modules -var fs = require('fs'), vm = require('vm'), chalk = require('chalk'); - -// Gulp and plugins -var gulp = require('gulp'), concat = require('gulp-concat'), - replace = require('gulp-replace'), uglify = require('gulp-uglify'); - -// Gulp minify for smallinizing our CSS -var minify = require('gulp-minify-css'); - -// Gulp filesize for printing sizes before and after minification -var size = require('gulp-size'); - -// SASS compiler -var sass = require('gulp-sass'); - -// Deleting files -var vinylPaths = require('vinyl-paths'); -var del = require('del'); - -// Source maps -var sourcemaps = require('gulp-sourcemaps'); - -var node_path = 'node_modules/'; - -var dest_path = 'priv/static/'; - -// Minifies all JS files and copies to target path. Files are not concatenated -gulp.task('js', function() { - return gulp.src([ - // Uncomment these two lines if you need bootstrap or jQuery - //node_path + 'jquery/dist/jquery.js', - //node_path + 'bootstrap-sass/assets/javascripts/bootstrap.js', - 'web/static/js/*.js' - ]) - - //.pipe(concat('app.js')) - .pipe(sourcemaps.init()) - .pipe(size({title: 'Original JS'})) - .pipe(uglify({ preserveComments: false }).on('error', console.error.bind(console))) - .pipe(size({title: 'Minified JS'})) - .pipe(sourcemaps.write('./')) - .pipe(gulp.dest(dest_path + 'js/')); -}); - -// Compiles SASS files. Will create one output file for each input file, but usually you -// only have app.scss which will be output as app.css -gulp.task('css', function() { - return gulp.src([ - 'web/static/css/*.scss' - ]) - .pipe(sass().on('error', sass.logError)) - .pipe(sourcemaps.init()) - .pipe(size({title: 'Compiled CSS'})) - .pipe(minify()) - .pipe(size({title: 'Minified CSS'})) - .pipe(sourcemaps.write('./')) - .pipe(gulp.dest(dest_path + 'css/')); - }); - -// Copies fonts -gulp.task('fonts', function() { - return gulp.src(node_path + 'bootstrap-sass/assets/fonts/bootstrap/*') - .pipe(gulp.dest(dest_path + 'fonts/')); -}); - -// Removes all files from dest_path -gulp.task('clean', function() { - return gulp.src(dest_path + '**/*', { read: false }) - .pipe(vinylPaths(del)); -}); - -gulp.task('default', ['js', 'css', 'fonts'], function(callback) { - callback(); - console.log('\nPlaced optimized files in ' + chalk.magenta(dest_path)); -}); diff --git a/lib/mebe_web/endpoint.ex b/lib/mebe_web/endpoint.ex index 81838b4..44af77e 100644 --- a/lib/mebe_web/endpoint.ex +++ b/lib/mebe_web/endpoint.ex @@ -9,6 +9,7 @@ defmodule MebeWeb.Endpoint do # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket plug Phoenix.LiveReloader plug Phoenix.CodeReloader end diff --git a/lib/mix/tasks/apprunner.exs b/lib/mix/tasks/apprunner.exs new file mode 100644 index 0000000..12d2a7e --- /dev/null +++ b/lib/mix/tasks/apprunner.exs @@ -0,0 +1,72 @@ +defmodule AppRunner do + def wait_for_eof() do + case IO.getn("", 1024) do + :eof -> nil + _ -> wait_for_eof() + end + end + + def exec(program, args) do + Port.open( + {:spawn_executable, program}, + [ + :exit_status, + :stderr_to_stdout, # Redirect stderr to stdout to log properly + args: args, + line: 1024 + ] + ) + end + + def get_pid(port) do + case Port.info(port) do + nil -> + nil + + info when is_list(info) -> + case Keyword.get(info, :os_pid) do + nil -> nil + pid -> Integer.to_string(pid) + end + end + end + + def kill(pid) do + System.find_executable("kill") |> System.cmd([pid]) + end + + def wait_loop(port) do + receive do + {_, {:data, {:eol, msg}}} -> + msg |> :unicode.characters_to_binary(:unicode) |> IO.puts() + + {_, {:data, {:noeol, msg}}} -> + msg |> :unicode.characters_to_binary(:unicode) |> IO.write() + + {_, :eof_received} -> + get_pid(port) |> kill() + :erlang.halt(0) + + {_, :closed} -> + :erlang.halt(0) + + {_, {:exit_status, status}} -> + :erlang.halt(status) + + {:EXIT, _, _} -> + :erlang.halt(1) + end + + wait_loop(port) + end +end + +[program | args] = System.argv() +port = AppRunner.exec(program, args) + +Task.async(fn -> + AppRunner.wait_for_eof() + :eof_received +end) + +AppRunner.wait_loop(port) diff --git a/lib/mix/tasks/frontend.build.assets.ex b/lib/mix/tasks/frontend.build.assets.ex new file mode 100644 index 0000000..172d444 --- /dev/null +++ b/lib/mix/tasks/frontend.build.assets.ex @@ -0,0 +1,14 @@ +defmodule Mix.Tasks.Frontend.Build.Assets do + use Mix.Task + import MebeWeb.{FrontendConfs} + + @shortdoc "Copy other assets to target dir" + + def run(_) do + # Ensure target path exists + out_path = "#{dist_path()}/fonts" + + File.mkdir_p!(out_path) + File.cp_r!("#{node_path()}/bootstrap-sass/assets/fonts/bootstrap", out_path) + end +end diff --git a/lib/mix/tasks/frontend.build.css.compile.ex b/lib/mix/tasks/frontend.build.css.compile.ex new file mode 100644 index 0000000..3fe3a79 --- /dev/null +++ b/lib/mix/tasks/frontend.build.css.compile.ex @@ -0,0 +1,31 @@ +defmodule Mix.Tasks.Frontend.Build.Css.Compile do + use Mix.Task + import MebeWeb.{TaskUtils, FrontendConfs} + + @shortdoc "Build the SCSS sources" + + def bin(), do: node_bin("node-sass") + + def out_path(:prod), do: "#{tmp_path()}/compiled/css" + def out_path(_), do: "#{dist_path()}/css" + + def args(), do: [ + "-o", + out_path(Mix.env()), + "--source-map", + "true", + "--include-path", + "#{node_path()}/bootstrap-sass/assets/stylesheets", + "--precision", + "8" + ] + + def scss_file(), do: "#{src_path()}/css/app.scss" + + def run(_) do + exec( + bin(), + args() ++ [scss_file()] + ) |> listen() + end +end diff --git a/lib/mix/tasks/frontend.build.css.ex b/lib/mix/tasks/frontend.build.css.ex new file mode 100644 index 0000000..9af60fd --- /dev/null +++ b/lib/mix/tasks/frontend.build.css.ex @@ -0,0 +1,15 @@ +defmodule Mix.Tasks.Frontend.Build.Css do + use Mix.Task + import MebeWeb.TaskUtils + + @shortdoc "Build the frontend CSS" + + def run(_) do + js_task = case Mix.env() do + :prod -> "frontend.build.css.minify" + _ -> "frontend.build.css.compile" + end + + run_task(js_task) + end +end diff --git a/lib/mix/tasks/frontend.build.css.minify.ex b/lib/mix/tasks/frontend.build.css.minify.ex new file mode 100644 index 0000000..40dc217 --- /dev/null +++ b/lib/mix/tasks/frontend.build.css.minify.ex @@ -0,0 +1,30 @@ +defmodule Mix.Tasks.Frontend.Build.Css.Minify do + use Mix.Task + import MebeWeb.{TaskUtils, FrontendConfs} + + @shortdoc "Minify built CSS files" + @preferred_cli_env :prod + + def run(_) do + run_task("frontend.build.css.compile") + + in_path = "#{tmp_path()}/compiled/css" + in_file = "#{in_path}/app.css" + out_path = "#{dist_path()}/css" + out_file = "#{out_path}/app.css" + + File.mkdir_p!(out_path) + + exec( + node_bin("cssnano"), + [ + in_file, + out_file, + "--sourcemap", + "#{out_path}/app.css.map" + ] + ) |> listen() + + print_size(out_file, in_file) + end +end diff --git a/lib/mix/tasks/frontend.build.ex b/lib/mix/tasks/frontend.build.ex new file mode 100644 index 0000000..4fe3909 --- /dev/null +++ b/lib/mix/tasks/frontend.build.ex @@ -0,0 +1,16 @@ +defmodule Mix.Tasks.Frontend.Build do + use Mix.Task + import MebeWeb.TaskUtils + + @shortdoc "Build the frontend" + + def run(_) do + run_task("frontend.clean") + + run_tasks([ + "frontend.build.js", + "frontend.build.css", + "frontend.build.assets" + ]) + end +end diff --git a/lib/mix/tasks/frontend.build.js.bundle.ex b/lib/mix/tasks/frontend.build.js.bundle.ex new file mode 100644 index 0000000..43d4dec --- /dev/null +++ b/lib/mix/tasks/frontend.build.js.bundle.ex @@ -0,0 +1,37 @@ +defmodule Mix.Tasks.Frontend.Build.Js.Bundle do + use Mix.Task + import MebeWeb.{TaskUtils, FrontendConfs} + + @shortdoc "Bundle the JavaScript sources into app.js" + + def bin(), do: node_bin("rollup") + + def out_path(:prod), do: "#{tmp_path()}/bundled/js" + def out_path(_), do: "#{dist_path()}/js" + + def args() do + op = out_path(Mix.env()) + + [ + "--config", + "rollup.config.js", + "--input", + "#{tmp_path()}/transpiled/js/app.js", + "--output", + "#{op}/app.js", + "--format", + "cjs", + "--sourcemap", + "#{op}/app.js.map" + ] + end + + def run(_) do + run_task("frontend.build.js.transpile") + + exec( + bin(), + args() + ) |> listen() + end +end diff --git a/lib/mix/tasks/frontend.build.js.ex b/lib/mix/tasks/frontend.build.js.ex new file mode 100644 index 0000000..9599785 --- /dev/null +++ b/lib/mix/tasks/frontend.build.js.ex @@ -0,0 +1,15 @@ +defmodule Mix.Tasks.Frontend.Build.Js do + use Mix.Task + import MebeWeb.TaskUtils + + @shortdoc "Build the frontend JavaScript" + + def run(_) do + js_task = case Mix.env() do + :prod -> "frontend.build.js.minify" + _ -> "frontend.build.js.bundle" + end + + run_task(js_task) + end +end diff --git a/lib/mix/tasks/frontend.build.js.minify.ex b/lib/mix/tasks/frontend.build.js.minify.ex new file mode 100644 index 0000000..0832174 --- /dev/null +++ b/lib/mix/tasks/frontend.build.js.minify.ex @@ -0,0 +1,38 @@ +defmodule Mix.Tasks.Frontend.Build.Js.Minify do + use Mix.Task + import MebeWeb.{TaskUtils, FrontendConfs} + + @shortdoc "Minify built JS files" + @preferred_cli_env :prod + + def run(_) do + run_task("frontend.build.js.bundle") + + in_path = "#{tmp_path()}/bundled/js" + in_file = "#{in_path}/app.js" + out_path = "#{dist_path()}/js" + out_file = "#{out_path}/app.js" + + File.mkdir_p!(out_path) + + exec( + node_bin("uglifyjs"), + [ + "--in-source-map", + "#{in_path}/app.js.map", + "--source-map", + "#{out_path}/app.js.map", + "--source-map-url", + "app.js.map", + "--screw-ie8", + "-m", + "-o", + out_file, + "--", + in_file + ] + ) |> listen() + + print_size(out_file, in_file) + end +end diff --git a/lib/mix/tasks/frontend.build.js.transpile.ex b/lib/mix/tasks/frontend.build.js.transpile.ex new file mode 100644 index 0000000..811ea8f --- /dev/null +++ b/lib/mix/tasks/frontend.build.js.transpile.ex @@ -0,0 +1,23 @@ +defmodule Mix.Tasks.Frontend.Build.Js.Transpile do + use Mix.Task + import MebeWeb.{TaskUtils, FrontendConfs} + + @shortdoc "Transpile JS sources to ES5" + + def bin(), do: node_bin("babel") + + def args(), do: [ + "#{src_path()}/js", + "--out-dir", + "#{tmp_path()}/transpiled/js", + "--source-maps", + "inline" + ] + + def run(_) do + exec( + bin(), + args() + ) |> listen() + end +end diff --git a/lib/mix/tasks/frontend.clean.ex b/lib/mix/tasks/frontend.clean.ex new file mode 100644 index 0000000..ba3b0f0 --- /dev/null +++ b/lib/mix/tasks/frontend.clean.ex @@ -0,0 +1,11 @@ +defmodule Mix.Tasks.Frontend.Clean do + use Mix.Task + import MebeWeb.{FrontendConfs} + + @shortdoc "Clean build artifacts" + + def run(_) do + File.rm_rf!(tmp_path()) + File.rm_rf!(dist_path()) + end +end diff --git a/lib/mix/tasks/frontend.watch.ex b/lib/mix/tasks/frontend.watch.ex new file mode 100644 index 0000000..dcfdac8 --- /dev/null +++ b/lib/mix/tasks/frontend.watch.ex @@ -0,0 +1,33 @@ +defmodule Mix.Tasks.Frontend.Watch do + use Mix.Task + import MebeWeb.{TaskUtils, FrontendConfs} + alias Mix.Tasks.Frontend.Build.Js.Transpile, as: TranspileJS + alias Mix.Tasks.Frontend.Build.Js.Bundle, as: BundleJS + alias Mix.Tasks.Frontend.Build.Css.Compile, as: CompileCSS + + @shortdoc "Watch frontend and rebuild when necessary" + + def run(_) do + run_task("frontend.build") + + [ + exec( + TranspileJS.bin(), + TranspileJS.args() ++ ["-w"] + ), + + exec( + BundleJS.bin(), + BundleJS.args() ++ ["-w"] + ), + + exec( + CompileCSS.bin(), + CompileCSS.args() ++ [ + "-w", + CompileCSS.scss_file() + ] + ) + ] |> watch() + end +end diff --git a/lib/mix/tasks/frontend_confs.ex b/lib/mix/tasks/frontend_confs.ex new file mode 100644 index 0000000..6a3902f --- /dev/null +++ b/lib/mix/tasks/frontend_confs.ex @@ -0,0 +1,34 @@ +defmodule MebeWeb.FrontendConfs do + import MebeWeb.TaskUtils + + @moduledoc """ + Project specific paths and other stuff for Mebe frontend. + """ + + @doc """ + Get absolute path to node_modules. + """ + def node_path() do + "#{proj_path()}/node_modules" + end + + @doc """ + Get absolute path to binary installed with npm. + """ + def node_bin(executable), do: "#{node_path()}/.bin/#{executable}" + + @doc """ + Get absolute path to source directory for frontend build. + """ + def src_path(), do: "#{proj_path()}/web/static" + + @doc """ + Get absolute path to temp directory for build artifacts. + """ + def tmp_path(), do: "#{proj_path()}/.tmp" + + @doc """ + Get absolute path to target directory for frontend build. + """ + def dist_path(), do: "#{proj_path()}/priv/static" +end diff --git a/lib/mix/tasks/utils.ex b/lib/mix/tasks/utils.ex new file mode 100644 index 0000000..d9c4455 --- /dev/null +++ b/lib/mix/tasks/utils.ex @@ -0,0 +1,311 @@ +defmodule MebeWeb.TaskUtils do + @moduledoc """ + Utilities for project build tasks. + """ + + require Logger + + @elixir System.find_executable("elixir") + + @default_task_timeout 60000 + + defmodule Program do + @moduledoc """ + Program to execute with arguments. Name is used for prefixing logs. + """ + defstruct [ + name: "", + port: nil, + pending_output: "" + ] + end + + @doc """ + Get configuration value. + """ + def conf(key) when is_atom(key) do + Application.get_env(:code_stats, key) + end + + @doc """ + Get path to project root directory. + """ + def proj_path() do + Path.expand("../../..", __DIR__) + end + + @doc """ + Get absolute path to a program in $PATH. + """ + def exec_path(program) do + System.find_executable(program) + end + + @doc """ + Run the given Mix task and wait for it to stop before returning. + + See run_tasks/2 for the argument description. + """ + def run_task(task, timeout \\ @default_task_timeout) do + run_tasks([task], timeout) + end + + @doc """ + Run the given Mix tasks in parallel and wait for them all to stop + before returning. + + Tasks should be tuples {task_name, args} or binaries (no args). + """ + def run_tasks(tasks, timeout \\ 60000) do + run_and_log = fn task, args -> + Logger.info("[Started] #{task}") + Mix.Task.run(task, args) + Logger.info("[Finished] #{task}") + end + + tasks + |> Enum.map(fn + task when is_binary(task) -> + fn -> run_and_log.(task, []) end + {task, args} -> + fn -> run_and_log.(task, args) end + end) + |> run_funs(timeout) + end + + @doc """ + Run the given functions in parallel and wait for them all to stop + before returning. + + Functions can either be anonymous functions or tuples of + {module, fun, args}. + """ + def run_funs(funs, timeout \\ 60000) when is_list(funs) do + funs + |> Enum.map(fn + fun when is_function(fun) -> + Task.async(fun) + {module, fun, args} -> + Task.async(module, fun, args) + end) + |> Enum.map(fn task -> + Task.await(task, timeout) + end) + end + + @doc """ + Start an external program with apprunner. + + Apprunner handles killing the program if BEAM is abruptly shut down. + Name is used as a prefix for logging output. + + Options that can be given: + + - name: Use as name for logging, otherwise name of binary is used. + - cd: Directory to change to before executing. + """ + def exec(executable, args, opts \\ []) do + name = Keyword.get( + opts, + :name, + (executable |> Path.rootname() |> Path.basename()) + ) + + options = [ + :exit_status, # Send msg with status when command stops + args: ["#{proj_path()}/lib/mix/tasks/apprunner.exs" | [executable | args]], + line: 1024 # Send command output as lines of 1k length + ] + + options = case Keyword.get(opts, :cd) do + nil -> options + cd -> Keyword.put(options, :cd, cd) + end + + Logger.debug("[Spawned] #{name}") + + %Program{ + name: name, + pending_output: "", + port: Port.open( + {:spawn_executable, @elixir}, + options + ) + } + end + + @doc """ + Listen to messages from programs and print them to the screen. + + Also listens to input from user and returns if input is given. + """ + def listen(programs, task \\ nil, opts \\ []) + + def listen([], _, _), do: nil + + def listen(%Program{} = program, task, opts) do + listen([program], task, opts) + end + + def listen(programs, task, opts) do + # Start another task to ask for user input if we are in watch mode + task = with \ + true <- Keyword.get(opts, :watch, false), + nil <- task, + {:ok, task} <- Task.start_link(__MODULE__, :wait_for_input, [self()]) + do + task + end + + programs = receive do + :user_input_received -> + [] + + {port, {:data, {:eol, msg}}} -> + program = Enum.find(programs, get_port_checker(port)) + msg = :unicode.characters_to_binary(msg, :unicode) + + prefix = "[#{program.name}] #{program.pending_output}" + + Logger.debug(prefix <> msg) + + programs + |> Enum.reject(get_port_checker(port)) + |> Enum.concat([ + %{program | pending_output: ""} + ]) + + {port, {:data, {:noeol, msg}}} -> + program = Enum.find(programs, get_port_checker(port)) + msg = :unicode.characters_to_binary(msg, :unicode) + + programs + |> Enum.reject(get_port_checker(port)) + |> Enum.concat([ + %{program | pending_output: "#{program.pending_output}#{msg}"} + ]) + + # Port was closed normally after being told to close + {port, :closed} -> + handle_closed(programs, port) + + # Port closed because the program closed by itself + {port, {:exit_status, 0}} -> + handle_closed(programs, port) + + # Program closed with error status + {port, {:exit_status, status}} -> + program = Enum.find(programs, get_port_checker(port)) + Logger.error("Program #{program.name} returned status #{status}.") + raise "Failed status #{status} from #{program.name}!" + + # Port crashed + {:EXIT, port, _} -> + handle_closed(programs, port) + end + + if not Enum.empty?(programs) do + listen(programs, task, opts) + end + end + + @doc """ + Kill a running program returned by exec(). + """ + def kill(%Program{name: name, port: port}) do + if name != nil do + Logger.debug("[Killing] #{name}") + end + + send(port, {self(), :close}) + end + + @doc """ + Print output from given programs to screen until user input is given. + + When user input is given, kill programs and return. + """ + def watch(%Program{} = program), do: watch([program]) + + def watch(programs) when is_list(programs) do + Logger.info("Programs started, press ENTER to exit.") + + listen(programs, nil, watch: true) + + Logger.info("ENTER received, killing tasks.") + + Enum.each(programs, &kill/1) + :ok + end + + @doc """ + Print file size of given file in human readable form. + + If old file is given as second argument, print the old file's size + and the diff also. + """ + def print_size(new_file, old_file \\ nil) do + new_size = get_size(new_file) + + {prefix, postfix} = if old_file != nil do + old_size = get_size(old_file) + + { + "#{human_size(old_size)} -> ", + " Diff: #{human_size(old_size - new_size)}" + } + else + {"", ""} + end + + Logger.debug("#{Path.basename(new_file)}: #{prefix}#{human_size(new_size)}.#{postfix}") + end + + def wait_for_input(target) do + IO.gets("") + send(target, :user_input_received) + end + + defp get_size(file) do + %File.Stat{size: size} = File.stat!(file) + size + end + + defp human_size(size) do + size_units = ["B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] # You never know + + human_size(size, size_units) + end + + defp human_size(size, [unit | []]), do: size_with_unit(size, unit) + + defp human_size(size, [unit | rest]) do + if size > 1024 do + human_size(size / 1024, rest) + else + size_with_unit(size, unit) + end + end + + defp size_with_unit(size, unit), do: "#{Float.round(size, 2)} #{unit}" + + defp get_port_checker(port) do + fn %Program{port: program_port} -> + program_port == port + end + end + + defp handle_closed(programs, port) do + case Enum.find(programs, get_port_checker(port)) do + %Program{} = program -> + Logger.debug("[Stopped] #{program.name}") + + programs + |> Enum.reject(get_port_checker(port)) + + nil -> + # Program was already removed + programs + end + end +end diff --git a/package.json b/package.json index 6835b59..aa24b15 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,24 @@ { "name": "mebe_web", - "version": "1.0.0", + "version": "1.1.0", "repository": {}, "license": "MIT", "description": "Minimalistic Elixir Blog Engine", "private": true, "devDependencies": { - "bower": "~1.4.1", - "chalk": "^1.0.0", - "del": "^1.1.1", - "end-of-stream": "~0.1.5", - "event-stream": "~3.3.1", - "gulp": "^3.8.11", - "gulp-concat": "~2.5.2", - "gulp-html-replace": "~1.4.5", - "gulp-minify-css": "~1.1.1", - "gulp-replace": "~0.5.3", - "gulp-sass": "~2.0.0", - "gulp-size": "~1.2.1", - "gulp-sourcemaps": "^1.5.2", - "gulp-uglify": "~1.2.0", - "vinyl-paths": "^1.0.0" - }, - "dependencies": { - "bootstrap-sass": "^3.3.5", - "jquery": "^2.1.4" + "babel-cli": "~6.22.2", + "babel-core": "~6.22.1", + "babel-polyfill": "~6.22.0", + "babel-preset-es2015": "~6.22.0", + "babel-preset-es2016": "~6.22.0", + "babel-preset-es2017": "~6.22.0", + "bootstrap-sass": "~3.3.7", + "cssnano": "~3.10.0", + "cssnano-cli": "~1.0.5", + "node-sass": "~4.3.0", + "rollup": "~0.41.4", + "rollup-plugin-sourcemaps": "~0.4.1", + "rollup-watch": "~3.2.2", + "uglify-js": "~2.7.5" } } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..3f32c89 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,7 @@ +import sourcemaps from 'rollup-plugin-sourcemaps'; + +export default { + plugins: [ + sourcemaps() + ] +}; diff --git a/web/static/js/app.js b/web/static/js/app.js index 8b13789..37d1035 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -1 +1,8 @@ +import checkForLaineHash from './old_hash_redirector'; +// Check hash even before site is loaded since it doesn't need to +// wait for loading +checkForLaineHash(); + +window.onload = () => { +}; diff --git a/web/static/js/old_hash_redirector.js b/web/static/js/old_hash_redirector.js index d50e0f3..f314f6b 100644 --- a/web/static/js/old_hash_redirector.js +++ b/web/static/js/old_hash_redirector.js @@ -4,8 +4,8 @@ * This is done in JS because the hash is not sent to the server. */ -(function () { - var RE = [ +function checkForLaineHash() { + const RE = [ [ /^\#\!\/(\d{4})\/(\d\d)\/(\d\d)\/(.*)$/, '/$1/$2/$3/$4' @@ -40,11 +40,11 @@ ] ]; - var currentHash = window.location.hash; + const currentHash = window.location.hash; if (currentHash) { - for (var i = 0; i < RE.length; ++i) { - var results = RE[i][0].exec(currentHash); + for (let i = 0; i < RE.length; ++i) { + const results = RE[i][0].exec(currentHash); if (results !== null) { window.location.replace(currentHash.replace(RE[i][0], RE[i][1])); @@ -52,4 +52,6 @@ } } } -}()); +} + +export default checkForLaineHash; diff --git a/web/templates/layout/app.html.eex b/web/templates/layout/app.html.eex index b248ddc..12a003b 100644 --- a/web/templates/layout/app.html.eex +++ b/web/templates/layout/app.html.eex @@ -76,7 +76,6 @@ - <%= raw extra_html() %>