HNHacker News
TopNewBestAskShowJobs

throwitaway1123

469 karma · joined November 28, 2023

Created this account 11/23. Might throw it away at some point.
submissionscomments
throwitaway1123··on The Future of Htmx
Agreed.

Frontend frameworks often do spend a lot of time thinking about the accessibility concerns associated with client side routing, so it's not absurd to consider this question in scope for a frontend library that handles DOM updates.

See for instance this 2019 study by Gatsby: https://www.gatsbyjs.com/blog/2019-07-11-user-testing-access...

Or even the modern Next docs on route announcements: https://nextjs.org/docs/architecture/accessibility#route-ann...

Some of this will have to be bespoke work done on a per-site basis, but I'm not sure I'm comfortable with the idea of completely punting this responsibility to developers using htmx, even if it does make philosophical sense to say "this is scope creep", because ultimately users with disabilities will end up being the ones whose experience on the web is sacrificed on this altar of ideological purity.

throwitaway1123··on App Should Have Been a Website (and Probably Your Game Too)
It's ultimately just a little event listener function that accepts a Request object and returns a Response object. I bundled the service worker by running a quick `npx esbuild --minify --bundle --outfile=sw.js sw.ts` command, and it produced an 18.6kb JS file in 10 milliseconds. That's not even half the size of libraries like HTMX, Alpine, and jQuery.

You can of course use the CacheStorage API directly as well (you're not obligated to use a mock server): https://developer.mozilla.org/en-US/docs/Web/API/CacheStorag...

I've certainly seen crazier things though. People routinely include entire copies of Ubuntu LTS in their Docker images to ship tiny HTTP servers.

throwitaway1123··on App Should Have Been a Website (and Probably Your Game Too)
> They don't work offline

This isn't true. Offline functionality is the raison d'être for Service Workers. You can run an entire HTTP request router on the client to respond to requests while offline: https://hono.dev/docs/getting-started/service-worker

throwitaway1123··on Isomorphic Web Components
> The MDN page has the deprecation notice for the `shadowRoot` property.

The `shadowRoot` property of the ElementInternals object is not deprecated: https://developer.mozilla.org/en-US/docs/Web/API/ElementInte...

What's deprecated is the `shadowroot` attribute on the template element, which the example I linked to does not use. The second line of code in that example is `<template shadowrootmode="open">`.

All of this is mentioned at the very top of the article where it says: "Note that the specification for this feature changed in 2023 (including a rename of shadowroot to shadowrootmode), and the most up to date standardized versions of all parts of the feature landed in Chrome version 124."

> Unfortunately, that support example will also break your code on other browsers.

No, it will not.

throwitaway1123··on Isomorphic Web Components
The only reason I avoid linking to that MDN page is because I think it does a bad job of actually assembling all of the pieces together into a working example. That's why I linked specifically to the component hydration section, which assembles it all into a functioning component complete with an actual class and constructor. The code example in that specific section doesn't appear to use any deprecated or non-standard features. Otherwise, I normally do prefer MDN as an authoritative source of documentation.
throwitaway1123··on Isomorphic Web Components
> Wtf is a shadowroot?

In the Document Object Model (DOM), every document has a root node, which you can retrieve by calling `getRootNode()` on any node in the document (e.g. `document.getRootNode()` or `document.body.getRootNode()`).

Custom Elements can have their own "shadow" document that's semi-isolated from the parent document, and the root node of that shadow document is called the shadow root.

The idea is to be able to create your own HTML elements (which also have their own hidden DOM). If you enable a setting in Chrome's Devtools (Show user agent shadow DOM) [1], you can actually see the hidden shadow structure of built in HTML Elements: https://www.youtube.com/watch?v=Vzj3jSUbMtI&t=291s

[1] https://developer.chrome.com/docs/devtools/settings/preferen...

throwitaway1123··on Isomorphic Web Components
> I'm not sure that's even that correct. If you write code using browser APIs that are currently standardised, your code will work indefinitely, whether or not you use Web Components, React, or jQuery.

The most charitable interpretation of this argument is that framework specific component libraries assume for the most part that you're using that specific framework. The documentation for popular React component libraries like shadcn are largely incomprehensible if you're not using React. Libraries like Shoelace (now being renamed to Web Awesome) make no such assumptions. You can just drop a script tag into your page's markup and get started without having to care about (or even be aware of) the fact that Shoelace uses Lit internally.

> But without Javascript, Web Components are essentially empty holes that can't do anything. They don't progressively enhance anything.

This is not true if you're using the new Declarative Shadow DOM API [1]. You literally just add a template tag with a shadowroot mode attribute inside your custom element, and then the component works without JavaScript. When (or if) the JavaScript loads, you simply check for the existence of a server-rendered shadow root using `internals.shadowRoot`. If a shadow root already exists then you don't have to replace anything, and you can attach your event listeners to the pre-existing shadow root (i.e. component hydration).

[1] https://web.dev/articles/declarative-shadow-dom#component_hy...

throwitaway1123··on How much memory do you need in 2024 to run 1M concurrent tasks?
Promisify converts a callback based function into a promise returning function [1]. If the function has a `promisify.custom` method, `promisify` will simply return the `promisify.custom` method instead of wrapping the original function. Calling `promisify` on `setTimeout` in Node is redundant because Node already ships a built in promisified version of `setTimeout`. So the following is true:

  setTimeout[promisify.custom] === require('node:timers/promises').setTimeout
You could of course manually wrap `setTimeout` yourself as well:

  const sleep = n => new Promise(resolve => setTimeout(resolve, n))

[1] https://nodejs.org/docs/latest-v22.x/api/util.html#utilpromi...
throwitaway1123··on Python type hints may not be not for me in practice
TypeScript has the equivalent of what you're describing via the `Parameters` and `ReturnType` utility types [1][2], and I've found these types indispensable. So you can do the following:

  type R = ReturnType<typeof someFunction>
  type P = Parameters<typeof someFunction>
[1] https://www.typescriptlang.org/docs/handbook/utility-types.h...

[2] https://www.typescriptlang.org/docs/handbook/utility-types.h...

throwitaway1123··on Playground Wisdom: Threads Beat Async/Await
I don't want to get mired in a theoretical discussion about what promise cancellation would hypothetically look like, and would rather instead look at some concrete code. If you reproduce the memory leak from that original Node Github issue while setting the --max-old-space-size to an extremely low number (to set a hard limit on memory usage) you can empirically observe that the Node process crashes almost instantly with a heap out of memory error:

  #! /usr/bin/env node --max-old-space-size=5
  
  const interruptPromise = new Promise(resolve =>
    process.once('SIGINT', () => resolve('interrupted'))
  )
  
  async function run() {
    while (true) {
      const taskPromise = new Promise(resolve => setImmediate(resolve))
      const result = await Promise.race([taskPromise, interruptPromise])
      if (result === 'interrupted') break
    }
    console.log(`SIGINT`)
  }
  
  run()
If you run that exact same code but replace `Promise.race` with a call to `Unpromise.race`, the program appears to run indefinitely and memory usage appears to plateau. And if you look at the definition of `Unpromise.race`, the author is saying almost exactly the same thing that I've been saying: "Equivalent to Promise.race but eliminates memory leaks from long-lived promises accumulating .then() and .catch() subscribers" [1], which is exactly the same thing that the Node contributor from the original issue was saying, which is also exactly the same thing the Chromium contributor was saying in the Chromium bug report where he writes "This will also grow the reactions list of `x` to 10e5" [2].

[1] https://github.com/cefn/watchable/blob/6a2cd66537c664121671e...

[2] https://issues.chromium.org/issues/42213031#comment5

throwitaway1123··on Playground Wisdom: Threads Beat Async/Await
> This isn't actually about removing the promise (completion) listener, but the fact that promises are not cancelable in JS.

You've made an interesting point about promise cancellation but it's ultimately orthogonal to the Github issue I was responding to. The case in question was one in which a memory leak was triggered specifically by racing a long lived promise with another promise — not simply the existence of the promise — but specifically racing that promise against another promise with a shorter lifetime. You shouldn't have to cancel that long lived promise in order to resolve the memory leak. The user who created the issue was creating a promise that resolved whenever the SIGINT signal was received. Why should you have to cancel this promise early in order to tame the memory usage (and only while racing it against another promise)?

As the Node contributor discovered the reason is because semantically `Promise.race` operates similarly to this [1]:

  function race<X, Y>(x: PromiseLike<X>, y: PromiseLike<Y>) {
    return new Promise((resolve, reject) => {
      x.then(resolve, reject)
      y.then(resolve, reject)
    })
  }
Assuming `x` is our non-settling promise, he was able to resolve the memory leak by monkey patching `x` and replacing its then method with a no-op which ignores the resolve and reject listeners: `x.then = () => {};`. Now of course, ignoring the listeners is obviously not ideal, and if there was a native mechanism for removing the resolve and reject listeners `Promise.race` would've used it (perhaps using `y.finally()`) which would have solved the memory leak.

[1] https://github.com/nodejs/node/issues/17469#issuecomment-349...

throwitaway1123··on Playground Wisdom: Threads Beat Async/Await
This doesn't even really appear to be a flaw in the Promise.race implementation [1], but rather a natural result of the fact that native promises don't have any notion of manual unsubscription. Every time you call the then method on a promise and pass in a callback, the JS engine appends the callback to the list of "reactions" [2]. This isn't too dissimilar to registering a ton of event listeners and never calling `removeEventListener`. Unfortunately, unlike events, promises don't have any manual unsubscription primitive (e.g. a hypothetical `removePromiseListener`), and instead rely on automatic unsubscription when the underlying promise resolves or rejects. You can of course polyfill this missing behavior if you're in the habit of consistently waiting on infinitely non-settling promises, but I would definitely like to see TC39 standardize this [3].

[1] https://issues.chromium.org/issues/42213031#comment5

[2] https://github.com/nodejs/node/issues/17469#issuecomment-349...

[3] https://github.com/cefn/watchable/tree/main/packages/unpromi...

throwitaway1123··on Playground Wisdom: Threads Beat Async/Await
> In C#, awaiting for things which never complete is not that bad, the standard library has Task.WhenAny() method for that.

It's not that bad in JS either. JS has both Promise.any and Promise.race that can trivially set a timeout to prevent a function from waiting infinitely for a non-resolving promise. And as someone pointed out in the Lobsters thread, runtimes that rely on multi-threading for concurrency are also often prone to deadlocks and infinite loops [1].

  import { setTimeout } from 'node:timers/promises'
  
  const neverResolves = new Promise(() => {})
  
  await Promise.any([neverResolves, setTimeout(0)])
  await Promise.race([neverResolves, setTimeout(0)])
  
  console.trace()

[1] https://lobste.rs/s/hlz4kt/threads_beat_async_await#c_cf4wa1
throwitaway1123··on Edge Scripting: Build and run applications at the edge
Node is the only major outlier. Bun supports this convention as well: https://bun.sh/docs/api/http#export-default-syntax
throwitaway1123··on Has the market spoken? Time to pick up Golang and Rust?
> The front end bundler ecosystem has been a hot mess forever though.

The bundler situation wasn't ideal for a few years, but I've been really happy with esbuild lately. It's incredibly fast, has zero dependencies (besides the Golang sys module which is a de facto part of Go's extended standard library), and is much easier to configure than Webpack. I even heard DHH praise esbuild on The Changelog podcast recently, and he's a notable anti-build evangelist.

throwitaway1123··on How React, Vue, and Angular Work – Core Principles in One File
Yeah Vue 2 used object getters and setters, and there were a few tricky caveats. For example, setting an array item using an index wouldn't trigger the setters (e.g. `myArray[0] = ''`). You had to remember these edge cases and use `Vue.set` [1]. Proxies made everything so much simpler, but they can't really be polyfilled at all, because they require deep JS engine integration (e.g. to register a callback that gets notified whenever a property is added to an object).

[1] https://v2.vuejs.org/v2/guide/reactivity.html

throwitaway1123··on Using Rust in Non-Rust Servers to Improve Performance
There are flags you can set to tune memory usage (notably V8's --max-old-space-size for Node and the --smol flag for Bun). And of course in advanced scenarios you can avoid holding strong references to objects with weak maps, weak sets, and weak refs.
throwitaway1123··on OOP is not that bad
> To get a real comparison they should have written a third system using their new architecture and the old langauge, but that would of course be hard to justify outside of academic research.

Good point. You need a control group to make sure you're measuring the thing you think you're measuring.

throwitaway1123··on OOP is not that bad
I agree with the general sentiment of your comment and I think there are several factors at play here.

* The human brain is not capable of evaluating and re-evaluating every possible option amongst the plethora of technical choices developers are faced with. This forces us to develop certain coarse grained mental heuristics (prejudices and biases) to navigate technology, and even if these broad generalizations are roughly true initially, we tend not to re-evaluate them over time. This leads to stale biases (e.g. some library/language was missing an API 10 years ago, and someone formed an immutable opinion on it).

* These broad generalizations lack nuance. I watched a talk recently by Dan Abramov where he calls these heuristics (I'm paraphrasing) a form of information compression [1]. That compression is lossy — it doesn't preserve the original context in which the heuristic was formed.

* There's also some insecurity at play here too. Developers want to believe that they've chosen The One True Solution, and harshly invalidating the alternatives is one way to reenforce that fantasy.

* And of course, social media has exacerbated this problem by rewarding inflammatory hot takes. You won't get nearly as many views/upvotes/likes for a sober take that says "technology X is well suited for this narrow use case" as you will for a hot take that says "why technology X failed", or "why everyone hates technology X".

You might enjoy this link: https://blog.aurynn.com/2015/12/16-contempt-culture

[1] https://www.youtube.com/watch?v=17KCHwOwgms

throwitaway1123··on Why I'm skeptical of rewriting JavaScript tools in "faster" languages
This anecdote about the double equality operator might have originated from Eich's chat with Lex Fridman where he states (at about 5 minutes and 26 seconds) that during the original 10 day sprint JavaScript didn't support loose equality between numbers and strings: https://www.youtube.com/watch?v=S0ZWtsYyX8E&t=326s

The type system was weakened after the 10 day prototyping phase when he was pressured by user feedback to allow implicit conversions for comparisons between numbers and serialized values from a database. So it wasn't because he was rushing, it was because he caved to some early user feedback.

throwitaway1123··on Blocking code is a leaky abstraction
Yeah I should've said there's no Node API for making synchronous HTTP requests (unless you count executing a child process synchronously). Even the older http.request API used in Node prior to the introduction of fetch is async and accepted a callback. Browsers have all sorts of deprecated foot guns though (like the synchronous mode of XMLHttpRequest).
throwitaway1123··on Blocking code is a leaky abstraction
You can perform file I/O in JavaScript synchronously. The localStorage API in the browser is synchronous (and in Node via the --experimental-webstorage option), and of course requiring a CommonJS module is also synchronous (and there are many other sync filesystem APIs in Node as a sibling comment pointed out).

You just can't perform network I/O synchronously. Although a network attached file system allows for both network and file I/O technically, but that's a really pedantic point.

throwitaway1123··on Node.js, Pipes, and Disappearing Bytes
> See my comment here: https://news.ycombinator.com/item?id=41829905

Your comment isn't equivalent to the original code.

Your one liner is doing this: `process.stdout.write('')`

Jitl's example is doing this: `new Promise(resolve => stream.write("", resolve))`

He's passing in a promise resolver as the callback to stream.write (this is basically a `util.promisify` version of the writable.write chunk callback). If the data is written in order (which it should be), then I don't see how the `stream.write` promise could resolve before prior data is flushed. The documentation says: "The writable.write() method writes some data to the stream, and calls the supplied callback once the data has been fully handled". [1]

[1] https://nodejs.org/api/stream.html#writablewritechunk-encodi...

throwitaway1123··on Fun with Go Iterators
Array.prototype.reduce can basically do almost anything a for loop can do, since it gives you access to state from one iteration to the next. The only reason I didn't remove the flatMap in my original example and convert it all to reduce, is because there's no longer any method chaining which was the point of the original comparison between the Go and JS examples.

> dont ask me why but for(a of b) is slower than for(i=0;i<b.length;i++)

Probably because for of loops use the iterator protocol. So I'm assuming under the hood the JS engine is actually invoking the Symbol.iterator method (which is slower).

  #! /usr/bin/env node --experimental-strip-types
  
  function processIterator(input: number[]) {
    let sum = 0
    for (let i = input[Symbol.iterator](), r; (r = i.next()); ) {
      if (r.done) return sum
      if (r.value % 2 === 0) sum += r.value * 2
    }
  }
  
  function processfor(input: number[]) {
    let sum = 0
    for (let i = 0; i < input.length; i++) {
      const value = input[i]
      if (value % 2 === 0) sum += value * 2
    }
    return sum
  }
  
  
  const input = Array.from({ length: 1_000_000 }, (_, i) => i)
  
  console.time('normal for loop')
  console.log(processfor(input))
  console.timeEnd('normal for loop')
  
  console.time('iterator for loop')
  console.log(processIterator(input))
  console.timeEnd('iterator for loop')
throwitaway1123··on Fun with Go Iterators
Yeah the comment I was originally responding to included a for loop (in the Pastebin link). My point is that if you're set on going down the functional route you don't need separate map and filter steps, you can just use flatMap which is effectively a filter and map combined (returning an empty array filters out the current value, since an empty array gets flattened into nothing).

Of course, if you want the most performant solution an imperative for loop is faster (which is what I said in my last comment).

throwitaway1123··on Fun with Go Iterators
It's a little bit faster (on my machine at least) if you combine the filter and map into a flatMap (it's still not as performant as the imperative solution though).

  function process3(input) {
    return input
      .flatMap((n) => (n % 2 === 0 ? n * 2 : []))
      .reduce((a, b) => a + b, 0)
  }
throwitaway1123··on Google must open Android for third-party stores, rules Epic judge
Apps weren't what drove people to the original iPhone (the original iPhone didn't have an app store). Apple was essentially the first mainstream company to commit fully to the current smartphone design — a flat, rectangular, portrait aspect ratio brick, with a single slab of capacitive multi-touch glass. There were many other competing form factors at the time. Apple correctly deduced that touching your screen is the most intuitive way to interact with smaller devices, and they had a huge first mover advantage by committing to that paradigm early.
throwitaway1123··on Building a single-page app with Htmx
The htmx author maintains his own swapping library which has more stars (although I wouldn't judge a project solely based on Github stars): https://github.com/bigskysoftware/idiomorph?tab=readme-ov-fi...
throwitaway1123··on Rust needs a web framework
Multicore OCaml finally landed a few years ago: https://news.ycombinator.com/item?id=34013767
throwitaway1123··on Ask HN: What type of Auth are you using on your side projects?
> if your email provider attempts to scan urls to see where they actually go you end up giving them an auth token

One way you can handle this is to place the token in the hash portion of the url (which doesn't get sent to servers during an HTTP request), and then have JS on the frontend send the token to your backend manually. As long as the email provider isn't scanning links via a headless browser that executes JS, this should work.

I agree with your point about email abuse though (although you still have to prevent bots from abusing email based password resets).

← PreviousPage 2 of 11Next →