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() %>