| 1 | // FIXME: es-module-shim won't shim the dynamic import without this explicit import |
| 2 | import "@hotwired/stimulus" |
| 3 | |
| 4 | const controllerAttribute = "data-controller" |
| 5 | |
| 6 | // Eager load all controllers registered beneath the `under` path in the import map to the passed application instance. |
| 7 | export function eagerLoadControllersFrom(under, application) { |
| 8 | const paths = Object.keys(parseImportmapJson()).filter(path => path.match(new RegExp(`^${under}/.*_controller$`))) |
| 9 | paths.forEach(path => registerControllerFromPath(path, under, application)) |
| 10 | } |
| 11 | |
| 12 | function parseImportmapJson() { |
| 13 | return JSON.parse(document.querySelector("script[type=importmap]").text).imports |
| 14 | } |
| 15 | |
| 16 | function registerControllerFromPath(path, under, application) { |
| 17 | const name = path |
| 18 | .replace(new RegExp(`^${under}/`), "") |
| 19 | .replace("_controller", "") |
| 20 | .replace(/\//g, "--") |
| 21 | .replace(/_/g, "-") |
| 22 | |
| 23 | if (canRegisterController(name, application)) { |
| 24 | import(path) |
| 25 | .then(module => registerController(name, module, application)) |
| 26 | .catch(error => console.error(`Failed to register controller: ${name} (${path})`, error)) |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | |
| 31 | // Lazy load controllers registered beneath the `under` path in the import map to the passed application instance. |
| 32 | export function lazyLoadControllersFrom(under, application, element = document) { |
| 33 | lazyLoadExistingControllers(under, application, element) |
| 34 | lazyLoadNewControllers(under, application, element) |
| 35 | } |
| 36 | |
| 37 | function lazyLoadExistingControllers(under, application, element) { |
| 38 | queryControllerNamesWithin(element).forEach(controllerName => loadController(controllerName, under, application)) |
| 39 | } |
| 40 | |
| 41 | function lazyLoadNewControllers(under, application, element) { |
| 42 | new MutationObserver((mutationsList) => { |
| 43 | for (const { attributeName, target, type } of mutationsList) { |
| 44 | switch (type) { |
| 45 | case "attributes": { |
| 46 | if (attributeName == controllerAttribute && target.getAttribute(controllerAttribute)) { |
| 47 | extractControllerNamesFrom(target).forEach(controllerName => loadController(controllerName, under, application)) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | case "childList": { |
| 52 | lazyLoadExistingControllers(under, application, target) |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | }).observe(element, { attributeFilter: [controllerAttribute], subtree: true, childList: true }) |
| 57 | } |
| 58 | |
| 59 | function queryControllerNamesWithin(element) { |
| 60 | return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).map(extractControllerNamesFrom).flat() |
| 61 | } |
| 62 | |
| 63 | function extractControllerNamesFrom(element) { |
| 64 | return element.getAttribute(controllerAttribute).split(/\s+/).filter(content => content.length) |
| 65 | } |
| 66 | |
| 67 | function loadController(name, under, application) { |
| 68 | if (canRegisterController(name, application)) { |
| 69 | import(controllerFilename(name, under)) |
| 70 | .then(module => registerController(name, module, application)) |
| 71 | .catch(error => console.error(`Failed to autoload controller: ${name}`, error)) |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | function controllerFilename(name, under) { |
| 76 | return `${under}/${name.replace(/--/g, "/").replace(/-/g, "_")}_controller` |
| 77 | } |
| 78 | |
| 79 | function registerController(name, module, application) { |
| 80 | if (canRegisterController(name, application)) { |
| 81 | application.register(name, module.default) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | function canRegisterController(name, application){ |
| 86 | return !application.router.modulesByIdentifier.has(name) |
| 87 | } |
| 88 | |