marten-stimulus v0.1.0
MartenStimulus
Hotwire Stimulus integration for Marten, built on top of marten-importmap.
Provides:
- Bundled
stimulus-loading.jsasset, auto-pinned into the importmap marten stimulus generate controller <name>CLI command to scaffold controllers
Installation
Add the shard to your shard.yml:
dependencies:
marten_stimulus:
github: treagod/marten-stimulus
marten-importmap is pulled in as a transitive dependency — no need to declare it separately.
Run shards install, then add the require:
# src/project.cr
require "marten_stimulus"
# src/cli.cr
require "marten/cli"
require "marten_stimulus/cli" # also loads marten_importmap/cli
Both apps must be registered explicitly in config.installed_apps in the correct order:
config.installed_apps = [
MartenImportmap::App,
MartenStimulus::App,
]
Setup
If you haven't initialized importmap yet, run:
marten importmap init
This creates config/initializers/importmap.cr, config/initializers/importmap_pins.cr, and src/assets/application.js. See marten-importmap for full details.
Note:
marten importmap initchecks forrequire "marten_importmap"literally. If your project only hasrequire "marten_stimulus", it will insert a redundantrequire "marten_importmap"line. This is harmless — Crystal's require is idempotent — but you can remove it afterwards sincemarten_stimulusalready pulls it in.
Pin Stimulus:
marten importmap pin @hotwired/stimulus
Then update src/assets/application.js to boot Stimulus and load controllers automatically:
import { Application } from "@hotwired/stimulus"
import { eagerLoadControllersFrom } from "stimulus-loading"
const Stimulus = Application.start()
eagerLoadControllersFrom("controllers", Stimulus)
Add pin_all_from to config/initializers/importmap.cr so the controllers directory is included in the importmap:
Marten.configure do |config|
config.importmap.draw do
pin "application", "application.js"
pin_all_from "src/assets/controllers", under: "controllers"
end
end
stimulus-loading is pinned automatically by MartenStimulus::App and served as stimulus-loading.js from the shard's bundled assets — no manual pin or vendor file needed.
Generating controllers
marten stimulus generate controller hello
# → creates src/assets/controllers/hello_controller.js
# → ensures pin_all_from is present in config/initializers/importmap.cr
The generated file:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
console.log("HelloController connected", this.element)
}
}
Controller naming follows the Stimulus convention: hello_controller.js is registered as hello, my_form_controller.js as my-form. Use data-controller="hello" in your templates to attach it.
Loading strategies
Two loading strategies are available:
eagerLoadControllersFrom(under, application) — imports all matching controllers immediately on page load:
import { eagerLoadControllersFrom } from "stimulus-loading"
eagerLoadControllersFrom("controllers", Stimulus)
lazyLoadControllersFrom(under, application) — imports a controller only when an element with the matching data-controller attribute first appears in the DOM:
import { lazyLoadControllersFrom } from "stimulus-loading"
lazyLoadControllersFrom("controllers", Stimulus)
Lazy loading uses a MutationObserver to watch for new elements, which can reduce the initial JavaScript footprint on pages that don't use every controller.
How it works
MartenStimulus::App pins stimulus-loading during app setup, pointing to a stimulus-loading.js asset bundled inside the shard. Marten's asset pipeline discovers it automatically via the app's assets/ directory.
Both loading functions read the importmap JSON at runtime, find all entries whose key starts with the given prefix (controllers/), and register each module's default export with Stimulus. Controller identifiers are derived by stripping the _controller suffix and converting underscores to dashes (e.g. controllers/my_form_controller → my-form).
marten-stimulus
- 0
- 0
- 0
- 0
- 2
- 7 days ago
- March 26, 2026
Thu, 26 Mar 2026 12:31:34 GMT