diff --git a/src/amber/cli/templates/app/.amber.yml.ecr b/src/amber/cli/templates/app/.amber.yml.ecr
index 100b05076..6bf401dbb 100644
--- a/src/amber/cli/templates/app/.amber.yml.ecr
+++ b/src/amber/cli/templates/app/.amber.yml.ecr
@@ -22,6 +22,7 @@ watch:
- ./src/views/**/*.slang
<%- end -%>
- ./src/locales/*.yml
+ - ./src/javascript/**/*.js
# exclude: # NOTE simplistic implementation: (1) enumerate all includes and excludes; (2) return (includes - excludes)
# - ./src/some_irrelevant_file.cr
spec:
diff --git a/src/amber/cli/templates/app/config/initializers/import_map.cr.ecr b/src/amber/cli/templates/app/config/initializers/import_map.cr.ecr
new file mode 100644
index 000000000..e60ca28f1
--- /dev/null
+++ b/src/amber/cli/templates/app/config/initializers/import_map.cr.ecr
@@ -0,0 +1,15 @@
+require "asset_pipeline"
+
+FRONT_LOADER = AssetPipeline::FrontLoader.new(js_source_path: Path["src/javascript"], js_output_path: Path["public"]) do |import_maps|
+ import_map = AssetPipeline::ImportMap.new
+
+ if Amber.settings.auto_reload
+ import_map.add_import("client_reload", "/client_reload.js")
+ end
+
+ import_map.add_import("@popperjs/core", "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js")
+ import_map.add_import("bootstrap", "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.esm.min.js")
+ import_map.add_import("amber", "/amber.js")
+
+ import_maps << import_map
+end
\ No newline at end of file
diff --git a/src/amber/cli/templates/app/config/logger.cr.ecr b/src/amber/cli/templates/app/config/logger.cr.ecr
index d31de0f15..1a198328c 100644
--- a/src/amber/cli/templates/app/config/logger.cr.ecr
+++ b/src/amber/cli/templates/app/config/logger.cr.ecr
@@ -26,6 +26,7 @@ backend.formatter = Log::Formatter.new do |entry, io|
io << " (#{entry.severity})" if entry.severity > Log::Severity::Debug
io << " "
io << entry.message
+ io << " #{entry.exception}" if entry.exception
end
Log.builder.clear
diff --git a/src/amber/cli/templates/app/public/js/client_reload.js b/src/amber/cli/templates/app/public/js/client_reload.js
deleted file mode 100644
index cf0e04dab..000000000
--- a/src/amber/cli/templates/app/public/js/client_reload.js
+++ /dev/null
@@ -1,52 +0,0 @@
-if ('WebSocket' in window) {
- (function () {
- /**
- * Allows to reload the browser when the server connection is lost
- */
- function tryReload() {
- var request = new XMLHttpRequest();
- request.open('GET', window.location.href, true);
- request.onreadystatechange = function () {
- if (request.readyState == 4) {
- if (request.status == 0) {
- setTimeout(function () {
- tryReload();
- }, 1000)
- } else {
- window.location.reload();
- }
- }
- };
- request.send();
- }
-
- /**
- * Listen server file reload
- */
- function refreshCSS() {
- var sheets = [].slice.call(document.getElementsByTagName('link'));
- var head = document.getElementsByTagName('head')[0];
- for (var i = 0; i < sheets.length; ++i) {
- var elem = sheets[i];
- var rel = elem.rel;
- if (elem.href && typeof rel != 'string' || rel.length == 0 || rel.toLowerCase() == 'stylesheet') {
- head.removeChild(elem);
- var url = elem.href.replace(/(&|\\?)_cacheOverride=\\d+/, '');
- elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
- head.appendChild(elem);
- }
- }
- }
-
- var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
- var address = protocol + window.location.host + '/client-reload';
- var socket = new WebSocket(address);
- socket.onmessage = function (msg) {
- if (msg.data == 'reload') {
- tryReload();
- } else if (msg.data == 'refreshcss') {
- refreshCSS();
- }
- };
- })();
-}
diff --git a/src/amber/cli/templates/app/shard.yml.ecr b/src/amber/cli/templates/app/shard.yml.ecr
index baf76e2fe..5c2e03fb9 100644
--- a/src/amber/cli/templates/app/shard.yml.ecr
+++ b/src/amber/cli/templates/app/shard.yml.ecr
@@ -51,6 +51,10 @@ dependencies:
github: dare892/citrine-i18n
version: ~> 1.0.0
+ asset_pipeline:
+ github: amberframework/asset_pipeline
+ version: ~> 0.34.0
+
development_dependencies:
ameba:
github: crystal-ameba/ameba
diff --git a/src/amber/cli/templates/app/public/js/amber.js b/src/amber/cli/templates/app/src/javascript/amber.js
similarity index 87%
rename from src/amber/cli/templates/app/public/js/amber.js
rename to src/amber/cli/templates/app/src/javascript/amber.js
index 1abbd755f..c4ffcae4a 100644
--- a/src/amber/cli/templates/app/public/js/amber.js
+++ b/src/amber/cli/templates/app/src/javascript/amber.js
@@ -231,28 +231,30 @@ export default {
/**
* Allows delete links to post for security and ease of use similar to Rails jquery_ujs
*/
-document.addEventListener("DOMContentLoaded", () => {
- let elements = document.querySelectorAll("a[data-method='delete']");
- for (let i = 0; i < elements.length; i++) {
- elements[i].addEventListener("click", (e) => {
- e.preventDefault();
- let message = elements[i].getAttribute("data-confirm") || "Are you sure?";
- if (confirm(message)) {
- let form = document.createElement("form");
- let input = document.createElement("input");
- form.setAttribute("action", elements[i].getAttribute("href"));
- form.setAttribute("method", "POST");
- input.setAttribute("type", "hidden");
- input.setAttribute("name", "_method");
- input.setAttribute("value", "DELETE");
- form.appendChild(input);
- document.body.appendChild(form);
- form.submit();
- }
- return false;
- })
- }
-});
+export function initialize() {
+ document.addEventListener("DOMContentLoaded", () => {
+ let elements = document.querySelectorAll("a[data-method='delete']");
+ for (let i = 0; i < elements.length; i++) {
+ elements[i].addEventListener("click", (e) => {
+ e.preventDefault();
+ let message = elements[i].getAttribute("data-confirm") || "Are you sure?";
+ if (confirm(message)) {
+ let form = document.createElement("form");
+ let input = document.createElement("input");
+ form.setAttribute("action", elements[i].getAttribute("href"));
+ form.setAttribute("method", "POST");
+ input.setAttribute("type", "hidden");
+ input.setAttribute("name", "_method");
+ input.setAttribute("value", "DELETE");
+ form.appendChild(input);
+ document.body.appendChild(form);
+ form.submit();
+ }
+ return false;
+ })
+ }
+ });
+}
if (!Date.prototype.toGranite) {
(function() {
diff --git a/src/amber/cli/templates/app/src/javascript/client_reload.js b/src/amber/cli/templates/app/src/javascript/client_reload.js
new file mode 100644
index 000000000..e86d225ba
--- /dev/null
+++ b/src/amber/cli/templates/app/src/javascript/client_reload.js
@@ -0,0 +1,35 @@
+let tryReload
+
+export function initializeWebSockets() {
+ console.log("initializeWebSockets has run...")
+
+ tryReload = async function () {
+ try {
+ const response = await fetch(window.location.href);
+ if (!response.ok) {
+ setTimeout(tryReload, 1000);
+ } else {
+ window.location.reload();
+ }
+ } catch (error) {
+ setTimeout(tryReload, 1000);
+ }
+ }
+
+ if ('WebSocket' in window) {
+ const protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://'
+ const address = protocol + window.location.host + '/client-reload'
+ const socket = new WebSocket(address)
+
+ socket.onmessage = function (msg) {
+ console.log(msg)
+ if (msg.data == 'reload') {
+ tryReload()
+ }
+ }
+
+ socket.onclose = function () {
+ setTimeout(tryReload, 1000)
+ }
+ }
+}
diff --git a/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr b/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr
index fe092e60c..10c337d65 100644
--- a/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr
+++ b/src/amber/cli/templates/app/src/views/layouts/application.ecr.ecr
@@ -38,9 +38,15 @@
-
-
-
- <%="<"%>%- if Amber.settings.auto_reload? -%><%="<"%>%- end -%>
+ <%= "<"%>%= FRONT_LOADER.render_import_map_tag %>
+