diff --git a/Gemfile.lock b/Gemfile.lock index 77711f1..8b765d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,7 +101,9 @@ GEM marcel (0.3.3) mimemagic (~> 0.3.2) method_source (1.0.0) - mimemagic (0.3.5) + mimemagic (0.3.10) + nokogiri (~> 1) + rake mini_mime (1.0.2) mini_portile2 (2.5.0) minitest (5.14.4) @@ -180,4 +182,4 @@ DEPENDENCIES webdrivers BUNDLED WITH - 2.2.5 + 2.2.12 diff --git a/app/assets/javascripts/stimulus/loaders/autoloader.js b/app/assets/javascripts/stimulus/loaders/autoloader.js index dd261b6..3ab9649 100644 --- a/app/assets/javascripts/stimulus/loaders/autoloader.js +++ b/app/assets/javascripts/stimulus/loaders/autoloader.js @@ -4,6 +4,30 @@ const application = Application.start() const { controllerAttribute } = application.schema const registeredControllers = {} +function createTokenList(element, attribute) { + const tokenList = document.createElement("div").classList + const tokens = element.getAttribute(attribute) || "" + tokens.split(/\s+/).filter(content => content.length).forEach(token => tokenList.add(token)) + + return tokenList +} + +function addToTokenList(element, attribute, value) { + const tokenList = createTokenList(element, attribute) + tokenList.add(value) + element.setAttribute(attribute, tokenList.toString()) +} + +function removeFromTokenList(element, attribute, value) { + const tokenList = createTokenList(element, attribute) + tokenList.remove(value) + if (tokenList.length) { + element.setAttribute(attribute, tokenList.toString()) + } else { + element.removeAttribute(attribute) + } +} + function autoloadControllersWithin(element) { queryControllerNamesWithin(element).forEach(loadController) } @@ -13,13 +37,16 @@ function queryControllerNamesWithin(element) { } function extractControllerNamesFrom(element) { - return element.getAttribute(controllerAttribute).split(/\s+/).filter(content => content.length) + const tokenList = createTokenList(element, controllerAttribute) + return Array.from(tokenList).map(name => ({ element, name })) } -function loadController(name) { +function loadController({ element, name }) { + addToTokenList(element, "data-stimulus-autoloading", name) import(controllerFilename(name)) .then(module => registerController(name, module)) .catch(error => console.log(`Failed to autoload controller: ${name}`, error)) + .finally(() => removeFromTokenList(element, "data-stimulus-autoloading", name)) } function controllerFilename(name) { @@ -33,7 +60,6 @@ function registerController(name, module) { registeredControllers[name] = true } - new MutationObserver((mutationsList) => { for (const { attributeName, target, type } of mutationsList) { switch (type) { diff --git a/test/dummy/app/assets/javascripts/controllers/hello_controller.js b/test/dummy/app/assets/javascripts/controllers/hello_controller.js index 612a01f..62012c4 100644 --- a/test/dummy/app/assets/javascripts/controllers/hello_controller.js +++ b/test/dummy/app/assets/javascripts/controllers/hello_controller.js @@ -1,7 +1,9 @@ import { Controller } from "stimulus" export default class extends Controller { - connect() { - this.element.textContent = "Hello World!" + static get targets() { return [ "input", "output" ] } + + greet() { + this.outputTarget.innerHTML = `Hello, ${this.inputTarget.value}` } } diff --git a/test/dummy/app/assets/javascripts/controllers/namespace/message_rendering_controller.js b/test/dummy/app/assets/javascripts/controllers/namespace/message_rendering_controller.js index 0f67014..f3b72ef 100644 --- a/test/dummy/app/assets/javascripts/controllers/namespace/message_rendering_controller.js +++ b/test/dummy/app/assets/javascripts/controllers/namespace/message_rendering_controller.js @@ -6,4 +6,8 @@ export default class extends Controller { connect() { this.element.innerHTML = `Namespace: ${this.messageValue}` } + + sayHello() { + this.element.innerHTML = `Hello from Namespace: ${this.messageValue}` + } } diff --git a/test/dummy/app/views/application/index.html.erb b/test/dummy/app/views/application/index.html.erb index f9f288b..2a71ef4 100644 --- a/test/dummy/app/views/application/index.html.erb +++ b/test/dummy/app/views/application/index.html.erb @@ -5,6 +5,15 @@ <%= tag.p "", data: { namespace__message_rendering_message_value: params[:message], controller: " namespace--message-rendering " } %> + +
+ +
+ + +
diff --git a/test/system/autoload_test.rb b/test/system/autoload_test.rb index fdea411..f1529ea 100644 --- a/test/system/autoload_test.rb +++ b/test/system/autoload_test.rb @@ -10,6 +10,16 @@ class AutoloadTest < ApplicationSystemTestCase end end + test "waits for autoloading to complete" do + visit root_path + + within "#eager-loaded" do + click_button "Say Hello" + + assert_text "Hello, Waiting for Eager Load" + end + end + test "autoloads Controller modules on the page lazily" do visit root_path(message: "Hello World!") @@ -45,4 +55,14 @@ class AutoloadTest < ApplicationSystemTestCase assert_text "Namespace: Hello, from Turbo page" end end + + def click_button(*) + begin + assert_css "[data-stimulus-autoloading]" + rescue + # does not eagerly load any Stimulus + end + assert_no_css "[data-stimulus-autoloading]" + super + end end