Support for webpackless install #14
Replies: 9 comments 4 replies
-
From what I know stimulus-rails with importmap-rails do some sort of autoloading (as per https://stimulus.hotwired.dev/handbook/installing), which could be potentially be adapted to this case |
Beta Was this translation helpful? Give feedback.
-
In case of import maps, we need something like this in the pin_all_from "frontend/components", under: "components" And then we need to implement our own eager loading. |
Beta Was this translation helpful? Give feedback.
-
Esbuild:index.jsimport { Application } from '@hotwired/stimulus'
export const application = Application.start()
import controllers from "./**/*_controller.js"
controllers.forEach((controller) => {
application.register(controller.name, controller.module.default)
}) esbuid.config.jsimport rails from 'esbuild-rails'
....
plugins: [
rails(),
] |
Beta Was this translation helpful? Give feedback.
-
also if you use components with sass + tailwind: // import postcss
import postcss from 'postcss'
import postCssConfig from './postcss.config.js'
// esbuild config
....
plugins: [
sassPlugin({
async transform(source, resolveDir) {
/// apply postCss config
const {css} = await postcss(postCssConfig.plugins).process(source)
//console.log(css)
return css
}
}),
rails(), |
Beta Was this translation helpful? Give feedback.
-
Hey here, I have customized, loading script to support loading directly with importmaps (mostly I just modified regexes) here is my config/importmap.rb #...
pin_all_from 'app/views/components', under: "components" here is my config/application.rb # ...
component_path = Rails.root.join("app", "views", "components")
config.autoload_paths << component_path
config.view_component.preview_paths << component_path
initializer "app_assets", after: "importmap.assets" do
Rails.application.config.assets.paths << Rails.root.join("app", "views") # for component sidecar js
end
config.importmap.cache_sweepers << component_path
#... here is my app/javascript/controller/custom_loading.js import "@hotwired/stimulus"
const controllerAttribute = "data-controller"
export function eagerLoadControllersFrom(lookupPaths, application) {
Object.entries(lookupPaths).map(entry => {
const [under, buildPathname] = entry;
const paths = Object.keys(parseImportmapJson()).filter(path => path.match(buildPathname(under)))
paths.forEach(path => registerControllerFromPath(path, under, application))
});
}
function parseImportmapJson() {
return JSON.parse(document.querySelector("script[type=importmap]").text).imports
}
function canRegisterController(name, application){
return !application.router.modulesByIdentifier.has(name)
}
function registerController(name, module, application) {
if (canRegisterController(name, application)) {
application.register(name, module.default)
}
}
function registerControllerFromPath(path, under, application) {
let name = path
.replace(new RegExp(`^${under}/`), "")
.replace("controller", "")
.replace(/\//g, "--")
.replace(/_/g, "-");
if (name.endsWith("--")) name = name.slice(0, -2)
if (canRegisterController(name, application)) {
import(path)
.then(module => registerController(name, module, application))
.catch(error => console.error(`Failed to register controller: ${name} (${path})`, error))
}
}
// Lazy load controllers registered beneath the `under` path in the import map to the passed application instance.
export function lazyLoadControllersFrom(under, application, element = document) {
lazyLoadExistingControllers(under, application, element)
lazyLoadNewControllers(under, application, element)
}
function lazyLoadExistingControllers(under, application, element) {
queryControllerNamesWithin(element).forEach(controllerName => {
loadController(controllerName, under, application)
})
}
function lazyLoadNewControllers(under, application, element) {
new MutationObserver((mutationsList) => {
for (const { attributeName, target, type } of mutationsList) {
switch (type) {
case "attributes": {
if (attributeName == controllerAttribute && target.getAttribute(controllerAttribute)) {
extractControllerNamesFrom(target).forEach(controllerName => loadController(controllerName, under, application))
}
}
case "childList": {
lazyLoadExistingControllers(under, application, target)
}
}
}
}).observe(element, { attributeFilter: [controllerAttribute], subtree: true, childList: true })
}
function queryControllerNamesWithin(element) {
return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).map(extractControllerNamesFrom).flat()
}
function extractControllerNamesFrom(element) {
return element.getAttribute(controllerAttribute).split(/\s+/).filter(content => content.length)
}
function loadController(name, lookupPaths, application) {
if (canRegisterController(name, application)) {
let errors = []
let success = false
let promises = Object.entries(lookupPaths).map(entry => {
const [under, buildPathname] = entry;
return import(buildPathname(name))
.then(module => {
registerController(name, module, application)
success = true
})
.catch(error => {
errors.push([buildPathname(name), error])
})
})
// wait for all promises to be resolved
Promise.all(promises).then(() => {
if (!success) {
console.error(`Failed to register controller: ${name}`, errors)
}
})
}
} here is usage of custom script loader: import { application } from "controllers/application"
import { lazyLoadControllersFrom, eagerLoadControllersFrom } from "./custom_loading"
eagerLoadControllersFrom({
controllers: (name) => new RegExp(`^${name}/.*_controller$`),
components: (name) => new RegExp(`^${name}.*controller$`),
}, application)
lazyLoadControllersFrom({
controllers: (name) => (`controllers/${name.replace(/--/g, "/").replace(/-/g, "_")}_controller`),
components: (name) => (`components/${name.replace(/--/g, "/").replace(/-/g, "_")}/controller`),
}, application) both eager or lazy loading is working. here is example naming from evil martial post example/component.rb data-controller is set via <%= controller_name %>, in this case to "example" |
Beta Was this translation helpful? Give feedback.
-
I struggled with this for a bit with a new Rails 7 application and the most recent setup style that utilizes
|
Beta Was this translation helpful? Give feedback.
-
Configuring TailwindCSS Rails: #31 |
Beta Was this translation helpful? Give feedback.
-
This worked for me along with @tenpaiyomi and @andrzejsliwa solutions above. |
Beta Was this translation helpful? Give feedback.
-
Do you have any visibility on the implementation of an official solution for use with importmap? I can't get anything to work with the various suggestions here and there. 🙏 |
Beta Was this translation helpful? Give feedback.
-
Hello there!
I am trying to upgrade some of my apps to a nodeless/webpackless approach with importmap, but I am facing a few issues with this gem, for example the way stimulus controllers are loaded is through
require.context
, which assumes webpack. What would be the correct way to do this without webpack?many thanks
Beta Was this translation helpful? Give feedback.
All reactions