Skip to content

Instantly share code, notes, and snippets.

@amkisko
Last active January 9, 2024 12:39
Show Gist options
  • Save amkisko/6f11f0812be8fc8bad6a14654875718e to your computer and use it in GitHub Desktop.
Save amkisko/6f11f0812be8fc8bad6a14654875718e to your computer and use it in GitHub Desktop.
Stimulus single application typescript decorator for controllers

stimulus.js typescript decorator

Let's you automate registering of new controllers for Stimulus.js by using TypeScript decorator @stimulusController. Good alternative for stimulus-vite-helpers.

Features

  • developer can set custom name for the controller
  • developer can set custom application for every controller
  • by default it uses window.StimulusApplication, also initialises one
  • assigns static controllerName attribute on the controller class level
  • produces debug logging, so it helps to figure out something
  • fully extensible

Usage

  1. copy-paste stimulus.ts and logger.ts somewhere to lib folder
  2. use @stimulusController("<PUT CONTROLLER NAME HERE>") before class declaration
  3. enjoy automation

DATE: 15.10.2023 AUTHOR: Andrei Makarov (github.com/amkisko)

// AUTHOR: Andrei Makarov (github.com/amkisko)
import { Controller, stimulusController } from "./stimulus";
import Logger from "./logger";
@stimulusController("example")
export class ExampleController extends Controller {
logger = new Logger("ExampleController");
connect(): void {
Promise.resolve().then(() => {
logger.log("connect promise");
});
}
}
// AUTHOR: Andrei Makarov (github.com/amkisko)
export default class Logger {
static log = (...args: unknown[]) => {
new Logger().log(...args);
}
DEBUG = process.env.NODE_ENV === "development" || import.meta.env.DEV;
label = "Logger";
constructor(label?: string) {
if (label) this.label = label;
}
log(...args: unknown[]) {
if (this.DEBUG) {
console.log(this.label, ...args);
}
}
}
// AUTHOR: Andrei Makarov (github.com/amkisko)
import {
Application,
Controller,
ControllerConstructor,
} from "@hotwired/stimulus";
import Logger from "./logger";
export { Application, Controller };
export function ApplicationInstance() {
if (!window.StimulusApplication) {
window.StimulusApplication = Application.start();
window.StimulusApplication.debug = false;
}
return window.StimulusApplication;
}
export const windowApplication = ApplicationInstance();
export interface IController extends ControllerConstructor {
controllerName?: string;
}
export function controllerNameFromClass(klass: ControllerConstructor) {
klass.name.replace(/([a-zA-Z])([A-Z])/g, "$1-$2").toLowerCase();
}
export function stimulusController(controllerName: string, application?: Application) {
return function <T extends IController>(
klass: T,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
options?: unknown,
) {
const logger = new Logger("Stimulus");
klass.controllerName = controllerName;
const resolvedInstance = application || windowApplication;
if (!resolvedInstance) {
throw new Error("Stimulus application is not initialized.");
}
const resolvedControllerName = klass.controllerName;
if (!resolvedControllerName) {
throw new Error(
`${klass.name} does not have a static 'controllerName' property.`,
);
}
logger.log("register", klass.name, resolvedControllerName);
resolvedInstance.register(resolvedControllerName, klass);
return klass;
};
}
@amkisko
Copy link
Author

amkisko commented Jan 9, 2024

importmap-rails fully resolves this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment