marten-stimulus v0.1.0

MartenStimulus

Hotwire Stimulus integration for Marten, built on top of marten-importmap.

Provides:

  • Bundled stimulus-loading.js asset, 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 init checks for require "marten_importmap" literally. If your project only has require "marten_stimulus", it will insert a redundant require "marten_importmap" line. This is harmless — Crystal's require is idempotent — but you can remove it afterwards since marten_stimulus already 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_controllermy-form).

Repository

marten-stimulus

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 2
  • 7 days ago
  • March 26, 2026
License

Links
Synced at

Thu, 26 Mar 2026 12:31:34 GMT

Languages