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