Reimagining front-end web development with htmx and hyperscript
nomadiq.hashnode.dev
nomadiq.hashnode.dev
The speed on the UI is noticeable for our users (a lot of underpowered androids!) and the interactivity is equal than our prior vue version.
React/Vue is like Nosql: Good for certain niche scenarios but overkill for most use cases. Plain html/css is pretty performant and so easy to do than duplicate the state (+validations, structures, routes,...) between client/server.
So, if you are on the fence: Just try it. Is very likely it will be FAR simpler than you imagine!
For me, I'm building https://www.adama-platform.com/ which is a reactive backend which then makes building reactive/interactive applications very simple.
With tailwind, I can just copy and paste HTML snippets and make it live with data. So much fun!
Web Components predate React as an idea, don't they? Admittedly it too hella long to get them properly standardised.
Except for the long list of issues that WC proponents pretend doesn't exist: https://twitter.com/Rich_Harris/status/1198332398561353728
How does that list square with https://shoelace.style/ ?
I remember there being more, but I can't remember the links off the top of my head (and on mobile).
This is the sad reality for web components. You end up needing so many libraries to do basic things that a framework would handle, that your project just becomes a defacto custom framework.
Web Components originally used a view-model architecture, which I would guess was heavily inspired by the most popular JS framework leading up to 2011: Backbone.
React was a massive departure from Backbone, and sadly the Web Components spec was developed before React had matured and changed the landscape.
Angular.JS was created in 2009 and released in 2010.
I’ll see myself out now
Although I really miss the flexibility of of React/Vue components that can take different parameters, and have rendering logic inside them. jinja2/pug and the likes, just don't have that.
The best part of component-based templating to me is not when it resembles OOP, but when it's functional. Components as functions with all that entails (scope, most significantly) is a step up from the very procedural, preprocessor-style template engines.
A template renders the same every time, given the same input. Rendering a template is like a function. It encourages people to put their logic elsewhere (separation of concerns) and first get the data they need, to only then render the template. Templates are merely views.
A web component in contrast has internal state, which it mutates, or at least can have internal stage and often does have such. Also it relies on global state, which is accesses. It seems to me like a step down from preprocessed templates, because state and behavior are lumped together again and because of internal mutating state. The view layer is no longer cleanly separated.
In practice the resulting DOM often is more convoluted and styles are defined in less obvious ways ultimately on the website. It makes writing user stylesheets and user scripts harder.
You can re-use web components, but realistically every projects adapts or writes their own component anyway and you can only re-use for the same framework or one based on the framework, for which the component was written. Re-use of components seems even less than it was for "my classes from the other project" in OOP, because not only does one have language specific classes, but framework-family specific components.
While not directly necessary for web components in general, I also find that syntax annoying, which mixes some kind of HTML look-alike with JS blocks inside of it. Kind of a reminder of PHP outputting HTML, which contains PHP blocks, which output JS, which modifies HTML. It is also annoying, because now I have to learn an HTML look-alike, which has new rules about what may be nested into what and those rules do not always make sense. I cannot nest as freely as in real HTML. What I write in a web component is not what ultimately gets output, but instead if will be translated at the end of the process. In a template I would simply write my HTML and compose them in such a way, that I have separate templates for semantically separate things, enabling re-use of templates. It is easy to do and the code is real HTML, no need for any indirection of translating a look-alike into ultimately HTML again.
Seems to me, that many lessons of the past have been forgotten.
My point about JSX was also mainly related to that, since I'm already using TypeScript, I can just continue to use that in my rendering logic. Or at least, the logic reads like any programming language, not this psuedo-like syntax.
Jinja/pug/handlebars and the likes, have their own templating language, which is always different from the programming language you're already using. And I personally don't find HTML that difficult to write, that I need to compile into it.
So what I would like to see, is a templating language (such as Jinja/pug/handlebars), but that uses plain HTML, with the ability to use JSX and write custom components (that will just output plain HTML). I don't need any of the state mangement or two-way bindings.
The thing is, that it requires the developer to have some disciplin to divide view from the logic behind it. Simply make a folder called "templates" and put your Scheme files containing SXML stuff in there. For disciplined developers, this works well. In Scheme this works well, compared to for example PHP outputting HTML, because Scheme code in general has a tree structure already and as such an expression can be transformed into an HTML document. No need for any additional language like a templating language, but also not making the mistake to treat HTML as mere string, like PHP does.
JSX kind of treats its HTML-look-alike as a string as well, kind of. In the code you have a backstring wrapped string, which is processed later. There needs to be an additional parser for that, which performs the magic behind the scenes and checks the nesting of elements, allowing JS blocks inside that "template string". No such additional parsing is needed in SXML. Given the mere string before processing it into some internal tree, which is hidden away in the machinery processing JSX, I cannot simply transform the tree it represents, because it is merely a string. I would need to make use of JSX processing tooling, to get an actual tree and work with that. That might be possible.
The only advantage JSX has over SXML is not one, which stems from JSX itself, but from the fact, that JS runs in the browser and as such can run on the client side. If that were not the case, JSX would simply be worse in every way, forcing people to learn another language. Another language, because what one types in JSX template strings is not actual HTML, but something else, which has new rules about what elements exist, how elements can be structured, what attributes exist ("class" ...) and how elements can be nested, without receiving a type error. All that could be avoided, if we had something as elegant as SXML in JS. However, I am not sure, whether it is even possible to have something like SXML in JS. How would one go about quasi-quoting and unquoting? I don't know.
I would actually prefer to simply use JS objects to express the HTML document, so that I can work with them like with any tree. There could be magical attributes, which you can assign some closure to, so that it gets the dynamic behavior like in JSX. Not sure any framework has gone that way. It would be cleaner than inventing an HTML-look-alike and it would allow for more efficient processing, since it does not have to be parsed again, before outputting HTML.
What is Jinja missing here when it has variables and logic in templates? You mean the rendering logic is less powerful so the template file alone can't encapsulate all the logic?
With Jinja it's this "weird" syntax that's completely different from Python and HTML. If that makes sense.
devs probably not (ie: as NoSql, Kubernetes and stuff, some people pick overkill things, and by some I also mean me!), but React/Vue as libraries, certainly!
A SPA is a niche among the most mainstream web site. It makes sense when the interactivity is BIGLY on the cliente (like for example: A paint app).
But most things on web are not 90% client vs 10% server. Is the opposite.
Your todos, mail client, eCommerce site, blog, etc are better served with a server-side approach (note also how much important security is on this kind of apps!).
Your screen that display an interactive map, your game, paint app must certainly need a more client-first approach.
But as SPAs says: Is a "SINGLE" app. One.
Is your full app ONLY ONE? No? Then you are not in the niche, and if you actually have that SINGLE screen/app is likely a fraction of the whole thing.
---
Another possibility is that single thing consume so much that you say, screw it, lets do the rest similar even if is a poor-fit, but I think that is rarely if ever the best (for the users!). This is what drive adoption, React/Vue solve nicely some hard stuff and naively you think is great for the most basic one. But as show the fact that stuff like "back button" get broken without EXTRA workarounds is not the best fit for them...
P.D: Is my opinion -after try for years do the opposite!- that the majority of the web apps be server-driven and maybe add a little of react/vue/alpine for extra nice touch...
watching a time lapse of a single biological cell develop for it's first few hours reminds me of the software tooling landscape... throbbing, twisting, shaking, bifurcating
Problems that most devs wish that they had but don't.
- Heroku
- AWS ECS Fargate
- AWS EC2 (AutoScalingGroups) + Docker
- AWS EKS (managed K8s) + Helm charts
Everything (except Heroku) is/was IaaC.
They are listed in the ascending order of how many problems they cause, and how much we have to worry about them ... I will never ever touch K8s if I can possibly avoid it.
That's not to say the rest aren't bad, of course, but I'd much rather have control in my hands than wondering if the next time it goes down again how many angry phone calls we'll get and if there is even anything we can do about it.
Give me a few bare servers any day, but you take what you can get I guess.
If doing professional work, use heroku, App Engine or any similar PaaS solution.
If doing hobby/side projects, Dokku is a great self hosted heroku alternative. Easy to setup and use.
Kubernetes is a very low level tool. If you want to run it yourself you need s full team of infra experts to not fuck it up. So I think it could be a great tool for large companies with many teams and a lot of money.
As usual the problem is not taking into account context, and single developers or startups thinking they need to do what google does so they go full Go microservices spa Kubernetes crazy. Add some of the Agile bullshit on top of this and welcome to your average startup nowadays.
Professional work on VPS is either a lot more work, or not that professional. If it is not being a lot more work then you're not providing the same service, security and guarantees to your customers.
This is where PaaS shine. Easy and safe. And if you factor in your own time, then cheaper too.
not that js/react are not useful, but rarely should be your starting point. much better to use them progressively when the trade off are worthwhile.
For most projects I have been doing since it was either pure HTML, CSS wirh a few sprinkles of handwritten Vanilla JS, or Jinja2 templates with occasional PHP. When it got really complicated I used Flask or Django.
But to this day I never understood people who use js/react for pages that could have been handwritten with zero js in half a day (including a handwritten fluid CSS layout).
I think people tend to improve their knowledge of some specific tool/language (JS in this case) and wanting it to apply to everything, not just what they initially learned it for and even if it might not be a great fit.
If all you know (because its what the schools/bootcamps teach) is generating HTML via DOM operations in JS, you might not even know better/simpler ways of doing it.
A bit like if someone just knew C and haven't really explored other languages/environments, then they might think that building a server serving HTML is a great thing to build in C, rather than using pre-built tools that serve static files or even using a safer language.
Now add responsive images. Good luck. You need to process then on server side, then handle them on client side etc...
Now add some customer area...
Add a few more features and it becomes complex.
Why would you want to now throw away all this and rewrite in react when you could just start with react.
In addition I can even migrate my old app to the new one and sometimes I can just copy paste certain components and css styles (thanks to css in Js) and it just works.
Honestly I don’t want to write responsive images manually. I just wrote a system once, now any app reuses the same thing. Same for other stuff. Plus I only need to know react and I can build anything.
You telling me now to learn htmlx syntax and use it sometimes, then use react other times just because… it’s simpler. The benefit is minimal, but now I need to know 2 worlds: react and htmlx.
Of course customers change their mind constantly, which is why you anticipate this in you choice of architecture to leave some wiggle room. The scenario you described tho: I never even remotely had a situation where it bit me in the arse that I wrote HTML with JS sprinkled in. For more complex stuff I have flask, django, websockets, htmx, custom REST-APIs I can built in any other language interacting with custom JS on that webpage (most of the software I write is server side). Or I could just take something like Grav CMS and write a custom Theme and some plugins for it. What is a good starting point depends on the kind of website.
There is so many ways to skin the cat it is not even funny. And if you don't know what cat it is beforehand it is your fault for not asking.
Also, my idea of a FE component is that of a class. A black box if you will. It the markup structure with the details as properties. Reusable because all properties are set from outide.
If you're hardcoding CSS within the "component" then it's not a component. And then having to effectively rebuild the entire app because a hardcoded bit of (Tailwind) CSS needed to he changed?? That's a step forward?
From a professional / career stand point I'm interested in React. But its model of thinking feels like 1999 in some ways.
I'm missing something? What am I missing?
As an alternative, you can just assign class names to components and use global CSS like the old days, if you prefer. Sass is pretty great, basically CSS with better selectors and variables.
Have a component-type class in the component then use Sass to configure that with helper properties (read: functions and mixins, etc.)
But hardcoding? Inline? I did that in 1997.
Keyword in. I know TW is done with Sass
If you're editing the actual components then that's a step backwards in time. They should be like (OOP) classes. Completely agnostic. Completely "decoupled". They should be the structure (i.e. markup). But the styling should be abstracted out into "real" CSS, not inlined hardcoded CSS.
Put another way, for all intents and purposes CSS in JS is like using important!. Nuff said.
Sure you can do that with server-side templates, but it's more readable (especially in a team) to use React components.
For me the main benefits of React didn't really shine until I had to maintain complex state and component trees among multiple devs. What is overkill for a simple blog is an absolute godsend for more complex apps.
Although frankly I still think Angular had a much better architecture, and I'm kinda sad React took over. But hey the frontend landscape gets a revolution every year or two (sigh) so who knows what the dominant paradigm will be by 2024.
I'm not here to piss on JS frameworks, it's good that devs push the limits of what's possible, I guess. But there's something to say for simplicity and stuff that's so matured that you can really rely on it. Making sound decisions for your product, your stack and your architecture is a skill.
I agree! It sucks that most jobs don't care about paying for that skill though.
Back then it was CSS that was so rapidly evolving nobody could keep up. Remember the ACID tests? And that was even before flexboxes made things both simpler and more complicated.
Then there was the streaming wars, where RealPlayer and Flash and ActiveX and whoever else competed to be able to deliver <video>.
And what we solve now with CSS layouts and React components used to require frames and iframes and sprinkled JS drop downs that spoke to ColdFusion templates served from a single old Apache instance shared between a thousand users and constantly crashed, and https cost like a thousand dollars and couldn't be used internationally...
The web was never simple. We were just younger then and our neurons weren't so degraded yet. /sigh
The HTML and CSS from the nineties require about five times more work just to get the same results.
This is progress.
As a (bad) side-effect, people have stopped creating amateur websites, because it's hard. HTML was easy and fun, and everyone wanted to create their own geocities monstrosity. I'm looking at modern web constructs like WebXR with disbelief: How is it an hypertext standard if it doesnt even involve html tags (it's just a rehash of openGL , but in JS??). Why dont we make it easy for people to make quirky stuff anymore?
Newbies are immediately plunged into the world of npm, react/vue, and AWS.
Gone are the days where the tutorials are “write a page in HTML and FTP it on to a server”
If you don't want to deal with the tool chain, just go straight to the "Include JS build" section. That one line script tag is all you need.
Markdown and restructured text are both easier than HTML.
Very fast, easy to use, me gusta.
https://gohugo.io/getting-started/quick-start/
easier than HTML, that a lot of people learned in school and with which people interact daily? no
C#
Python
C++
Django
Git
Jenkins
Docker
Kubernetes
Also sort of a web dinosaur here (remember when VRML was supposed to change the web forever?). Even today 90% of the web is static pages with JS sprinkled on top because much of the process of creating that was automated years ago. Contrary to popular belief people aren't actually that generous in their usage of SPAs because it's expensive.Also while React has the highest market share, it's not the only framework out there.
It's just a corporate hellhole now, with Facebook owning the frontend via React, Google and Apple owning the hardware, and Amazon owning the network.
I miss those one person passion project pages that you used to be able to find on Yahoo.
If you're talking about web sites as art projects, there are communities like that although they are fairly small. Neocities is one of the more prominent ones.
The small web still exists, but you have to stray quite a way off the beaten path to find it.
Like most content is now controlled by, what, 5 to 10 corporations?
2022 - 15 = 2007.... Which means you aren't even in the IE era. Hardly a dinosaur if you ask me :)
The other way around, using the dumber tools and trying to do highly interactive apps, is a lot harder.
That might be part of the reasoning most things are nowadays built with these spa like frameworks.
Not that I agree. Just trying to understand.
But for a small, simpler app, maybe one you are working on alone, only having a “backend” is appealing.
I’ve been using Alpine on a project recently and removing the overhead of a frontend to manage has made progress faster.
Have you ever had to debug non-functional SSR in React or Next.js? I very much doubt SSR is simpler than plain HTML or PHP or AJAX.
After all, SSR is just rendering plain HTML. It doesn't have to be that complicated full of gotchas and quirks like it is in the React world.
"I very much doubt SSR is simpler than plain HTML or PHP or AJAX"
What does that even mean?
But my fear is that when things get complex in the frontend, at some point there is a peak point at which these tools become easier and the more traditional ways more difficult. And it is not easy for me to day where that peak point is.
Astro might solve that with selective hydration.
i know they aren't going to be everyone's cup of tea, but htmx is a pretty pure take on "what if HTML had kept going as a hypermedium", generalizing the various limitations currently placed on HTML. htmx stays within the original, REST-ful model of the web, exchanging hypermedia with the server in a way that is extremely compatible w/ Roy Fielding's description of the web architecture.
hyperscript is an event & DOM oriented scripting language, based on HyperTalk, the old scripting language for HyperCard. definitely not everyone's cup of tea, but it fills a need for a general, event oriented language that is easily embedded directly in HTML. This complements htmx really well in many cases. hyperscript is definitely much more speculative, but it has some pretty interesting features, such as async-transparency: https://hyperscript.org/docs/#async
anyway, it's a different take on web dev, but I'm glad to see some folks running with it and I'm happy to answer questions
I swear this whole design pattern in the article looks just like cold fusion did in 2000.
Not sure if this is good practice.
It's not like linux distros figured out this stuff was an issue decades ago...
In the past I did came into contact with git submodules through freelancing for another company, and I agree it was quite a hassle it times using it in a team setting.
Perhaps this is the reason I've found this approach work well with Lua. I've never tried it with other languages that I use, like Swift, Objective-C, C#.
I did work in a company in the past that used Nexus [0] for their dependencies across various platforms. Perhaps Nexus has some tooling build in to deal with transitive dependencies, not sure.
---
Here, I believe in taking HTML and then finding the absolute minimal extensions. My most work is to embed a mustache like primitives into HTML. For example <div rx:iterate="posts"><div><lookup path="title"> - <lookup path="body"></div><div> will iterate over an array and render the title and body appropriately.
I then take it a step further by making the binding reactive such that updates flow in real-time.
I am having a great deal of fun with this approach as I continue to explore this approach.
Last six months with my last employer I spent immersed in the full Next, React, Prisma ecosystem. I swear that 80% of developers' time was wasted on fighting compatibility issues. Actual work happened in the small crevices of time left between endless stream of build breakages or chasing random bugs caused by changes in transitive dependencies.
My God, never again.
I’m looking for a framework that compiles to web assembly to generate next.js to spit out react components that generate JavaScript that prints html.
No, that is not a typo.
Recent example: bundler X insists on import statements including file extensions. But the typescript compiler refuses to let you import foo.js and won’t rewrite an import of foo.ts to foo.js when you build. There’s giant GitHub issues about this but nobody wants to fix it on their end. In short, everything is awful.
Writing javascript in the browser used to be just the same as nodejs, with the exception that you needed a bundler like browserify - which mostly just concatenated everything together. Now I dread setting up new projects. Will typescript work with svelte? Will this webassembly module work with rollupjs? Will it pull in my type definitions correctly? Will the url path system magically work or break? Urgh.
I feel like I’m no longer competent enough to get arbitrary tools working properly together. We need bespoke build systems now because it seems like they’re the only thing that works any more. And that means I can’t throw together a simple server side rendering system like I used to be able to do.
Bryan Cantrill once joked that javascript was the failed state of programming languages. That doesn’t feel like much of a joke any more.
HTTP/2 mitigates the TCP overhead.
You can have an unbundled, unminified web app load quicker than the average bundled and minified React app.
Best of all, you can’t use NPM modules so your codebase can stay ultra-clean!
And wasn’t server push for HTTP/2 removed by web browsers? If the browser needs to do a round-trip to the server to fetch every individual javascript file (and it doesn’t even know which files it needs up front) then the result will be way slower than a bundler. I would expect performance to get worse, not better using this approach.
Not clear to me that the added complexity is worth it personally.
SSR is typically just a way to pre-render the client app into (critical) static assets that can be delivered to the browser quickly, so it can display at least some content before the client app loads, runs and ”hydrates” the server-generated HTML back into the exactly same interactive app (e.g. a React app) that was used to generate the SSR assets.
That does not change, that in practice people will write their web components all in one file and render a template, which contains <MyFancyNamedTag>{js logic here that should not be written here}</MyFancyNamedTag>. The mere ability to have arbitrary js logic in there and the fact, that rendering MyFancyNamedTag actually renders a component, which itself can contain arbitrary js logic, instead of outputting a real tag named MyFancyNamedTag, leads to people coupling everything tightly together. Traditional server side template rendering encourages people to make up their data beforehand and then render the template.
Maybe you don't do it that way. Maybe you are smarter than the average frontend/js-is all-I-know-because-I-heard-its-all-I-will-ever-need developer. Not saying you specifically make these mistakes it.
Edit: With regards to being able to contain arbitrary js code in braces in the call to render a component, this actually inches close to the mess many people make when using PHP. There is was: open php "tag", php logic, output HTML, treating it as a string, inside HTML open PHP again, output some JS, JS logic contains code for manipulating HTML ...
V = f(D)
… which is an oversimplification, since UI commonly has local state, which definitely doesn’t need to be elevated all the way to the master data. Any interactive UI thus certainly uses several sources of truth.Let’s add some to the previous. Let our previous data D now be D_master since it is our master business data (which we may not change in any way, shape, or form). Then, add state of app routing D_router, user auth state D_auth, and local widget state D_widget.
V = f(D_master, D_router, D_auth, D_widget)
We might have all kinds of access checks regarding the three first ones, but here we are only interested in what this means for rendering a view.To use all our data, let’s imagine a form where a dropdown select widget is placed inside a tabbed container. The user needs to have a particular user role in their auth data to see this widget. There are many similar widgets in this form, all with fine-grained controls.
- The options for the list come from D_master.
- The state of the tabbed container lives in the URL so that it isn’t lost on refresh, so it is accessed through D_router.
- The user role comes from D_auth.
- And finally, whether the dropdown is open or not is stored in the component’s local state, D_widget.
This is not a far-fetched example, such things are common in more complicated applications.
In any case, it does make intuitive sense to me to allow logic in the render function, as long as that logic operates directly on the render function parameters and not on some on-the-fly transformed intermediate.
Rules of refactoring also apply.
Without logic in the render function, the coupling between frontend and backend would get insane, as every component fetching data would require custom on-the-fly denormalisation for that data in the backend. In our example, we would be pre-generating data for the entire form for a particular user, probably even for unused features.
It would be like generating snippets of HTML in response to AJAX requests, but without the prerendering to HTML.
It used to be pretty easy to set this up. Modern bundlers, jsx, es modules, typescript, webassembly and modern web frameworks which need their own compilers have all made this much more complex. I’m not sure the complexity is worth it any more.
The situation would certainly be worse in HTTP/1 because of the lack of pipelining and the six connections limit, but HTTP/2 does not make not-bundling in any way reasonable.
Without any runtime boilerplate like module bundling, corejs polyfills and the usual frontend/css-in-js frameworks, all code that gets downloaded is your code, so make it count. 640 kilobytes should be enough for everyone.
But even if you’re not using external dependencies, I maintain: by not bundling, you’re slowing things down, and it’s generally the people on the other side of the world from your servers and the disadvantaged with less-powerful computers and more expensive data that will suffer for your whim.
It’s not npm’s fault for sure, but the ecosystem is fundamentally divergent on this issue and there doesn’t seem to be a universal solution.
A CJS-targeting build can’t slurp up ESM dependencies. Not without dubious hacks, at least.
See e.g. https://stackoverflow.com/questions/70545129/compile-a-packa...
You don't need to complicate things - just use vanilla JavaScript features as they were intended.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guid...
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... expresses it well:
> Use dynamic import only when necessary. The static form is preferable for loading initial dependencies, and can benefit more readily from static analysis tools and tree shaking.
(Also, a quibble over your wording: dynamic imports aren’t a statement, but a call, though not a function call.)
Instead import your initial generic dependencies up-front as usual, but then notionally break your application up by CUJ and load the CUJ-specific code dynamically as needed.
E.g. for a hypothetical email client you would import the initial "inbox view" code upfront (along with generic common dependencies), but you might not import the code needed for a rich text editor until the user clicks on the "write email" button, saving that network load and code execution for when - or indeed if - the user actually needs it.
When I spoke of unused code, I was meaning things like a file containing a bunch of functions, of which you only use one, and so there are the other functions and perhaps deeper imports that weren’t actually necessary; a bundler would remove all the unused stuff. The “solution” in such a case would be to do something like splitting each function into its own file, but that’s worse than before because now your import chains are probably even longer, and even more files have to be loaded (and there is still some filewise overhead, even if HTTP/2 improves it drastically).
I’ll temper my expressed position slightly by saying that bundling is not so significantly advantageous if you have written all of the code (you’re not importing third-party libraries), and keep your file count fairly low and import depth very low. But at that point, perhaps you should have just dropped it all in one file.
That's wrong. The latest TS version allows you to import .js files with extensions just fine! In fact, one of their recommendations is that you write the your JavaScript code as you would if they were intended for a browser (which doesn't automatically append any file extension, doesn't support importing directories by appending /index.js, only supports file-relative or absolute imports (outside of import maps) etc).
If I made my import line be “import .. from ‘./foo.ts’” then typescript was happy, but the compiled output still specifies .ts rather than .js. So the import was broken (that file didn’t exist in tsc’s output directory).
My other option was to “import .. from ‘./foo’”. Then typescript worked but the bundler I was using refused to import the file at all - since it was missing an extension in the import statement. And apparently extensions are technically required by the spec.
The real solution would be for the typescript compiler to rewrite import statements. Like, import foo.ts should be rewritten to import foo.js. There’s a GitHub issue talking about this[1] that was closed because the typescript authors think this is someone else’s problem.
I ended up just giving up and trying a different bundler - which had equally different, but equally frustrating problems.
The ecosystem is a disaster.
TypeScript doesn't rewrite (that part of) your code (it downcompiles ES features if necessary, but won't rewrite import specifiers). Write import specifiers as you would in regular JS code intended for the browser (ie. relative or absolute paths, including file extension, no directory imports, …).
> […] but the bundler I was using […]
Trying to write code that's understood by both bundlers AND produces working output when processed directly by TypeScript is a bit too much to ask at this time. Maybe we'll get there, eventually, but unless it's plain ES2022 code (intended directly for browsers, not targeting bundlers or TS or test runners etc) you have to write the code with a specific intent.
When I tried it, the typescript compiler errored at this - because no such file exists. VS Code also got really confused by this approach. Has the situation changed? If this is the recommended way to write typescript code, why doesn’t typescript’s documentation reflect that?
Typescript needs to pick a lane. A compiler that can’t produce working code is broken. And last I tried, typescript broke when I imported foo.js, and every other option didn’t produce spec-compliant javascript (since the extension is officially required in the import statement).
My take is that if typescript can rename my file from .ts to .js, or change import to require(), it should be able to rename my import statements too. But if you want to die on that hill and recommend people import the resulting .js file, then recommend that everywhere so tool authors can fix their tools! Don’t play it both ways then blame users when we can’t make our code work.
> Trying to write code that's understood by both bundlers AND produces working output when processed directly by TypeScript is a bit too much to ask at this time.
What rot. Being able to turn my source code into working software is exactly what I expect from a compiler. If typescript can’t deliver, what use is it?
Edit: I just tried it and "import .. from './foo.js'" seems to work correctly now in both the typescript compiler and vs code. Good! I'll raise tickets in other tools when I run into problems.
You can use ESM without issue in browsers, Node and using proper ESM bundlers like Rollup, as long as you don't expect `import 'a/b'` to arbitrarily load `./node_modules/a/b.js` or `./node_modules/a/b/index.js`
npm also keeps actively harming the community by not validating packages in any ways whatsoever, so everyone keeps pushing broken crap into the ecosystem.
> You see, it's not that ESM killed JavaScript, all the hacky build tools did.
The problem is that ESM added a massive extra amount of complexity to all those hacky build tools, because suddenly we have 2 different kinds of javascript code and people expect their code to be mutually compatible. That is really complex.
- Typescript's canonical way to import things (import foo from './blah') is incompatible with ESM imports (because imports must have file extensions).
- Nodejs has a plethora of options to configure packages to support combinations of ESM and CJS[1]. Half of those options are deprecated. Lots of tools read package.json - but don't parse all those different options consistently with nodejs's implementation.
- Eg: wasm-pack only knows how to compile code "for the web" or "for nodejs". (You want a package that works everywhere? Adorable.) The web version creates an ESM module, but doesn't specify "type": "module" in package.json - so even though it could work with nodejs, it doesn't.
- Lots of packages in npm are broken in either CJS or ESM mode. And there's no way to tell without trying them.
Essentially, we went from having a simple, extensible system (commonjs + bundlers) to a very complex system. The number of people who understand all that complexity has dropped by orders of magnitude. Now I feel less capable of creating working code than I was a few years ago.
Its an unmitigated disaster for the ecosystem. Maybe in a few years, everything will be using ESM. But until then, I want off this ship.
[1] https://nodejs.org/dist/latest-v18.x/docs/api/packages.html#...
> Lots of packages in npm are broken in either CJS or ESM mode. And there's no way to tell without trying them.
I blame npm for allowing that to happen. I get that it’s technically a third party, but they never even checked that the file mentioned in `main` was included, even without a “postinstall” step.
They, together with node, could have built a tool that validates main/type/exports on publish, but no. There’s literally no way to validate ESM until you run them or use a full linter. The whole point of ESM was that it’s statically analyzable.
Unless you mean for clients.
Still haven't seen anything from "the new JS" that makes me want to switch.
I developed some games like https://github.com/lee101/wordsmashing in a fairly simplistic jquery style i developed where i purposefully avoid using features of JS like "this", it trips most developers up when you pass functions around and the reference to "this" gets lost.
Agree with lots of the comments here about complexity of both JS (new es module compatibility issues etc) and JS frameworks that are limiting what people can actually build these days from cognitive overload and bugs.
a tonne of technologies (like polymer.io/web components) died in a sea of complexity and much technology we use is going to go the same way unfortunately.
Another gripe i think unpopular but true is that we as coders prefer complex things like static site generators like Jekyll/ghost etc, normal people can use a CMS and get stuff done without having to resolve packaging conflicts and SSL issues, we have to resist the urge to pretend coding/setting up complex software is easy because its not... also Kubernetes...
Please stop.
The reason why React is so powerful is because of the maturity of React Native.
If you're a startup deciding your FE framework and you have mobile to consider, then chances are that React is best fit.
Unless you have capital to blow on a separate native team, lest it even be more separated to kotlin / swift, then sure you can possibly afford all these niche FE frameworks.
Lets not even talk about hiring pools.
React is good enough, and it's a shame that many of you think the answer is a new framework with better philosophy.
Trust me, I love reactivity > virtual dom, but React is good enough.
It doesn't need to be the best, it just needs to do its job.
The only thing I'll ever consider switching to is when WASM comes out with a framework - till then I'm betting on React.
You’re adding a lot of complexity to your project which may be unnecessary if you’re app is a simple website with a few interactive elements.
But you're not going to get anywhere near the hiring pool nor are you going to have an ease of choice in native platforms.
React is easily the best decision a startup could make right now.
> If your app is a simple website with a few interactive elements.
lol
There is a huge requirement out of web apps today that SPAs are more and more required than your simple html + css blog posts.
Posts like these is where I feel the age of HN, that are becoming bitter about fields that were once very simple, ending up complex to suit the needs of the times.
Everywhere I look around, people on HN feel like we're still in the age of blog posts and form submissions, when in reality, there are people trying to port applications like Photoshop towards the web.
If Photoshop was available on the web, wouldn't you think that the Front End required an honest amount of "engineering"
And it doesn't even have to be a front-end heavy application like photoshop - it could just be a simple chat application that needs to be on the forefront of the site while you can still browse.
You can use whatever framework you'd like to solve your "bloat" issue, but the problem is, especially for startups, you're not going to get anywhere near the hiring pool, nor or you going to have an ease of choice for your native platform.
This is what makes React so strong right now.
I wished you had addressed this, instead of just deriding the entire front end community.
Sometimes you have to be practical and give up certain areas for the sake of the whole.
I don't even think React is even that bad as you claim it to be, judging from your past posts.
React Native is one of the best things to ever happen to native development, removing the atrocity that is the separation of development of apple and android - one of the most productivity inhibiting factors to a startup out there.
This is why react is so strong.
What's easily the better decision for a startup that has to deal with mobile and native platforms?
I seriously don't know what you people are building that you think low/no js is viable.
Collab tools like Trello Asana need high js
Chat applications need high js
Drafting applications like FanDuel, DraftKings, Sleeper need high js
Music apps like Spotify or Soundcloud need high js
Financial apps like Coinbase or Robinhood need high js
Seriously, what are you people building that you think low/no js is okay.
A lot of devs like approaches like this because they can do more with their server-side language and frameworks that they’re used to/more familiar with, since trying to do everything server-side doesn’t give you the interactivity UX, product, etc wants.
(I personally prefer client side HTML view rendering like React, and only talking to the backend via API calls, but I understand the use case for this sometimes)
normal on* attributes are fine, but they aren't general (e.g. htmx fires a bunch of events you might want to hook into, and you can't use normal on* attributes to cathc them)
hyperscript also has a lot of features that are DOM oriented (e.g. query literals like <div.foo/>)
certainly not everyone's cup of tea, but it dovetails nicely with htmx and fills a need for a general, event oriented scripting language to complement it
All that said I don't think this gives any real benefit over JS, sure, fix node_modules bloat, but I guess I'm a little bit skeptical if there wouldn't end up being hyperscript bloat if it became the biggest programming language in the world powering what for many is their defacto platform and thus everything needed to be solved in it.
SPA still has its own place though: a backend provides the data, and a frontend renders it. the separation of two concerns still has lots of benefits not just for traditional website, but also for browser-based GUI(e.g. Electron apps).
For example, let's say you need to write an admin dashboard with many pages, no fancy stuff. Odds are you'll be very productive with a good old Django/Rails, having no API to write and a single simpler project to maintain.
Once that monolithic framework choice is made, before HTMX, as soon as you needed interactivity, you had to mess with jQuery and the code was quickly awful. The only solution was to plug a SPA framework and develop an API (this is not trivial!). HTMX (and other tools like Alpine.js) solve that problem well.
So, the question is not "is HTMX better than SPA frameworks", but rather: "Knowing that I can now build decent front-end with Django/Rails, does a more complex stack with Next worth it?"
HTMX-like tools make SPA frameworks a little less indispensable in some use cases. That's it.
I write React in my day job and I think it's the bees knees, but I am interested to learn about alternative approaches and projects aiming to create new/revive old paradigms.
Serving html is trivial but do we really need every back-end juggling this stuff just to make these guys happy? What do they do when they have to support a number of clients. Oops...
I don't see this design pattern as forcing a particular back-end framework?
It's merely shifting the frontend<-->backend contract from JSON to HTML.
Some backend engineers arguably would prefer HTML. Especially if their backends weren't Node. Backends were originally C servers that sent a string of HTML back. I think backend engineers are fully capable of "juggling" with this new/old paradigm.
I know some projects plan to only ever use browser clients, so this may seem irrelevant for them. But I'd rather not try to predict how a business will want to use it's data in the future, and would prefer to design a backend that can be adapted to many scenarios without being bound to a particular client.
Sure, then the backend can expose a gRPC / protobuf interface instead. Mobile apps can readily deserialize this and it will be more performant, both in network bandwidth and data parsing, too, especially with first-party to first-party SDKs. It's strongly typed and serves as a better data contract language than both HTML and JSON. JSON is loosey goosey because of JavaScript.
Then the frontend server now plays the role of aggregating data from gRPC backends and converting it to HTML.
The gravitation to JSON is a case of web developer familiarity.
When you build something that is highly transactional, say booking train tickets, and you don't have the resources, like dozen of 200k-400k a year devs, you are better of with a really short path between the where the state is stored and what is displayed to the user.
What we are seeing currently is something like that : DB(ms sql, ...) <-> [ microservices(java spring ?) ] <-> FrontEnd (react ?) With few integration tests and load testing, legacy authentication with oauth or oidc on top, and a really poor user experience (see https://www.sncf-connect.com/). The user experience is in fact poorer that it use to be with php, and even lousier than it use to be with https://en.wikipedia.org/wiki/Minitel
The "antijs crowd" is saying : When it's relevant, let's make more simple systems, but let's still offer solution for some interactivity in the frontend. That's it, it's not a "world view".
https://www.reddit.com/r/htmx/comments/ufsqcj/vue_angular_re...
> So, consider something like google sheets, with a large number of dependent cells that need to be recomputed and synced on every cell entry. Probably not a good idea to jam an HTTP request after every cell edit, and a place where a more elaborate SPA framework is called for. On the other hand, maybe htmx is useful for the settings page for that application. And for something like Gmail, htmx would be perfect.
on click set @src to "https://whatever.com/cat.gif"
the htmx approach would be to replace the image tag entirely
<img src="my_cat.jpg" alt="My cat" _="on click set @src to 'my_other_cat.jpg'"> <img src="my_cat.jpg" alt="My cat" onclick="this.src = 'my_other_cat.jpg'">1. You can easily reimplement all htmx and hyperscript functionality and fine-tune to your liking
2. You are not limited by htmx and hyperscript
Some projects claim they are "zero dependencies" but a few sentences into the readme and you see an `npm install` and now you notice there is `package.json` after all . They meant zero runtime dependencies, which is still praiseworthy, but not zero `devDependencies`.
For my greenfield web app, I attempted to have zero dev dependencies on Node. I wrote some vanilla HTML, JS, CSS, and now needed to reduce network transfer. Requirements:
- minify
- inlining CSS file into HTML
- compress with Brotli
- print out final byte size
- nice to have: run a dev server that serves static Brotli
We know the story with WebPack. I looked to Parcel instead because it's the "zero-config" tool. It was promising because it inlined my `@import css` into my HTML, and logged out the filesize. The watch and reload feature wasn't working as promised, but that was nice to have anyways. For Brotli compression, which is mainstream at this point, I quickly needed to add a `.parcelrc` config. Their code sample didn't work because it turns out that in the real world you need to make your .parcelrc extend/inherit "@parcel/config-default". Parcel stopped logging at the compressed file sizes and not the final Brotli file sizes. Finally, Parcel's dev server is useless to me because you can't set encoding headers for "Brotli" files so the browser just attempts to download it. I didn't have a .gitignore file, and adding this one Node dependency on Parcel added 3,000 new files. To be fair, that's miniscule for a Node project.
I asked myself if I can achieve the same results with CLI tools that are not Node. I first looked to the Golang ecosystem. I stumbled upon https://github.com/tdewolff/minify. It just does minification. It's comparable in output size to other Node minifiers, but faster and just works. The lexer code is very readable, even for me who is not a compiler or systems expert. It's one command to install the CLI, and one command to Minify. You can do `npm install global -g` to get CLIs, but Node supply chains tend to be much deeper.
This bespoke one-liner does the CSS inlining for me:
RUN sed -i "s/@import \"style.css\"/$(cat style.css)/g" index.html`
For Brotli compression, I went with the official first party CLI ( also Go based ) instead of using a third-party wrapper like you'd have to do with JavaScript.One thing I was missing in NPM scripts as a self-documenting way of describing what commands to build, run, test. I've seen cases where people are beholden to a `package.json`, sometimes when their project doesn't even involve JavaScript. When you have a hammer, everything looks like a nail. It turns out Makefiles are a better way of doing this, especially if you don't already have a active relationship with Node
.PHONY: help
help: ## This help.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# DOCKER TASKS
build: ## Build the container
podman build -t ${APP_NAME} .
run: ## Run container on port configured in `config.env`
podman run -it --rm -d --env-file=./config.env -
p=$(HOST_PORT):80 --name="$(APP_NAME)"
It's even better than self-documentation, because the documentation is self-documenting: $ make help
help This help.
build Build the container
run Run container on port
configured in `config.env`Makefile and sed are decades old, stable, available on every Linux distro including the Alpine docker image clocking in at 30mb total. The entire image containing an Linux distro with all the needed CLI tools is 30mb.
└─ ▶ podman build -t base . && podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/base latest b593261330c1 1 minutes ago 30.5 MB
The only dependency here is the Go based minifier, and Go programs are self-contained executables.Alpine contains an entire package manager. Compare with adding Node and one CLI for minification.
└─ ▶ echo 'RUN apk add npm' >> Containerfile && echo 'RUN npm i -g terser' >> Containerfile && podman build -t node . && podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/node latest a28f38037137 6 seconds ago 93.5 MB
localhost/base latest b593261330c1 5 minutes ago 30.5 MB
Assuming dependency management was equal level of maintenance with Node, the spirit of Node ecosystem tends accept being whimsical with breaking changes. Terser or whatever Node-based processing library will introduce breaking changes to their config in relatively small timescales ( months, years, instead of decades ). That's not inherently a technical fault of the ecosystem as it is a cultural one. The popularity of JavaScript is its own undoing. Opting for tools in other web-friendly languages selects for a different type of developer.That's just for minification. Want to inline CSS? That sed command is 66 characters long. That's about the same as Parcel's zero-config config boilerplate that is NoOp: https://parceljs.org/features/plugins/. Surely a WebPack config is more than 66 bytes. I proceeded to add Parcel to compare how much it increases the image size, unfortunately it failed because there is a Python dependency:
└─ ▶ echo 'RUN npm i parcel' >> Containerfile && podman build -t parcel .
[2/2] STEP 18/18: RUN npm i parcel
npm WARN deprecated stable@0.1.8: Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility
npm ERR! code 1
npm ERR! path /usr/share/nginx/html/node_modules/@parcel/watcher
npm ERR! command failed
npm ERR! command sh -c node-gyp-build
npm ERR! gyp info it worked if it ends with ok
npm ERR! gyp info using node-gyp@9.0.0
npm ERR! gyp info using node@18.2.0 | linux | arm64
npm ERR! gyp ERR! find Python
Fine, I added Python, Make, and other transitive dependencies to see how deep this rabbit hole goes: └─ ▶ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/python latest 05582c8b2425 2 minutes ago 156 MB
Will Parcel installation work now? Nope, here's the new error trace: https://gitlab.com/-/snippets/2364041.
It appears this is a dependency issue with Parcel, Maybe?, with this similar 6 week old thread: https://github.com/parcel-bundler/parcel/issues/8152, which reiterates the painpoint about Node.Let's take a step back and forget the Parcel example for a second. How about NextJS? From that 30mb Alpine base image:
```Containerfile
RUN apk add npm && npm i next react react-dom
└─ ▶ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/nextjs latest d177ee032ff0 9 seconds ago 266 MB
Kudos to NextJS for installing properly at least, but that single command added 266-30mb = 236MB to the image, and this is where the bloat creeps in. Thinking 200MB is a nothingburger is how we end up with gigabytes large images.And this is ignoring the time lost watching WebKit process the same thousands of one-liner dependencies over and over.
Instead of the almost instantaneous tools written in go.
He even uses make instead of any of the reinvented square wheels used in JS!