Why Htmx Does Not Have a Build Step(htmx.org) |
Why Htmx Does Not Have a Build Step(htmx.org) |
""" My goal is that if I have a site that I made 3 or 5 years ago, I’d like to be able to, in 20 minutes:
- get the source from github on a new computer
- make some changes
- put it on the internet """
Front-end build scripts make sense for projects that are constantly developed by a team that is actively ready to incrementally fix any problems that come up.
For sites that only get maintained every year or so (and I have plenty of those myself) my experience is that they often cause more problems than they solve.
I also found that I started really enjoying JavaScript development again once I gave myself permission to ignore the npm/webpack/etc script world entirely and just write regular code that runs in browsers!
I was very delighted to find htmx solves that problem for me!
today i had a conversation with another programmer who is doing mainly backend work, and shudders in despair looking at anything more complex with frontend javascript (and that's not even considering build tools or frameworks). i thought, perfect, that's exactly what i can help with, and then i realized that, sure i can use frameworks and build the most complex interface, but if it takes anything more than a script tag to load up the code then they will never be able to maintain it.
"i need to do WHAT to deploy this?"
i love building SPAs, i am not a stickler for minimizing the need for javascript. if it helps to solve a problem then i'll use it. but deployment has to be so easy that someone who has never touched the project can do it. "here is a website. it works. go make a copy of it. add your changes and send it back."
in the process i asked myself why go through the trouble and not just use the build tools like everyone else. and i came pretty much to the same answer. when i need to make some changes to the site in a few years, i do not want to be stuck with having to figure out how to get 5 year old build tools working before i can even start working on the site.
the current site, btw was build in 2016, and it still works, and i was able to dive in with adding new features without delay. the upgrade of the framework is optional, because i want to take advantage of new features not because the site would have stopped working otherwise.
With a proper `package-lock` and `nvm` i picked up 3 or 4 year-old projects, installed everything with one `npm install`, made my changes and never faced a problem.
Edit: I remember facing one issue with `node-gyp` but that was just a poor choice of packages that were trying to be too native.
Today, nothing works, module resolution problems everywhere. It's going to take me days of fixing the build system now just to get the initial compile working again.
Node is a disaster.
Bingo.
That's mostly why!, you need to have installed something. If you don't use a build step you don't even need nodejs, your browser is your only dependency.
> With a proper `package.lock`
I've never heard of package.lock. Are you as familiar with the subject as the general tone of your comment implies?
I’m not a front end dev, but I’m found the same to be true with the few occasions I’ve found myself needing to spin something up. For whatever reason, frontend dev culture doesn’t seem to value robustness in tooling - defined, as Julia noted, in being able to have tools that just work. I’m not sure why that is, but it doesn’t necessarily mean build tools as a concept are wrong. It just means that the standard implementations for them don’t seem to value developer experience.
I told them that it's value proposition was not that it "added" anything but that it "eliminated" things, like build systems, configs, and the need to use js frameworks in incidences where it's an overkill (which in my experience is majority of the cases)
HTMX is a good example/manifestation of Nassim Taleb's "Via Negative" heuristic i.e some systems can improved simply by the removal of components that are dangerous, ineffective or cause friction. As opposed to trying to improve systems by trying to add components that we think will make a system better. [0]
A lot of technology is focused on improvement by adding, but I think we should start thinking more in this paradigm of improvement by subtracting — subtraction complexity that makes systems more difficult to maintain & build upon.
“Write code that is easy to delete, not easy to extend.”
― Antoine de Saint-Exupéry
Between that, HTTP2/3 and ES modules many of the downsides for building apps with no compile step are almost completely mitigated.
IMO build step woes are really overstated, my build step has linting, type checking, optimization, bundle analysis size diff checks, enables JSX, and is basically necessary for development anyway as you get hot reloading, compiler-added features for better debugging, etc.
It used to, but browsers ruined it because they didn't want to help improve caching support
If you don't have tons of code, tree shaking and minification is not that important. If you depend on a couple libraries (Jquery, for example) those can be minified and just refered to directly in the source.
I haven't done js since jquery days, but keen to get into modern js development
1. Even if you use a language like TypeScript in your library, your users shouldn't have to. Provide types to them, even provide features like decorators, but have first-class and ergonomic ways for plain JS users to accomplish the same task.
2. Frameworks don't need compilers. All the reasons those that do claim to have them: especially performance, ergonomics, are disproven as requirement by libraries that meet or exceed them on those axes. You especially don't need to fork JS and HTML, and doing so fragments the web dev ecosystem, and causes tool friction, for very little gain. You can offer a compiler, but it should be an optional optimizer.
However, even as compilers should be optional, I also think they should be possible. If users want TypeScript to check their work, and especially if they're in large teams and organizations where extra checks help collaboration, your approach shouldn't prevent it.
And if you're doing things in a language without traditionally good static analysis tools, like HTML, you should try to provide them, otherwise your approach really doesn't scale due to ever-increasing fragility as your code base and team size grows.
- Typescript is very light from the code transformation point of view. If anyone remembers the output of babel especially for async/await + the way module imports are implemented generates a really hard to read code and the "best practices" about hiding the state make things a lot harder. Just think about the fact that back in the days (tm) it was common to inspect the state of the app and change it full lisp style and now the js console can mostly be used for quick code tests and looking around when execution is stopped at breakpoint and you can't see that much anyway because of how the code is generated. - The whole babel thing still feels like a madness to me. Yeah, you're getting some new language features but for the price of non obvious code transformation that makes you need source maps (and you need to make them work and maintain that) and all the rest - Build step + npm results in on controlled bloat of the js bundle. `npm install <random-component>` may inflate you bundle by 500kb easily - And with all that there is still a quite big trouble with requiring necessary modules for the use on frontend. Just think that there is still no simple way to require all css provided by a library from npm
Another big problem on my opinion is the propagation of "write once/use everywhere" approach. It works sometimes yeah, but you need to sell your soul for that: you'll have to use something like next.js and that implies that you're bound to use node.js and then you either do all your development in node or have another service doing stuff in the language of your choice and then have a node service as a templating layer and then you need containers and orchestration and so on which makes it a technically challenging project for something that almost for sure can be done in some trivial set of technologies if this requirement had been dropped.
One could argue that we need a different approach with libraries that acknowledges that frontend is distinct world and aims to provide ready made components specifically for the web with an easy setup and no/minimal dependencies. Maybe even with a vendor folder like approach, since the won't be too much code anyway. That should be enough for 99.999% of projects and the rest can use the full scale approach and embrace complexity if they really need it.
I broke my dev machine because a bad application of reps + brew, and triying to reinstall all was a messy mess.
Now I doing things with nix, and is almost good, yet I can't escape dependency hell completely...
As far as the C code that I've seen from the past (early 2000s), they do compile pretty fine even today. I doubt if "C" belongs there.
C code from 1999 will, unless written by a literal psychic, not have "opt[ed] for new language features" that arose in the interim.
Definitely not. Typescript upgrades are nearly always breaking in some way. But the fun really starts when upgrading typings (type definitions), or when a library that used to have no typings starts including typings which are more often than not not compatible with the 3rd party typings causing a whole headache by itself.
I love the concept of Typescript but I’m glad I left it behind in favor of Blazor (laugh if you want). It allows me to focus on the customer problems in the limited time I have rather than fixing my build for the Nth time.
Also, lately the Typescript type system has become more complex and the typings itself have also grown more complex, with things like:
export type SomeType = IPickOne<SomeOtherType,SomeProperty> | IPickExcept<SomeOtherType2,SomeProperty2>;
Utterly insane and you need to pre-compile the types in your mind or rely on auto-complete to understand them.I get idea that some parts of the front-end ecosystem are designed to be busy work, or at least usually turn out to be. But not all of us work in a SaaS development team that can affort 30% or 40% to just play with front-end build systems. Some of us work for clients instead, and need to make the best of the time we have. And more often than not, that means excluding risk factors like large parts of the NPM ecosystem. Limiting things to some simple gulp, dart-sass and some terser to “build” the front-end.
I feel like this build step would still work in 5 to 10 years:
- esbuild is compiled into a single binary for each target system so has minimal dependencies.
- NPM makes the version immutable, and will download the same binary version on ‘npm install’.
- I will use .tsx as my extension but use only pure JS, this gets me IDE suggestions without writing TS types, which works well for small projects.
Compared to webpack, esbuild seems unbreakable.
Maybe I don't understand the right way of doing things with htmx, but returning large amounts of html seemed like it's a waste of bandwidth to me. That said, I appreciate htmx not having a build step. Even tailwind gets annoying to me.
I highly recommend the “building hypermedia systems” book (free at https://hypermedia.systems/) so you can understand the HTML/ hypermedia architecture of which htmx is a key component and how it’s meant to be used.
Or don't use a framework at all. It's easy enough to convert JSON to HTML — you only need a few lines of JS.
It only sends large amounts of html if there is a lot of data, and if there is a lot of data it will send large amounts of JSON. html in itself doesn't add much, it's the data that makes the bulk of the content
I view JS as a particularly unreliable language to build solid (as in dependable, consistent, coherent, and sound) projects in, and the various build tools are just ways to at least partially overcome those inherent shortcomings of the language. Transpiling from ESNext to ES5, merging disparate source components, etc are all just icing on the cake and not the real reason to use TypeScript (though I am surprised that the article talked about its ECMAScript version restrictions caused by IE11 support but didn't talk about how w/ TypeScript you can use language-level (not library-level) features such as the spread operator, for ... of, async/await, etc. and have them automatically converted/transpiled to functional equivalents that work on ES5 browsers).
Just look at how the various @typescript-eslint/* packages can supplant basic eslint rules, replacing them with more precise variants¹ by using the extra information provided by the TypeScript AST instead of just the info surfaced by the JavaScript AST.
¹ Let alone the existence of the hundreds of additional lints that are only possible for TypeScript codebases.
Part of my job is to maintain more than 50 web projects. That includes streamlining, refactoring, upgrading, optimization etc. Some of them are a decade old.
The most painful and time consuming part is dealing with JS issues.
To anyone reading this: cut the fat and do it early. A little bit of perceived “DX” is not worth writing “npm install”.
Just write the code that needs to be written in a straight forward manner. Lean on standards and don’t get distracted by fads. Prioritize stability.
Use libraries that do so as well.
I just ran into a case yesterday where some code ported from JavaScript to TypeScript five or more years ago once more wouldn't compile after upgrading from TypeScript 5.1.x to 5.2.x and in this case it was a DOM event handler that for some reason (probably copy-and-paste from another location in the codebase) had a `return foo;` in a couple of the conditional statements in the event handler callback. Now obviously event handlers don't return anything, and anything they do return is ignored (perfectly normal for JavaScript). TypeScript didn't error out on that, but it did error out because not all the code paths returned a value, so it was no longer able to infer the return type.
A function (that returns a value) that can execute along some logic path without returning a value is a soundness issue. Granted it didn't actually cause any problems (as the return value was always ignored in all cases, the solution was to just remove the `return` statements that never should have been there) but tsc incidentally caught something funky that never should have been in the code in the first place.
So if you view incremental tsc releases as "tightening down all the bolts" then I think its behavior makes sense. I wish tsc caught everything from the start, but I'm only ever happy to see that my code no longer compiles because it means that something tsc *wrongly* accepted before is now being caught - which usually means that potentially errant runtime behavior has been identified and this is a chance for me to correct it. Which is, after all, the whole point of turning JavaScript into a compiled language - so I can catch errors (or at least as many as the TypeScript language is empowered to and the TypeScript compiler is smart enough to) at compile-time rather than at run-time.
(But I'm not primarily a web developer, I hate loosely-typed languages, I absolutely abhor runtime errors, and I think any soundness holes in a language should be treated alarm bells left and right, so perhaps my opinion is not actually reflective of the average webdev.)
The bigger issue I've found is when you're using 3rd party type definitions for some library you can't always trust them to be correct or updated. It's a major risk for the soundness of your build.
It's most visible in the Angular ecosystem, where both the core library and additional stuff have such bounds.
For this reason Angular updates are often a terrible experience for everyone involved.
That said, I do wait 2-3 months to give time for dependencies to update.
As I remember, it never saw broad adoption because it was technically challenging to integrate with, and it tended to push a lot of resources clients already had, so it wasn't a big win for efficiency anyway.
Plus you basically get it for free because it's browser cached because 75% of the sites out there are using it.
> Especially when the old project in question is not yours
Then it falls out of the stated scope of "updating my small website once every 5 years or so"
If someone writes a comment intended to come across as knowledgeable about something (NB: in service of trying to downplay the experience of others, including the person they are responding to), but their self-report gets something as wrong as referring to package-lock.json as "package.lock", which is in the ballpark, but far enough off to be weird, then it raises questions about just how much experience they actually have with that thing (including and especially relative to the person they were trying to contradict).
They use slightly different wording but are describing the same basic idea: This syntax will be ignored entirely and then optional/other process can use this area for whatever.