Module: Plumbo::Stimulus

Defined in:
lib/plumbo/stimulus.rb

Overview

Finds the Stimulus controllers referenced by a page and nests each one under the template/partial whose source declares it. Controllers are discovered by scanning a file’s source for data-controller attributes, then mapping each identifier to its source file via Stimulus’s naming convention. No asset pipeline or digest guessing is involved: the mapping is deterministic.

data-controller="hello"            -> app/javascript/controllers/hello_controller.js
data-controller="date-picker"      -> app/javascript/controllers/date_picker_controller.js
data-controller="users--list-item" -> app/javascript/controllers/users/list_item_controller.js

(A “–” in an identifier is a directory separator; a “-” is a word separator.)

Constant Summary collapse

ATTRIBUTE =
/data-controller\s*=\s*["']([^"']*)["']/i

Class Method Summary collapse

Class Method Details

.children(path, depth, config) ⇒ Object



31
32
33
34
35
36
# File 'lib/plumbo/stimulus.rb', line 31

def children(path, depth, config)
  source = source_for(path, config)
  return [] unless source

  controllers(source, config).map { |controller| [controller, depth + 1] }
end

.controllers(markup, config) ⇒ Object

Source paths for every Stimulus controller referenced in markup, prefixed and deduped (first occurrence wins). Empty when disabled or none are found.



49
50
51
52
53
# File 'lib/plumbo/stimulus.rb', line 49

def controllers(markup, config)
  return [] unless config.include_stimulus

  identifiers(markup).map { |id| path_for(id, config) }.uniq
end

.identifiers(markup) ⇒ Object

Each whitespace-separated identifier across all data-controller attributes.



56
57
58
# File 'lib/plumbo/stimulus.rb', line 56

def identifiers(markup)
  markup.scan(ATTRIBUTE).flatten.flat_map(&:split)
end

.nest(files, config) ⇒ Object

Given the ordered [path, depth] file list, returns a new list with each template/partial’s Stimulus controllers inserted right after it, nested one level deeper — so a controller appears under the view/partial that references it. Files whose source can’t be read (or non-.erb files) contribute no children.



25
26
27
28
29
# File 'lib/plumbo/stimulus.rb', line 25

def nest(files, config)
  return files unless config.include_stimulus

  files.flat_map { |path, depth| [[path, depth]] + children(path, depth, config) }
end

.path_for(identifier, config) ⇒ Object



60
61
62
63
# File 'lib/plumbo/stimulus.rb', line 60

def path_for(identifier, config)
  file = "#{identifier.gsub('--', '/').gsub('-', '_')}_controller.js"
  "#{config.path_prefix}#{config.javascript_root}/controllers/#{file}"
end

.source_for(path, config) ⇒ Object

Reads the on-disk source of a rendered template/partial (only .erb files), reconstructing the absolute path from the prefixed path and the root.



40
41
42
43
44
45
# File 'lib/plumbo/stimulus.rb', line 40

def source_for(path, config)
  return unless path.end_with?(".erb")

  absolute = File.join(config.root, path.delete_prefix(config.path_prefix))
  File.file?(absolute) ? File.read(absolute) : nil
end