HN user

throwitaway1123

469 karma

Created this account 11/23. Might throw it away at some point.

Posts0
Comments307
View on HN
No posts found.

Yes, as you pointed out, not only can bundlers tree shake namespace imports, but they're literally used in the esbuild documentation to demonstrate the concept of tree shaking.

The issue you linked to is referring to the case in which you import a namespace object and then re-export it. Bundlers like webpack and rollup (which vite uses in production) can tree shake this pattern as well, but esbuild struggles with it.

If you're using esbuild instead of this:

  import * as someLibrary from "some-library"
  someLibrary.someFunction()
  export { someLibrary }
You can still do this:
  import * as someLibrary from "some-library"
  someLibrary.someFunction()
  export * from "some-library"
  export { default as someLibraryDefault } from "some-library"
  
Tree shaking works as expected for downstream packages using esbuild in the second case, which someone else in the linked issue pointed out: https://github.com/evanw/esbuild/issues/1420#issuecomment-96...

Somewhat related: you technically can access some type metadata in TypeScript at runtime using the `emitDecoratorMetadata` and `experimentalDecorators` tsconfig options, along with Microsoft's `reflect-metadata` polyfill package. There was a trend at one point where people were writing database ORMs using decorator metadata (e.g. Typegoose, TypeORM, and MikroORM).

This of course requires your build tool to actually understand the TS type system, which is why it's not supported in tools like esbuild and tsx (which uses esbuild under the hood).

Which browsers have you tested this in? I ran the feature detection script from the Chrome docs and neither Safari nor Firefox seem to support fetch upload streaming: https://developer.chrome.com/docs/capabilities/web-apis/fetc...

  const supportsRequestStreams = (() => {
    let duplexAccessed = false;
  
    const hasContentType = new Request('http://localhost', {
      body: new ReadableStream(),
      method: 'POST',
      get duplex() {
        duplexAccessed = true;
        return 'half';
      },
    }).headers.has('Content-Type');
  
    return duplexAccessed && !hasContentType;
  })();
Safari doesn't appear to support the duplex option (the duplex getter is never triggered), and Firefox can't even handle a stream being used as the body of a Request object, and ends up converting the body to a string, and then setting the content type header to 'text/plain'.

Enums and parameter properties can be enabled with the --experimental-transform-types CLI option.

Not being able to import TypeScript files without including the ts extension is definitely annoying. The rewriteRelativeImportExtensions tsconfig option added in TS 5.7 made it much more bearable though. When you enable that option not only does the TS compiler stop complaining when you specify the '.ts' extension in import statements (just like the allowImportingTsExtensions option has always allowed), but it also rewrites the paths if you compile the files, so that the build artifacts have the correct js extension: https://www.typescriptlang.org/docs/handbook/release-notes/t...

In Node 22.7 and above you can enable features like enums and parameter properties with the --experimental-transform-types CLI option (not to be confused with the old --experimental-strip-types option).

Deno 2.4 1 year ago

Node does have a permissions system, but it's opt in. Many runtimes/interpreters either have no sandbox at all, or they're opt in, which is why Deno's sandbox is an upgrade, even if it's not as hardened as iptables or Linux namespaces.

Deno 2.4 1 year ago

you can't just allow exact what you need in that category? You have to allow the entire category and then deny everything you don't want/need?

No, you can allow access to specific domains, IP addresses, filesystem paths, environment variables, etc, while denying everything else by default. You can for instance allow access to only a specific IP (e.g. `deno run --allow-net='127.0.0.1' main.ts`), while implicitly blocking every other IP.

What the commenter is complaining about is the fact that Deno doesn't check which IP address a domain name actually resolves to using DNS resolution. So if you explicitly deny '1.1.1.1', and the script you're running fetches from a domain with an A record pointing to '1.1.1.1', Deno will allow it.

In practice, I usually use allow lists rather than deny lists, because I very rarely have an exhaustive list on hand of every IP address or domain I'm expecting a rogue script to attempt to access.

JSX was also created with the intention of using traditional conditional/looping constructs, but that hasn't stopped Solid and Preact from repurposing it for fine grained reactivity. Preact's signal implementation has Solid-like Show/For components [1].

I won't speak for the author of the proposal, but given that Lit itself has special control flow mechanisms for conditionals and loops (the cache and repeat directives respectively [2][3]), I can't imagine the proposal being too opposed to these mechanisms.

[1] https://github.com/preactjs/signals/blob/eae850a9f3aa62e505a...

[2] https://lit.dev/docs/templates/conditionals/#caching-templat...

[3] https://lit.dev/docs/templates/lists/#the-repeat-directive

This is why both SolidJS and Vue Vapor both have helpers for rendering lists and conditional elements

Haven't these tagged template libraries coalesced around the html`<${SomeComponent}><//>` syntax for custom components (including control flow components like Solid's `For` component)? The readme for Ryan Carniato's Lit DOM Expressions library includes this example for instance [1]:

  const view = html`
    <table class="table table-hover table-striped test-data">
      <tbody>
        <${For} each=${() => state.data}
          >${row => html`
            <tr>
              <td class="col-md-1" textContent=${row.id} />
              <td class="col-md-4">
                <a onClick=${[select, row.id]}>${() => row.label}</a>
              </td>
              <td class="col-md-1">
                <a onClick=${[remove, row.id]}
                  ><span class="glyphicon glyphicon-remove"
                /></a>
              </td>
              <td class="col-md-6" />
            </tr>
          `}<//
        >
      </tbody>
    </table>
  `;
The author of the article mentions this very briefly, where he writes "For JSX-style references you would need to use binding syntax like <${MyComponent}>". The Preact author's htm tagged template library uses this convention as well [2].

[1] https://github.com/ryansolid/dom-expressions/tree/7fd9f86f1b...

[2] https://github.com/developit/htm

You clearly have an axe to grind, and I'm not particularly interested in proselytizing. If you don't find the Declarative Shadow DOM useful, that's fine!

The example I was responding to was using the Declarative Shadow DOM. My comment was intended to point out the simple fact that the imperative component definition the author was complaining about is superfluous, meaning you can safely remove that entire script from the example.

First off, what’s with the pointless JavaScript? There’s no need for imperative code here.

Exactly, I'm not sure why you've included the JS. The whole point of the Declarative Shadow DOM is to create shadow roots declaratively, rather than imperatively. To quote web.dev "This gives us the benefits of Shadow DOM's encapsulation and slot projection in static HTML. No JavaScript is needed to produce the entire tree, including the Shadow Root." [1]

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

You absolutely can. It's the primary purpose of the DSD (Declarative Shadow DOM), one of the many new specifications people complain incessantly about.

  <my-component>
    <template shadowrootmode="open">
      <style>
        ::slotted(*) {
          font-weight: bold;
          font-family: sans-serif;
        }
      </style>
      <slot></slot>
    </template>
    <p>content</p>
  </my-component>

you have to explicitly specify mode: open

The mode does not toggle the shadow DOM on and off. It just specifies whether or not the element's shadowRoot object is a public property. An open mode makes this possible: `document.querySelector('my-component').shadowRoot`.

You have to explicitly opt-in to the shadow DOM either imperatively by calling `attachShadow`, or declaratively in HTML by giving the element a template child element with a `shadowrootmode` attribute.

It's worth pointing out that Node has a built in globbing function: https://nodejs.org/docs/latest-v24.x/api/fs.html#fspromisesg...

Also there's arguably design. Should a 'glob' library actually read the file system and give you filenames or should it just tell you if a string matches a glob and leave the reset to you?

There's a function in Node's stdlib that does this as well (albeit it's marked as experimental): https://nodejs.org/docs/latest-v24.x/api/path.html#pathmatch...

which hinged on your definition of what the word "inevitable" means is the narrowest possible interpretation of my statement.

My argument does not hinge upon the definition of the word inevitable. You originally said "I mean, I can't think of a time a high profile project written in a lower level representation got ported to a higher level language."

I gave a relatively thorough accounting of why you've observed this, and why it doesn't indicate what you believe it to indicate here: https://news.ycombinator.com/item?id=43339297

Instead of addressing the substance of the argument you focused on this introductory sentence: "I'd like to address your larger point which seems to be that all greenfield projects are necessarily best suited to low level languages."

Regardless of how narrowly or widely you want me to interpret your stance, my point is that the data you're using to form your opinion (rewrites from higher to lower level languages) does not support any variation of your argument. You "can't think of a time a high profile project written in a lower level representation got ported to a higher level language" because developers tend to be more hesitant about reaching for lower level languages (due to the higher barrier to entry), and therefore are less likely to misuse them in the wrong problem domain.

I wrote 362 words on why language rewrites are a faulty indicator of language quality with multiple examples and anecdotes, and you hyper-fixated on the very first sentence of my comment, instead of addressing the substance of my claim. In what alternate universe is that a good faith argument? If you were truly arguing in good faith you'd restate your position in whichever way you'd like your argument represented, and then proceed to respond to something besides the first sentence. Regardless of how strongly or weakly you believe that "native representations win out", my argument about misusing language rewrite anecdata still stands, and it would have been far more productive to respond to that point.

If I said "If you drink and drive you will inevitably get into an accident" - would you argue against that statement?

If we were having a discussion about automobile safety and you wrote several hundred words about why a specific type of accident isn't indicative of a larger trend, I wouldn't respond by cherry picking the first sentence of your comment, and quoting Google definitions about a phone ringing.

That is not my intention. Perhaps you are reading absolutes and chasing after black and white statements.

The first comment I wrote in this thread was a response to the following quote: "Yet projects inevitably get to the stage where a more native representation wins out." Inevitable means impossible to evade. That's about as close to a black and white statement as possible. You're also completely ignoring the substance of my argument and focusing on the wording. My point is that language rewrites (like the TS rewrite that sparked this discussion) are a faulty indicator of scripting language quality.

I almost think you aren't reading my post at this point and are just arguing with a strawman you invented in your head. But I am assuming good faith on your part here, so once again I'll just repeat myself again and again: LLMs have already changed the barrier to entry for low-level languages and they will continue to do so.

And I've already said that I disagree with this assertion. I'll just quote myself in case you haven't read through all my comments: "I'm not an AI pessimist, but I'm also not an AI maximalist who is convinced that AI will completely eliminate the need for human code authoring and review, and as long as humans are required to write and review code, then those benefits [of scripting languages] still apply." I was under the impression that I didn't have to keep restating my position.

I don't believe that AI has eroded the barriers of entry to the point where the average Ruby or PHP developer will enjoy passing around memory allocators in Zig while writing API endpoints. Neither of us can be 100% certain about what the future holds for AI, but as someone else pointed out, making technical decisions in the present based on AI speculation is a gamble.

Rather than fixating on this single Prisma example, I'd like to address your larger point which seems to be that all greenfield projects are necessarily best suited to low level languages.

First of all, I would argue that software rewrites are a bad proxy metric for language quality in general. Language rewrites don't measure languages purely on a qualitative scale, but rather on a scale of how likely they are to be misused in the wrong problem domain.

Low level languages tend to have a higher barrier to entry, which as a result means they're less likely to be chosen on a whim during the first iteration of a project. This phenomenon is exhibited not just at the macroscopic level of language choice, but often times when determining which data structures and techniques to use within a specific language. I've very seldomly found myself accidentally reaching for a Uint8Array or a WeakRef in JS when a normal array or reference would suffice, and then having to rewrite my code, not because those solutions are superior, but because they're so much less ergonomic that I'm only likely to use them when I'm relatively certain they're required.

This results in obvious selection bias. If you were to survey JS developers and ask how often they've rewritten a normal reference in favor of a WeakRef vs the opposite migration, the results would be skewed because the cost of dereferencing WeakRefs is high enough that you're unlikely to use them hastily. The same is true to a certain extent in regards to language choice. Developers are less likely to spend time appeasing Rust's borrow checker when PHP/Ruby/JS would suffice, so if a scripting language is the best choice for the problem at hand, they're less likely to get it wrong during the first iteration and have to suffer through a massive rewrite (and then post about it on HN). I've seen plenty of examples of competent software developers saying they'd choose a scripting language in lieu of Go/Rust/Zig. Here's the founder of Hashicorp (who built his company on Go, and who's currently building a terminal in Zig), saying he'd choose PHP or Rails for a web server in 2025: https://www.youtube.com/watch?v=YQnz7L6x068&t=1821s

First, they are using WASM which itself is a a low-level representation.

WASM is used to generate the query plan, but query execution now happens entirely within TypeScript, whereas under the previous architecture both steps were handled by Rust. So in a very literal sense some of the Rust code is being rewritten in TypeScript.

Basically, if the majority of your application is already in JavaScript and expects primarily to interact with other code written in JavaScript, it usually doesn't make sense to serialize your data, pass it to another runtime for some processing, then pass the result back.

My point was simply to refute the assertion that once software is written in a low level language, it will never be converted to a higher level language, as if low level languages are necessarily the terminal state for all software, which is what your original comment seemed to be suggesting. This feels like a bit of a "No true Scotsman" argument: https://en.wikipedia.org/wiki/No_true_Scotsman

As for the "compilers are special" reasoning, I don't ascribe to it.

Compilers (and more specifically lexers and parsers) are special in the sense that they're incredibly well suited for languages with shared memory multithreading. Not every workload fits that profile.

The old cases I would choose the scripting language (familiarity, speed of adding new features, ability to hire a team quickly) seem to be eroding in the face of LLMs.

I'm not an AI pessimist, but I'm also not an AI maximalist who is convinced that AI will completely eliminate the need for human code authoring and review, and as long as humans are required to write and review code, then those benefits still apply. In fact, one of the stated reasons for the Prisma rewrite was "skillset barriers". "Contributing to the query engine requires a combination of Rust and TypeScript proficiency, reducing the opportunity for community involvement." [1]

[1] https://www.prisma.io/blog/from-rust-to-typescript-a-new-cha...

I mean, I can't think of a time a high profile project written in a lower level representation got ported to a higher level language.

Prisma is currently being rewritten from Rust to TypeScript: https://www.prisma.io/blog/rust-to-typescript-update-boostin...

Yet projects inevitably get to the stage where a more native representation wins out.

I would be careful about extrapolating the performance gains achieved by the Go TypeScript port to non-compiler use cases. A compiler is perhaps the worst use case for a language like JS, because it is both (as Anders Hejlsberg refers to it) an "embarassingly parallel task" (because each source file can be parsed independently), but also requires the results of the parsing step to be aggregated and shared across multiple threads (which requires shared memory multithreading of AST objects). Over half of the performance gains can be attributed to being able to spin up a separate goroutine to parse each source file. Anders explains it perfectly here: https://www.youtube.com/watch?v=ZlGza4oIleY&t=2027s

We might eventually get shared memory multithreading (beyond Array Buffers) in JS via the Structs proposal [1], but that remains to be seen.

[1] https://github.com/tc39/proposal-structs?tab=readme-ov-file

Preact is definitely a good choice if you're looking for something lightweight. React-dom was already relatively hefty, and seems to have gotten even larger in version 19. Upgrading the React TypeScript Vite starter template from 18 to 19 increases the bundle size from 144kB to 186kB on my machine [1][2]. They've also packaged it in a way that's hard to analyze with sites like bundlephobia.com and pkg-size.dev.

[1] https://github.com/facebook/react/issues/27824

[2] https://github.com/facebook/react/issues/29913

Those definitely help, but the proposed erasableSyntaxOnly flag would disallow all features that can't be erased. So it would prevent you from using features like parameter properties, enums, namespaces, and experimental decorators.

It would essentially help you produce TypeScript that's compatible with the --experimental-strip-types flag (and ts-blank-space), rather than the --experimental-transform-types flag, which is nice because (as someone else in this thread pointed out), Node 23 enables the --experimental-strip-types flag by default: https://nodejs.org/en/blog/release/v23.6.0#unflagging---expe...

Enums is going to make your TypeScript code not work in a future where TypeScript code can be run with Node.js

Apparently they're planning on adding a tsconfig option to disallow these Node-incompatible features as well [1].

Using this limited subset of TS also allows your code to compile with Bloomberg's ts-blank-space, which literally just replaces type declarations with whitespace [2].

[1] https://github.com/microsoft/TypeScript/issues/59601

[2] https://bloomberg.github.io/ts-blank-space/