Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Hey Dan, htmx maintainer here.

I would love to know in what other two ways relative links are broken, and which event stopped working, so we can get those fixed. With respect to the fix you PRed (thank you, by the way), we did get that merged very quickly, and would love to do the same for whatever is broken here, even (especially) if you are no longer interested in doing the fix yourself. [0]

As for the DX of making changes to htmx: absolutely true! The single file is a DX we've chosen, and it has both costs and benefits, which we've written about in "Why htmx doesn't have a build step". [1]

[0] https://github.com/bigskysoftware/htmx/pull/1960

[1] https://htmx.org/essays/no-build-step/




Hi Alex, thanks for the reply and for your work on HTMX. I love the idea and think there's a strong need for something like HTMX, and if HTMX can live up to those promises then great. For me it doesn't, currently, but it sounds like it does for others.

The relative link bugs are [0] and [1], and I fixed [2]. My fix was merged quickly, but the other two bugs of what appears to be similar significance, have been open for well over a year each. My issue however is less about these specific issues, and more the general lack of support for hypermedia, which speaks to a general state of HTMX not living up to its promises.

As for the DX, I think not having a build step, and code structure, are somewhat orthogonal. I agree with much of that essay, but most of it is about issues with complex build systems and the use of Typescript. It's not about having 190 top-level functions, which, with all due respect, I think indicates a lack of architecture. Again, the issue is less about the specifics, and more about the fact that HTMX is not living up to its promise of simplicity because of how impenetrable the codebase is in this regard.

As mentioned, so far I have found Stimulus and Turbo to be better implementations of what HTMX appears to promise to be. More activity in this space from anyone and everyone is great here though!

[0]: https://github.com/bigskysoftware/htmx/issues/1476 [1]: https://github.com/bigskysoftware/htmx/issues/1736 [2]: https://github.com/bigskysoftware/htmx/pull/1960


Why is the number of top level functions a problem? As long as they are named and organized appropriately I don't see the issue


I love writing prototypes as a single file as much as the next guy, but there's almost 5k lines of code in this one file: https://github.com/bigskysoftware/htmx/blob/master/src/htmx....

They're sectioned off, so "event/log support" and "AJAX" and such are grouped by the type of method, but they're not prefixed so there's no way for your editor to help you explore grouped functionality.

Given that the code isn't particularly organized or structured in any way that I could quickly glean (aside from the aforementioned grouping of related functionality, which is only so helpful), I think I'd be put off from wanting to contribute to this codebase.


De gustibus, but after taking a look personally I think it's pretty good. It's typed to the extent allowed by js, sections are clearly delineated, and if you collapse all the functions you can get a quick overview of every section. I personaly would use a different naming convention, but still their functions look reasonably named.


See: php.


190 functions at a single level strongly implies that they're not named and organized appropriately.


Naming doesn't have anything to do with the number of functions. They section things off in the file, so if you prefer things split into multiple files I can understand, but it's a personal perference in something this size


If you're not using more common organization tools like modules, naming tends to be where organization is implemented: common prefixes, etc. Even if they're carefully done, you still end up with a long list of names, with a lot of implicit rules to keep in mind to decode the name (this is why Hungarian notation seemed like such a good idea in the beginning, and a bad idea once it was actually extensively used). Names shouldn't need decoding.

I haven't looked deeply at HTMX, so I won't claim they're falling prey to this exact problem. But it's definitely a code smell that's concerning.


i don't mind a lot of functions in a single file:

https://htmx.org/essays/codin-dirty/#i-prefer-to-minimize-cl...


The more you need to hold in your head at once to understand code the harder it is to do understand, and the harder it is to contribute or onboard to a codebase.

A lot of functions doesn't necessitate a lot of things to hold in your head, but in my experience, HTMX hasn't got enough other structure to prevent this. I was not confident about the changes I made, and the reviewer was not confident about the changes. In a well architected codebase the goal is for these things to be obvious.

As for minimising classes? Sure. I can get behind that. But I think it's orthogonal to having a lot of top level functions with no clear naming or sorting.

If the goal is to have a single-file codebase, I'd suggest considering the following (you may already have done so, but I haven't noticed consideration in the few documents I've read):

- Structuring the file into clearer regions – there is already some of this, but comment blocks are easy to miss in a 5.2k file, and utilities are everywhere.

- Adding named closures for grouping related functionality – "classes lite", at least gives some function namespacing and code folding.

- Ordering the file to help direct readers to the right bit, literate-programming style, so that there's a sort of narrative, which would help understand the architecture.

- Function name prefixes to indicate importance – is something the entrypoint to core functionality? is it a util that should be considered a sort of private function?

- Pure functions – so much of the code is state management performed in the DOM, which makes it hard to test, hard to know if it's working, hard to know what interactions will be introduced, etc. State management is always hard, but centralising state management more would be good.

- (That said... arguably library internals are the place to have make the low-abstraction high-performance trade-off with a bunch of mutable state. However, this makes it hard to have other Javascript that co-exists with HTMX, because it's too easy to stomp over each other's changes. A better integration path, like Stimulus, might alleviate this and retain HTMX's control over the DOM).

- I understand the preference for longer functions, but `handleAjaxResponse` is a lot. More abstraction would really help make this more understandable.

I get that personal preferences are key to why HTMX is the way it is, but I think it's important for the general health of open source projects that others are able to contribute, safely and effectively, and I'm not sure the current choices are most conducive to that. Hopefully some sort of middle ground can be found where HTMX doesn't lose its "personality"(?) but where some of these things can be improved.


Obviously it's an idiosyncratic way to organize code, and I get that someone coming in and making a one-time contribution might be a bit overwhelmed by how different it is than other codebases, but I've been happy with how easily i've been able to come into it after taking time off and figure things out, particularly when debugging.

We have other people contributing on an ongoing basis and i've had people (mostly non-JS people) comment on that they find the codebase easy to navigate, since you don't have to rely on IDE navigation.

There isn't a lot of state involved in htmx: it's mostly passed around as parameters or stored on nodes. History, where you ran into a problem, is a tricky part of the library that's hard to test, and that's where most of the problems have cropped up. I could probably factor that better to make it more testable, at the cost of more abstractions and indirection.

In general, I'm not inclined to change the organization much (or the codebase, per the article above) so that we can keep things stable (including the behavior of events, which apparently changed on you at one point.) I'll sand it, but I'm not going to do a big refactor. We've had people come in and propose big changes and reorganizations, and we've said no.

https://data-star.dev is a result of someone proposing a big htmx rewrite and then taking it and doing their own thing, which I think is a good thing. htmx will stay boring, just generalizing hypermedia controls.

I did a walk through the codebase here:

https://www.youtube.com/watch?v=javGxN-h9VQ

Given the lack of API changes going forward, I hope that artifacts like that, coupled with overall stability of the implementation, will mitigate risks for adopters.


I think an opinionated stance is in general a good thing when it comes to open source project. I just worry that contrarianism in frontend development is being conflated with contrarianism in general programming. The former being the intention of the project that I support (yay hypermedia!), and the latter most likely not being the goal.

Maybe the sanding will help, and if HTMX is not going to change much then maybe not much is needed, but I think there's still a way to go to stability and feature completeness.

> https://www.youtube.com/watch?v=javGxN-h9VQ

Thanks for the link, I'll definitely give this a watch!


i'm pretty contrarian in programming as well, so it tracks:

https://htmx.org/essays/codin-dirty/

https://grugbrain.dev/

htmx is feature complete (see the article)

we can argue about stability, but we have lots of happy users


> The relative link bugs are [0] and [1],

Thanks. Genuinely asking something here: are relative links actually common in frontend? I've only ever used absolute URLs for static assets. Actually I even made tooling that ensures that static asset paths will always be correct: https://yawaramin.github.io/dream-html/dream-html/Dream_html...

Of course, this kind of tooling exists for many frameworks. But I've never seen frontend frameworks suggest using relative links for static assets, they always seem to put them in a separate subdirectory tree.


Relative links are common in the Microsoft ecosystem; the IIS webserver, in its default configuration, serves websites at a subpath named something like /Our.App/ instead of at /. Frontends often use the <base> tag so that they can refer to assets by relative path, allowing different environments to have different base paths.


Random developer here: in my code, yes they are common. I tend to write modules so that I can route to them from wherever, reuse them, and move them around as I please. Relative links help greatly with such things.

Besides that, they are a very basic part of the spec, and I consider anything that breaks them to be truly fundamentally broken.


Understood, but how do you deal with static asset caching and cache-busting with version markers or similar? Does your framework/build system/whatever automatically add version markers to static assets that are bundled directly with your module?


I tend to keep statics consolidated and slap middleware on those routes to handle expiration headers and whatnot. Versioning is handled where links are generated; an href="{{ linkfor(asset) }}" style resulting in generated urls complete with content hash tacked on. I almost never construct asset links without some form of link generator function because said functions give me a centralized place to handle things just like this.

(edit: I should clarify that I'm mostly working in Go these days, and all my assets are embedded in the binary as part of compilation. That does make some of this much easier to deal with. Javascript-style build mechanisms tend far too much toward "complex" for my tastes.)


OK, wait, you said earlier that you bundle static content like images in the same module as the feature itself, but now you're saying you keep them consolidated in a separate place? You also said earlier that you commonly used relative links but now you're saying you generate them with a link generator function? I'm confused about what you're actually doing...if you look at the link I posted earlier, you can see how I'm doing it. So...are you using relative links like <img src=dog.png> or not?


I was unclear; I bundle them per-module; each module has an asset route with attached middleware. So for a users module, there will be users/assets or some such route, to which various static-related middleware gets attached.

The link gens inherently accept relative paths (foo) and output relative links with appropriate adjustments (foo.png?v=abcdef), though they can generate fully-qualified links if circumstances warrant (eg I often have a global shared css file for theming, though that's all I can think of offhand other than "/" for back-home links). The module can then be mounted under /bar/blort, or wherever else, and the links remain relative to wherever they are mounted.

On occasion I do hardcode links for whatever reason; my rule of thumb is relative links for same level or deeper, or root-anchored if you're referencing a global resource outside the current path, but there aren't many of those in my apps. A rare exception might be something like an icon library, but I tend toward font-based icons.

To put the rule more succinctly, I never use "../foo". Going up and then over in the hierarchy strikes me as a recipe for bugs, but that's just instinct.

I also never use fully-qualified (including hostname) urls to reference my own assets. That's just madness, though I vaguely recall that WordPress loves to do crap like that; there's a reason I don't use it anymore. :)

So the main difference if I understood your link correctly, would be that I would have maybe one or two items in /static/assets by root-anchored urls, and the rest accessed as /path/to/module/assets/whatever (edit: or just assets/whatever from within the module).

Because I'm doing this in go, the module's assets live where the code does and gets embedded in a reasonably standard way. I actually tend to use git commit as my cache buster to avoid having to hash the content, but full hashing can be done as well very easily just by changing the generator functions.

I can then just import the module into an app and call its associated Route() function to register everything, and it just works. Admittedly it's very rare that I reuse modules between apps, but it's easily possible.

For background to my thought process, I originally started using the link gen paradigm long ago to make it easy to switch CDN's on and off or move between them, back before whole-site proxying was considered best-practice. The rest is just a desire for modularity/encapsulation of code. I like to be able to find stuff without having to spelunk, and keeping assets together with code in the source tree helps. :)


Thanks for the explanation!


Sorry, forgot about this bit:

> and which event stopped working

It was `htmx:load`. On 1.x this fired on first page load, which I used to do a bunch of setup of the other little bits of JS I needed, and which would cause that JS to re-setup whenever the page changed.

On 2.x this never fired as far as I could tell. Maybe I got something else wrong, but only downgrading to 1.x seemed to fix it immediately, I didn't investigate further.

I did wonder if there were breaking changes in 2.x, but as far as I could tell from the release notes and documentation there were not.


Maybe I'm missing something here, but JS modules do not require a build step.

* Note to non-JS hackers: JS module symbol scope is per-source file.


JS modules can't be imported with a plain script tag.


Script type=module doesn't work?


Not if you want to support the 0.2% market share that IE 11 has.


That's true, but also a trade-off that is a perfectly valid engineering choice for many or even most teams.


HTMX 2 stopped IE support, so that shouldn't be an issue.


we don't :)


we want to support the traditional script tag w/ a src attribute and nothing else


Why?


idk just like it better that way


Chiming in to say modules are awesome and you shouldn't be so scared.


I'm not scared, I just want people to be able to include htmx on their page the same way, for example, good ol'jQuery is included.

We generate esm and cjs modules for people who want to use htmx in that manner though:

https://github.com/bigskysoftware/htmx/tree/master/dist


you can define import maps in a separate <script> tag and reuse the module name elsewhere


sure, but I wanted the same experience of, say, jquery, where you just drop in a script tag w/ a src attribute and it just works

an aesthetic decision, I suppose


Depending on the import tree depth, it significantly increases latency.

(Which is why bundlers still exist.)




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: