From 56903b144b995f01df86a6dc0cee9e0181ad11c9 Mon Sep 17 00:00:00 2001 From: Daniel Orner Date: Fri, 30 Jun 2023 16:02:51 -0400 Subject: [PATCH] Update Sprockets --- Gemfile | 2 +- Gemfile.lock | 6 +- config/importmap.rb | 2 +- vendor/javascript/stimulusloading.js | 85 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 vendor/javascript/stimulusloading.js diff --git a/Gemfile b/Gemfile index 3e99ae3892..da3435373d 100644 --- a/Gemfile +++ b/Gemfile @@ -107,7 +107,7 @@ gem "clockwork" gem "mini_racer", "~> 0.8.0" gem "nokogiri", ">= 1.10.4" gem "image_processing" -gem "sprockets", "~> 4.0.0" +gem "sprockets", "~> 4.2.0" group :production do # Reduce the noise of logs and include custom fields to it for easier access diff --git a/Gemfile.lock b/Gemfile.lock index d86e2b71ab..0779499fbf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -553,9 +553,9 @@ GEM snaky_hash (2.0.0) hashie version_gem (~> 1.1) - sprockets (4.0.3) + sprockets (4.2.0) concurrent-ruby (~> 1.0) - rack (> 1, < 3) + rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) @@ -700,7 +700,7 @@ DEPENDENCIES simple_form simplecov skylight - sprockets (~> 4.0.0) + sprockets (~> 4.2.0) standard (~> 1.28) stimulus-rails strong_migrations (= 1.5.0) diff --git a/config/importmap.rb b/config/importmap.rb index 4af8ecbdcf..f46da0086a 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -7,7 +7,7 @@ pin "startup", to: "startup.js", preload: true pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true -pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulusloading.js", preload: true pin_all_from "app/javascript/controllers", under: "controllers" pin_all_from "app/javascript/utils", under: "utils" pin "bootstrap", to: "https://ga.jspm.io/npm:bootstrap@4.6.2/dist/js/bootstrap.js" diff --git a/vendor/javascript/stimulusloading.js b/vendor/javascript/stimulusloading.js new file mode 100644 index 0000000000..99a22f8b0b --- /dev/null +++ b/vendor/javascript/stimulusloading.js @@ -0,0 +1,85 @@ +// FIXME: es-module-shim won't shim the dynamic import without this explicit import +import "@hotwired/stimulus" + +const controllerAttribute = "data-controller" +const registeredControllers = {} + +// Eager load all controllers registered beneath the `under` path in the import map to the passed application instance. +export function eagerLoadControllersFrom(under, application) { + const paths = Object.keys(parseImportmapJson()).filter(path => path.match(new RegExp(`^${under}/.*_controller$`))) + paths.forEach(path => registerControllerFromPath(path, under, application)) +} + +function parseImportmapJson() { + return JSON.parse(document.querySelector("script[type=importmap]").text).imports +} + +function registerControllerFromPath(path, under, application) { + const name = path + .replace(new RegExp(`^${under}/`), "") + .replace("_controller", "") + .replace(/\//g, "--") + .replace(/_/g, "-") + + if (!(name in registeredControllers)) { + import(path) + .then(module => registerController(name, module, application)) + .catch(error => console.error(`Failed to register controller: ${name} (${path})`, error)) + } +} + + +// Lazy load controllers registered beneath the `under` path in the import map to the passed application instance. +export function lazyLoadControllersFrom(under, application, element = document) { + lazyLoadExistingControllers(under, application, element) + lazyLoadNewControllers(under, application, element) +} + +function lazyLoadExistingControllers(under, application, element) { + queryControllerNamesWithin(element).forEach(controllerName => loadController(controllerName, under, application)) +} + +function lazyLoadNewControllers(under, application, element) { + new MutationObserver((mutationsList) => { + for (const { attributeName, target, type } of mutationsList) { + switch (type) { + case "attributes": { + if (attributeName == controllerAttribute && target.getAttribute(controllerAttribute)) { + extractControllerNamesFrom(target).forEach(controllerName => loadController(controllerName, under, application)) + } + } + + case "childList": { + lazyLoadExistingControllers(under, application, target) + } + } + } + }).observe(element, { attributeFilter: [controllerAttribute], subtree: true, childList: true }) +} + +function queryControllerNamesWithin(element) { + return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).map(extractControllerNamesFrom).flat() +} + +function extractControllerNamesFrom(element) { + return element.getAttribute(controllerAttribute).split(/\s+/).filter(content => content.length) +} + +function loadController(name, under, application) { + if (!(name in registeredControllers)) { + import(controllerFilename(name, under)) + .then(module => registerController(name, module, application)) + .catch(error => console.error(`Failed to autoload controller: ${name}`, error)) + } +} + +function controllerFilename(name, under) { + return `${under}/${name.replace(/--/g, "/").replace(/-/g, "_")}_controller` +} + +function registerController(name, module, application) { + if (!(name in registeredControllers)) { + application.register(name, module.default) + registeredControllers[name] = true + } +}