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

Can someone explain to me what the advantages of ESM actually are to me as a backend dev who uses import / export syntax in TS already?

Parent article mentions static analysis and synchronous loading on startup but that has never been an issue for me despite building some large and complex Node apps over the years.

I’ve looked into this in the past but all I could find are strong opinions without solid technical reasoning.



A big technical reason especially specific to using Typescript: not transpiling to CJS speeds up your Typescript builds.

It also opens up more options when Typescript is just "type stripped" rather than transpiled to an incompatible module syntax. You probably still want a full Typescript compile at CI time to get robust typechecking, and you'll have the Typescript LSP doing its thing in your IDE still, but you can use type-stripping tools like esbuild for very fast type stripping in some portions of your inner dev loop.

The static analysis that ESM supports is handy because it opens up technical benefits like Tree Shaking. Your apps might run on SSDs, you probably don't have a lot of reasons to bundle them, you likely aren't worried about on disk size, you might not be worried about bundle publish size to npm, and so you might not think Tree Shaking applies to you, but V8 under the hood of Node is still going to do Tree Shaking of memory and garbage collection for you with ESM in ways that it simply cannot with CJS. I've seen some real memory performance gains in Node apps just switching from CJS to ESM already, and V8's ESM optimizations only seem to get better as more ESM is deployed in the wild.

There are more, smaller technical benefits, but "type stripping not transpiling" and "in memory tree shaking" are strong ones that are easy to overlook.


This is good information - thank you. I would need to do some benchmarks to measure the impact - might make for a nice blog post in future! :)


IMO the game changer in your scenario is when type annotations are added as a standard JS feature:

https://github.com/tc39/proposal-type-annotations

At that point (and with ES modules) you'd be able to run your TS code directly without any transpilation necessary. I'd love to remove all the build process junk from my projects and have them run quicker.

I've written a few projects as plain ESM JS with JSDoc type annotations and it truly is a joy to bypass build steps. JSDoc type annotations do get annoying as a project grows to a certain size, though.


I would argue the benefit is just making everything much simpler. You never need to think about module systems and interoperability -- there is only one module system to deal with.

That proposal seems to have stalled, and I doubt it's going anywhere. Here is the thing: people want hard, strict compiler checks that can be enforced at all time, not Python-style type hints. If you want JavaScript but type checked, Typescript is the de-facto standard. Either do "checkJS" or convert the project to Typescript. In fact, the latter is much better in terms of expressiveness.


It only makes it simpler if you have a new project and can be ESM pure. If you are working with existing code or need non-ESM modules, everything is much more complicated.


When I see “stage 1” and “if they stick within a certain reasonably large subset of the language” the cynical old dev in me starts waving a YAGNI sign. :)


Honestly as you read through the proposal the immediate impression is "they're just describing TypeScript syntax", which is... fine, I think? But I do feel like it would be a considerably quicker process for Node to just add the ability to parse a TS file than it will be to go through this formal approval process.


The process is standardizing the ECMAScript language. It would make types (as comments) standard for all JS runtimes, not just Node. I don’t think the community at large wants more non-standard Node stuff, as that’s a frequent point of pain and complaint (see OP article as an example).


Fwiw the github page for that proposal isn't linking to what seems to be the most recent discussion *. I thought type-annotations-as-comments was a no-brainer, but it seems messy. The Sept 2023 discussion shows that it is complicated by the current variety of annotation schemes (TypeScript is not the only one), and the parsing subtleties, which gave rise to unresolved questions about what is the basic motivation. bummer.

* https://github.com/tc39/notes/blob/main/meetings/2023-09/sep...


> The aim of this proposal is to enable developers to run programs written in TypeScript, Flow, and other static typing supersets of JavaScript without any need for transpilation,

> if they stick within a certain reasonably large subset of the language. <-------- !!!!!!!!!!

js + type annotations !== typescript

The most recent discussion sums this up with, I think this comment:

> EAO: Okay. So the sense I get overall of this whole proposal, that it’s more of a – it’s a solution looking for a problem that it’s trying to solve a year ago when this got accepted for Stage 1, the problem statement that was in fact considered then was only effective we made up or made concrete during the meeting itself, and then – so what it looks like now is that since then, that problem statement has evolved to this current form of unifying or unforking JavaScript and somehow then presenting type annotations as a way of achieving this result. However, I’ve not been able to find any conversation anywhere or description of how in practice this unification is supposed to happen as a consequence of accepting type annotations.

No one had a good answer to that, because there isn't one.

Anyhow, tldr; this has basically nothing to do with any tangible reason to think ESM is good or useful in anything other than a massively broad, speculative 'maybe in the future' kind of way.

...it is most certainly not any kind of advantage or reason to use ESM for anyone, right now.


> js + type annotations !== typescript

It's very close though. The proposal is well written and covers the cases where existing Typescript code would not parse correctly/work as intended under the proposal. Most of those cases are pre-1.0 Typescript features still in the language for backwards compatibility but generally frowned upon and that you probably already have strict warnings and linter errors preventing you from using today. The big exception is a lot of TS codebases heavily use `enum` and I've still not seen enough suggestions to tighten the linter warning against it. (But there are at least some of us suggesting avoiding TS `enum` today.)


Being able to copy paste typescript snippets in the browser console is enough of a benefit in my opinion


> Can someone explain to me what the advantages of ESM actually are to me as a backend dev who uses import / export syntax in TS already?

1. The downstream users of your backend library can use ESM JS.

2. You can author (or output) isomorphic code that works in Node.js and other environments.

3. Static analysis tools can more reliably understand your dependency graph, e.g. find unnecessary code/modules/packages.

4. You can use a custom load API to do customized operations. E.g. import a YAML file as a JavaScript object.

If none of these apply to you individually, then you don't have a lot to be gained.


> 1. The downstream users of your backend library can use ESM JS.

ESM code can use CJS packages.

> 2. You can author (or output) isomorphic code that works in Node.js and other environments.

TS compilation makes this possible already - e.g. I have authored API SDK packages that can be used in FE and BE code.

> 3. Static analysis tools can more reliably understand your dependency graph, e.g. find unnecessary code/modules/packages.

An earlier sibling comment mentions this - this is potentially a good reason but I need to do some benchmarking to see the actual impact (would make for a good blog post one day) :)

> 4. You can use a custom load API to do customized operations. E.g. import a YAML file as a JavaScript object.

TIL but it seems like a niche feature that I would never need in BE world.


> ESM can use CJS packages

True. Everything is a default import, but it does work.

> I have authored API SDK packages that can be used in FE and BE code.

FE code can't use CJS. You need to have an extra step to convert to ESM (or AMD, webpack chucks, etc).

And the general consensus is that frontend JS has too much complexity.

---

CJS works pretty well for Node.js.

If it didn't, it wouldn't have been adopted.

The primary advantage is authors can write and distribute isomorphic code. (Again, without relying on heavy or kludgy module system conversations.)


There isn't that much difference on the backend if you use Node and Typescript. ESM is the standard for JavaScript and you'll probably run into situations where you can't use a new JavaScript feature with CommonJS as early as you can with ESM. There are disadvantages and advantages to how the modules load on Node, but ESM is generally improving faster than CommonJS.

If you're looking into other runtimes ESM used to be the way to go because of Deno, but these days you're likely either running Node or Bun and both work well with CommonJS and ESM.

If you do both frontend and backend work, or if your team has a lot of cross-over between the two sides of the ecosystem, then it'll likely be easier for you to use ESM on both ends as CommonJS isn't supported by browsers.

I think the primary reason CommonJS is still around is mostly because Typescript replaces it's import/export module system with something that's basically similar in syntax to the way ESM does it untill it gets transpiled into Javascript. If people actually had to work with CommonJS modules in 2024 then I think they'd likely go insane.

Not everyone will agree with me on this, but I don't think there is a reason to rewrite old projects into ESM unless you have a very good reason to do so. That being said, there isn't really a good reason to start new projects with CommonJS either... Unless you have a really good reason to do so.


A statically analyzable dependency tree of modules. No more "foo is undefined" when using cyclic require()




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

Search: