From d07a95ad4ff5219cae77b43ce7f3911a3f9708aa Mon Sep 17 00:00:00 2001
From: Marwin Hochfelsner <50826859+hlfan@users.noreply.github.com>
Date: Mon, 22 Jun 2026 02:46:53 +0200
Subject: [PATCH] Migrate js controllers to lazy loaded modules
---
app/assets/javascripts/index.js | 67 ++++++-------------
.../{index => index_modules}/changeset.js | 4 +-
.../{index => index_modules}/directions.js | 12 ++--
.../{index => index_modules}/element.js | 26 ++++---
.../{index => index_modules}/export.js | 4 +-
.../{index => index_modules}/history.js | 6 +-
.../{index => index_modules}/home.js | 4 +-
app/assets/javascripts/index_modules/index.js | 18 +++++
.../{index => index_modules}/new_note.js | 4 +-
.../{index => index_modules}/note.js | 4 +-
.../{index => index_modules}/query.js | 4 +-
.../{index => index_modules}/search.js | 4 +-
app/assets/javascripts/osm.js.erb | 13 ++--
app/assets/javascripts/router.js | 37 ++++++----
config/eslint.config.mjs | 10 +++
15 files changed, 117 insertions(+), 100 deletions(-)
rename app/assets/javascripts/{index => index_modules}/changeset.js (98%)
rename app/assets/javascripts/{index => index_modules}/directions.js (97%)
rename app/assets/javascripts/{index => index_modules}/element.js (91%)
rename app/assets/javascripts/{index => index_modules}/export.js (99%)
rename app/assets/javascripts/{index => index_modules}/history.js (99%)
rename app/assets/javascripts/{index => index_modules}/home.js (95%)
create mode 100644 app/assets/javascripts/index_modules/index.js
rename app/assets/javascripts/{index => index_modules}/new_note.js (99%)
rename app/assets/javascripts/{index => index_modules}/note.js (98%)
rename app/assets/javascripts/{index => index_modules}/query.js (99%)
rename app/assets/javascripts/{index => index_modules}/search.js (98%)
diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js
index 486c7441c..2e1493492 100644
--- a/app/assets/javascripts/index.js
+++ b/app/assets/javascripts/index.js
@@ -10,18 +10,8 @@
//= require leaflet.query
//= require index/contextmenu
//= require index/initializations
-//= require index/search
//= require index/layers/data
-//= require index/export
//= require index/layers/notes
-//= require index/history
-//= require index/note
-//= require index/new_note
-//= require index/directions
-//= require index/changeset
-//= require index/query
-//= require index/home
-//= require index/element
//= require router
OSM.initializations = [];
@@ -270,45 +260,26 @@ $(function () {
});
}
- OSM.Index = function (map) {
- const page = {};
-
- page.pushstate = page.popstate = function () {
- map.setSidebarOverlaid(true);
- document.title = OSM.i18n.t("layouts.project_name.title");
- };
-
- page.load = function () {
- const params = new URLSearchParams(location.search);
- if (params.has("query")) {
- $("#sidebar .search_form input[name=query]").value(params.get("query"));
- }
- return map.getState();
- };
-
- return page;
- };
-
OSM.router = OSM.Router(map, {
- "/": OSM.Index,
- "/search": OSM.Search,
- "/directions": OSM.Directions,
- "/export": OSM.Export,
- "/note/new": OSM.NewNote,
- "/history/friends": OSM.History,
- "/history/nearby": OSM.History,
- "/history": OSM.History,
- "/user/:display_name/history": OSM.History,
- "/note/:id": OSM.Note,
- "/node/:id(/history)": OSM.MappedElement("node"),
- "/node/:id/history/:version": OSM.MappedElement("node"),
- "/way/:id(/history)": OSM.MappedElement("way"),
- "/way/:id/history/:version": OSM.Element("way"),
- "/relation/:id(/history)": OSM.MappedElement("relation"),
- "/relation/:id/history/:version": OSM.Element("relation"),
- "/changeset/:id": OSM.Changeset,
- "/query": OSM.Query,
- "/account/home": OSM.Home
+ "/": "index",
+ "/search": "search",
+ "/directions": "directions",
+ "/export": "export",
+ "/note/new": "new_note",
+ "/history/friends": "history",
+ "/history/nearby": "history",
+ "/history": "history",
+ "/user/:display_name/history": "history",
+ "/note/:id": "note",
+ "/node/:id(/history)": { module: "index_element", part: m => m.mappedElement("node") },
+ "/node/:id/history/:version": { module: "index_element", part: m => m.mappedElement("node") },
+ "/way/:id(/history)": { module: "index_element", part: m => m.mappedElement("way") },
+ "/way/:id/history/:version": { module: "index_element", part: m => m.element("way") },
+ "/relation/:id(/history)": { module: "index_element", part: m => m.mappedElement("relation") },
+ "/relation/:id/history/:version": { module: "index_element", part: m => m.element("relation") },
+ "/changeset/:id": "changeset",
+ "/query": "query",
+ "/account/home": "home"
});
if (OSM.preferred_editor === "remote" && location.pathname === "/edit") {
diff --git a/app/assets/javascripts/index/changeset.js b/app/assets/javascripts/index_modules/changeset.js
similarity index 98%
rename from app/assets/javascripts/index/changeset.js
rename to app/assets/javascripts/index_modules/changeset.js
index 4f14c8deb..370c1f98b 100644
--- a/app/assets/javascripts/index/changeset.js
+++ b/app/assets/javascripts/index_modules/changeset.js
@@ -1,4 +1,4 @@
-OSM.Changeset = function (map) {
+export default function (map) {
const page = {},
content = $("#sidebar_content");
@@ -88,4 +88,4 @@ OSM.Changeset = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/index/directions.js b/app/assets/javascripts/index_modules/directions.js
similarity index 97%
rename from app/assets/javascripts/index/directions.js
rename to app/assets/javascripts/index_modules/directions.js
index ae2febd70..790c3aaa0 100644
--- a/app/assets/javascripts/index/directions.js
+++ b/app/assets/javascripts/index_modules/directions.js
@@ -1,9 +1,9 @@
-//= require ./directions-endpoint
-//= require ./directions-route-output
+//= require ./../index/directions-endpoint
+//= require ./../index/directions-route-output
//= require_self
-//= require_tree ./directions
+//= require_tree ./../index/directions
-OSM.Directions = function (map) {
+export default function (map) {
let controller = null; // the AbortController for the current route request if a route request is in progress
let lastLocation = null;
let chosenEngine;
@@ -288,9 +288,9 @@ OSM.Directions = function (map) {
};
return page;
-};
+}
-OSM.Directions.engines = [];
+OSM.Directions = { engines: [] };
OSM.Directions.addEngine = function (engine, supportsHTTPS) {
if (location.protocol === "http:" || supportsHTTPS) {
diff --git a/app/assets/javascripts/index/element.js b/app/assets/javascripts/index_modules/element.js
similarity index 91%
rename from app/assets/javascripts/index/element.js
rename to app/assets/javascripts/index_modules/element.js
index 08f27e21e..6bf51dfef 100644
--- a/app/assets/javascripts/index/element.js
+++ b/app/assets/javascripts/index_modules/element.js
@@ -1,12 +1,10 @@
-(function () {
- let abortController = null;
- const languagesToRequest = [...new Set(OSM.preferred_languages.map(l => l.toLowerCase()))];
- const wikisToRequest = [...new Set([...OSM.preferred_languages, "en"].map(l => l.split("-")[0] + "wiki"))];
- const isOfExpectedLanguage = ({ language }) => languagesToRequest[0].startsWith(language) || language === "mul";
+let abortController = null;
+const languagesToRequest = [...new Set(OSM.preferred_languages.map(l => l.toLowerCase()))];
+const wikisToRequest = [...new Set([...OSM.preferred_languages, "en"].map(l => l.split("-")[0] + "wiki"))];
+const isOfExpectedLanguage = ({ language }) => languagesToRequest[0].startsWith(language) || language === "mul";
- $(document).on("click", "button.wdt-preview", e => previewWikidataValue($(e.currentTarget)));
-
- OSM.Element = type => function () {
+export function element(type) {
+ return function () {
const page = {};
page.pushstate = page.popstate = function (path, id, version) {
@@ -34,9 +32,11 @@
return page;
};
+};
- OSM.MappedElement = type => function (map) {
- const page = OSM.Element(type)(map);
+export function mappedElement(type) {
+ return function (map) {
+ const page = element(type)(map);
page._addObject = function (type, id, version, center) {
const hashParams = OSM.parseHash();
@@ -56,6 +56,10 @@
return page;
};
+};
+
+{
+ $(document).on("click", "button.wdt-preview", e => previewWikidataValue($(e.currentTarget)));
function previewWikidataValue($btn) {
if (!OSM.WIKIDATA_API_URL) return;
@@ -164,4 +168,4 @@
}
return $("
").append(cell);
}
-}());
+}
diff --git a/app/assets/javascripts/index/export.js b/app/assets/javascripts/index_modules/export.js
similarity index 99%
rename from app/assets/javascripts/index/export.js
rename to app/assets/javascripts/index_modules/export.js
index 5219fe54a..02e7dfb32 100644
--- a/app/assets/javascripts/index/export.js
+++ b/app/assets/javascripts/index_modules/export.js
@@ -1,6 +1,6 @@
//= require download_util
-OSM.Export = function (map) {
+export default function (map) {
const page = {};
const locationFilter = new L.LocationFilter({
@@ -124,4 +124,4 @@ OSM.Export = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/index/history.js b/app/assets/javascripts/index_modules/history.js
similarity index 99%
rename from app/assets/javascripts/index/history.js
rename to app/assets/javascripts/index_modules/history.js
index 8b64a4432..344938e59 100644
--- a/app/assets/javascripts/index/history.js
+++ b/app/assets/javascripts/index_modules/history.js
@@ -1,6 +1,6 @@
-//= require ./history-changesets-layer
+//= require ./../index/history-changesets-layer
-OSM.History = function (map) {
+export default function (map) {
const page = {};
$("#sidebar_content")
@@ -305,4 +305,4 @@ OSM.History = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/index/home.js b/app/assets/javascripts/index_modules/home.js
similarity index 95%
rename from app/assets/javascripts/index/home.js
rename to app/assets/javascripts/index_modules/home.js
index 1cc644cf2..bd3608cdc 100644
--- a/app/assets/javascripts/index/home.js
+++ b/app/assets/javascripts/index_modules/home.js
@@ -1,4 +1,4 @@
-OSM.Home = function (map) {
+export default function (map) {
let marker;
function clearMarker() {
@@ -35,4 +35,4 @@ OSM.Home = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/index_modules/index.js b/app/assets/javascripts/index_modules/index.js
new file mode 100644
index 000000000..223dba291
--- /dev/null
+++ b/app/assets/javascripts/index_modules/index.js
@@ -0,0 +1,18 @@
+export default function (map) {
+ const page = {};
+
+ page.pushstate = page.popstate = function () {
+ map.setSidebarOverlaid(true);
+ document.title = OSM.i18n.t("layouts.project_name.title");
+ };
+
+ page.load = function () {
+ const params = new URLSearchParams(location.search);
+ if (params.has("query")) {
+ $("#sidebar .search_form input[name=query]").value(params.get("query"));
+ }
+ return map.getState();
+ };
+
+ return page;
+};
diff --git a/app/assets/javascripts/index/new_note.js b/app/assets/javascripts/index_modules/new_note.js
similarity index 99%
rename from app/assets/javascripts/index/new_note.js
rename to app/assets/javascripts/index_modules/new_note.js
index 0c6691f02..2fdf393a3 100644
--- a/app/assets/javascripts/index/new_note.js
+++ b/app/assets/javascripts/index_modules/new_note.js
@@ -1,4 +1,4 @@
-OSM.NewNote = function (map) {
+export default function (map) {
const noteLayer = map.noteLayer,
content = $("#sidebar_content"),
page = {},
@@ -175,4 +175,4 @@ OSM.NewNote = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/index/note.js b/app/assets/javascripts/index_modules/note.js
similarity index 98%
rename from app/assets/javascripts/index/note.js
rename to app/assets/javascripts/index_modules/note.js
index ad3844559..923fb5f45 100644
--- a/app/assets/javascripts/index/note.js
+++ b/app/assets/javascripts/index_modules/note.js
@@ -1,4 +1,4 @@
-OSM.Note = function (map) {
+export default function (map) {
const content = $("#sidebar_content"),
page = {};
@@ -99,4 +99,4 @@ OSM.Note = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/index/query.js b/app/assets/javascripts/index_modules/query.js
similarity index 99%
rename from app/assets/javascripts/index/query.js
rename to app/assets/javascripts/index_modules/query.js
index 9bb999cb7..f3a3f9250 100644
--- a/app/assets/javascripts/index/query.js
+++ b/app/assets/javascripts/index_modules/query.js
@@ -1,4 +1,4 @@
-OSM.Query = function (map) {
+export default function (map) {
const uninterestingTags = ["source", "source_ref", "source:ref", "history", "attribution", "created_by", "tiger:county", "tiger:tlid", "tiger:upload_uuid", "KSJ2:curve_id", "KSJ2:lat", "KSJ2:lon", "KSJ2:coordinate", "KSJ2:filename", "note:ja"];
let marker;
@@ -276,4 +276,4 @@ OSM.Query = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/index/search.js b/app/assets/javascripts/index_modules/search.js
similarity index 98%
rename from app/assets/javascripts/index/search.js
rename to app/assets/javascripts/index_modules/search.js
index 77d0c0d08..eb141aee7 100644
--- a/app/assets/javascripts/index/search.js
+++ b/app/assets/javascripts/index_modules/search.js
@@ -1,4 +1,4 @@
-OSM.Search = function (map) {
+export default function (map) {
$("#sidebar_content")
.on("click", ".search_more a", clickSearchMore)
.on("click", ".search_results_entry a.set_position", clickSearchResult);
@@ -105,4 +105,4 @@ OSM.Search = function (map) {
};
return page;
-};
+}
diff --git a/app/assets/javascripts/osm.js.erb b/app/assets/javascripts/osm.js.erb
index bd73260fe..5cc455cea 100644
--- a/app/assets/javascripts/osm.js.erb
+++ b/app/assets/javascripts/osm.js.erb
@@ -33,10 +33,15 @@ OSM = {
LAYER_DEFINITIONS: <%= MapLayers::full_definitions("config/layers.yml", :legends => "config/legend.yml").to_json %>,
- MODULE_PATHS: <%= {
- :mapbox_rtl_text => "@mapbox/mapbox-gl-rtl-text/dist/mapbox-gl-rtl-text.js",
- :make_plural_cardinals => "make-plural/cardinals.js"
- }.transform_values { |path| javascript_path(path) }.to_json %>,
+ MODULE_PATHS: <%=
+ %i[changeset directions element export history home index new_note note query search]
+ .each_with_object({}) { |module_name, hash| hash[:"index_#{module_name}"] = "index_modules/#{module_name}" }
+ .merge(
+ :mapbox_rtl_text => "@mapbox/mapbox-gl-rtl-text/dist/mapbox-gl-rtl-text.js",
+ :make_plural_cardinals => "make-plural/cardinals.js"
+ )
+ .transform_values { |path| javascript_path(path) }
+ .to_json %>,
apiUrl: function (object) {
const apiType = object.type === "note" ? "notes" : object.type;
diff --git a/app/assets/javascripts/router.js b/app/assets/javascripts/router.js
index 737e7541b..0038b93c3 100644
--- a/app/assets/javascripts/router.js
+++ b/app/assets/javascripts/router.js
@@ -68,7 +68,7 @@ OSM.Router = function (map, rts) {
return regexp.test(path);
};
- route.run = function (action, path, ...args) {
+ route.run = async function (action, path, ...args) {
let params = [];
if (path) {
@@ -77,7 +77,11 @@ OSM.Router = function (map, rts) {
});
}
- if (!controllerInstance) controllerInstance = controller(map);
+ if (!controllerInstance) {
+ const moduleName = typeof controller === "string" ? "index_" + controller : controller.module;
+ const select = controller.part || (m => m.default);
+ controllerInstance = await import(OSM.MODULE_PATHS[moduleName]).then(select).then(m => m(map));
+ }
return controllerInstance[action]?.(...params, ...args);
};
@@ -97,6 +101,7 @@ OSM.Router = function (map, rts) {
let currentPath = location.pathname.replace(/(.)\/$/, "$1") + location.search,
currentRoute = routes.recognize(currentPath),
currentHash = location.hash || OSM.formatHash(map);
+ let routingInProgress = Promise.resolve();
const router = {};
@@ -113,13 +118,17 @@ OSM.Router = function (map, rts) {
function transition(action, path, route, beforeEnter = () => {}) {
if (!route) return false;
- currentRoute.run("unload", null, route === currentRoute);
- beforeEnter();
- currentPath = path;
- currentRoute = route;
- currentRoute.run(action, currentPath);
- updateSecondaryNav();
- return true;
+ routingInProgress = routingInProgress
+ .catch(() => {})
+ .then(async () => {
+ await currentRoute.run("unload", null, route === currentRoute);
+ beforeEnter();
+ currentPath = path;
+ currentRoute = route;
+ await currentRoute.run(action, currentPath);
+ updateSecondaryNav();
+ });
+ return routingInProgress;
}
$(window).on("popstate", function (e) {
@@ -128,17 +137,17 @@ OSM.Router = function (map, rts) {
route = routes.recognize(path);
if (path === currentPath) return;
const done = transition("popstate", path, route);
- if (done) map.setState(e.originalEvent.state, { animate: false });
+ if (done) done.then(() => map.setState(e.originalEvent.state, { animate: false }));
});
router.route = function (url) {
const path = url.replace(/#.*/, ""),
route = routes.recognize(path);
const state = OSM.parseHash(url);
- return transition("pushstate", path, route, () => {
+ return Boolean(transition("pushstate", path, route, () => {
map.setState(state);
window.history.pushState(state, document.title, url);
- });
+ }));
};
router.replace = function (url) {
@@ -179,8 +188,8 @@ OSM.Router = function (map, rts) {
map.off("movestart", disableMoveListener);
};
- router.load = function () {
- const loadState = currentRoute.run("load", currentPath);
+ router.load = async function () {
+ const loadState = await currentRoute.run("load", currentPath);
router.stateChange(loadState || {});
};
diff --git a/config/eslint.config.mjs b/config/eslint.config.mjs
index 66b857df7..554e53eb5 100644
--- a/config/eslint.config.mjs
+++ b/config/eslint.config.mjs
@@ -167,6 +167,16 @@ export default [
}
}
},
+ {
+ // Additional configuration for index modules
+ files: ["app/**/index_modules/*.js"],
+ languageOptions: {
+ sourceType: "module"
+ },
+ rules: {
+ "no-invalid-this": "off"
+ }
+ },
{
files: ["config/eslint.config.mjs"],
languageOptions: {
--
2.47.3