Some of the more compelling newer features of Rails like Propshaft and importmaps have resulted in the framework no longer having a hard dependency on Node.js for asset bundling. I’ve found that not requiring Node has made Rails even easier for newer developers to get started with, and greatly simplifies deployment for new apps (or existing apps that are updated to take advantage). I may be reading the guides wrong, but it seems like Hanami still has requirements for having Node.js available so I was wondering if the team has any long-term plans for removing that dependency?
Not having Node.js as a dependency is also a goal for me. What I’m building needs to be self-hostable and eliminating the frontend build step would greatly simplify that process. I can’t speak for the long-term planning of Hanami/Hanakai, but I will certainly be taking a stab at it in the next few months, if no one else beats me to it of course.
Hey David! Thanks for checking this out
You’re right: Node.js is currently a requirement for our assets layer. We chose this to begin with because it would support the widest range of frontend arrangements.
I’d love for us to be able to provide a simpler non-Node.js-requiring alternative. What would it look like, in your eyes? Is it like Rails’ import maps support?
To make this happen we’d need someone to come in and champion it. I’d be happy to provide support for the effort. @wout or @davidcelis, if this is either one of you (or both!), that’d be awesome. ![]()
Note: I would not want us to go beyond two blessed first-party ways of doing assets. Any more and it would be confusing to users (I’ve lost track of all of Rails’ ways of doing it) and too hard for us to support.
I’d be happy to help out here, or take on the complete task if no one did so before me. Right now this is not very high on my priority list though, so it would be something for in a few months.
@timriley I completely agree on limiting it to two options. Importmap for simple frontends (e.g. htmx + Alpine.js + plain CSS), and esbuild if there’s a need for preprocessing, tree shaking, and/or legacy support.
The cherry on the cake would be to have something like hotwire-spark to get (hot) reloading via SSE/WebSockets in development, but that’s more long-term, and probably also a separate gem.
Personally I’d like to see this be totally configurable on hanami new something like hanami new my_app –bun or hanami new my_app –vite
I’m admittedly much stronger on the backend than I am on the frontend
I’ve used Rails’ import maps and preferred that to having to bundle, but the frontend ecosystem moves so quickly that I don’t know if something else has come along that’s becoming more of a canonical standard. From @wout’s message, it seems like maybe import maps are still the way to go for this kind of alternative?
As far as I know, importmap is the best option if you want no build step. It’s a web standard so it’s (hopefully) less likely to be replaced by the next shiny new tool like it’s customary in JS land
.
In my experience, it’s really, really hard to get importmaps to work nicely in anything beyond the most rudimentary needs. I actually tried to write a version of an importmaps resolver that fixed a number of issues that Rails’ importmaps approach has; but ultimately the maintenance overhead (especially once you factor in modern packages that are composed by smaller packages, private packages, and dependency updates) killed that project.
I think that ultimately it’s easier from a conceptual & maintenance standpoint for JS bundling to be in node; with a basic entrypoint/configuration approach. It’s easy to explain, automated dependency management can already pick up the changes, and the variety of JS runtimes gives people options.
I can understand the hesitancy to keep something as messy as JS in the dev environment; but I think Hanami has a chance to be good neighbors in the web stack with JS, rather than deriding it.
Thanks for this helping input, @tcannonfodder (and welcome to the forum!).
One thing I should point out is that we also rely on our hanami-assets esbuild plugin to find CSS (via JS entry points in our case), but also images and fonts and any other static asset you want available in the frontend.
These files get built or copied into public/, and are then indexed in assets.json manifest files (one per slice), which our asset helpers reference to generate the relevant URLs or HTML tags.
Any approach that looks to work without Node.js will need to handle all of this logic, too. It does make me wonder how worthwhile it is, given the all limitations I hear about using import maps for real life.
I’ve pointed Konnor Rogers to this post. He seems to be very expert in these things and could have some sage advice to give us. (Update: he does! We can wait until he has time to kindly weigh in).
Yep! Konnor and I have passed Miyazaki memes about importmaps back & forth dozens of times ![]()
![]()
By that, do you mean that you strongly believe they’re an insult to life itself? ![]()
What was going to be a long post is pretty simple: (edit: this still ended up being long…oops.)
Hanami should only support as many bundlers / “asset management” systems as it feels comfortable with out of the box. Every additional “out of the box” way to do it introduces its own level of maintenance burden.
The good news is Hanami already has the plumbing for working with a common JSON schema for assets in the form of assets.json files. At this point, its largely writing plugins for Hanami to make this all come together.
That would mean the plugin is responsible for “gathering” and “writing” as many asset files as it plans to own to the relevant assets.json file for that slice.
The TLDR is that (without looking at code) a working solution is largely plugging APIs, grabbing the assets you need, and then writing them. Yes, that means a plugin also needs to be written for vite, webpacker, rspack, turbo, et al. But as long as the docs are clear on how the “AssetManager” expects you to write files, this is probably fine.
As a final note, importmaps the spec , are good. they let you do a lot of things. And reduce the number of CLI processes you need running which is always nice.
Rails version of importmaps has self-imposed restrictions that make it incompatible with many packages in the NPM registry, particularly those with “multi-entrypoints”.
If anyone has any interest in this, im happy to “consult” with them and help them along the way if they have any questions. Ive pretty much read most of Propshaft, importmaps-rails, Sprockets, jsbundling-rails, Webpacker, and pretty much anything frontend assets related and have even written my own personal attempts as well.
This is my experience as well. I definitely want to have the option of using a bundler in Hanami ![]()
I am one of the two creators and maintainers of https://www.faucet-pipeline.org. The idea is quite simple: Build tools in JS come and go. We provide a wrapper around them, and curate it for you. And you don’t need to change anything when the underlying build tool changes, you just update. The project was on a bit of a hiatus for a time, but we recently started to revive it. But even with the hiatus, it is still used in production (Rails) apps.
I’m happy to propose a solution for Hanami for the “build” (as opposed to the “no build”) path that is flexible, but still easy to maintain for Hanami.
Yes, unfortunately importmaps weren’t really designed in a backwards compatible way with the way Node and bundler module resolution algorithms work.
Importmaps expect a “flat” tree where all modules share the same dependency. NPM is designed with “isolated dependencies per-package” which means in the case where you have 2 packages with conflicting dependency requirements, importmaps will fail.
importmaps will also fail if they’re using non-standard JS syntax like `import “thing.css”` (though with type assertions importmaps, this may be more do-able in the future)
Any thing which requires custom “Transforms” to run (like React components not transpiled to JS for example)
importmaps are good, but limited.
Thanks for your input, @KonnorRogers!
So it sounds like a task for me (or anyone wants to take this on) is to define the “specification” for Hanami Assets: its expected behaviours and outputs. This is what people can refer to when adding support for additional asset management systems.
If someone does get to building a fully working system using importmaps, this sounds like a reasonable candidate to include in our first-party gems, as the option for people who want a non-Node.js setup, though of course I’d want us to be able to appraise it fully, given it would mean we bring it under maintenance along with everything else, and would need to document it alongside our existing setup.
Thanks for your offer of help! We do already support a “build”-based approach in Hanami Assets right now. It’s based around esbuild and is set up by default with new Hanami apps.
Though once we get the above specification out, sounds like you could write support for Faucet, so you can easily use that with Hanami apps ![]()
Makes a lot of sense to me. With faucet, we chose a very simple contract between the framework and the asset pipeline: A JSON file that maps the original name of the file to the name after it has been processed. This is why the Rails plugin I wrote is tiny and works to this day without any adjustments: faucet_pipeline_rails/lib/faucet_pipeline_rails/manifest.rb at main · faucet-pipeline/faucet_pipeline_rails · GitHub
The processed name in most cases is the name with a hash injected. But in some cases, it is more than that: faucet can optimize images, so the input might be a JPG and the output an AVIF file.
The reason we decided to make the hashing the responsibility of the pipeline and not the framework is CSS, Fonts and Images. A font will be fingerprinted, the resulting path will be used in CSS but also might be used in HTML (preloading). So the pipeline needs the resulting font name for producing the CSS, and the framework still needs to look up the path for preloading. Rails went with an imho weird solution of post processing the CSS again to replace the paths in the already processed CSS.