Live data from Hacker News

Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

redfin.engineering

81–90 of 152 posts

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#81
post #57
post #37

I’ll conclude with three guidelines for library authors to follow: Provide a CJS version of your library Provide a thin ESM wrapper for your CJS Add an exports map to your package.json This seems like the same fallacy of python’s 2to3, and why they should have written 3to2 instead. If ESM is the new way, library authors should instead have a CJS shim to prevent fossilization of cruft.

> should instead have a CJS shim OP wrote, note that it’s easy to write an ESM wrapper for CJS libraries, but it’s not possible to write a CJS wrapper for ESM libraries.

It's not possible to write a nice automated transparent one for arbitrary libraries because of top level await, but it's totally possible for the 95%.

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#82
post #10

JavaScript just gives to much options. I keep having problems with libraries because ES6 allows import x from "library"; import {x} from "library"; import * as x from "library"; import {x as y} from "library"; Just to compare Java uses this: import java.lang.Double; import java.lang.*; No renaming, not a gazillion options during in- and export, but one statement. If you want to rename something, assign it to a variab…

So what happens when you need to deal with java.time and Joda Time in the same class (e.g. because Joda was part of your public API but you're migrating to Java time). Fully qualified names everywhere, when it would be nicer to have a construct like:

import org.joda.time.Instant as JodaInstant;

The rest of the weirdness is to make it look like JS destructuring

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#84
post #48

Earlier quoted context omitted.

Sure, but maybe upgrading the existing Python 2 code wasn't the problem: the vast majority of the Python 2 code alive when Python 3 was released was probably scrapped by the time Python 2 was EOL'd . Over a decade is a very long time for a codebase to live. (Again, there are exceptions, but the vast majority of websites or data processing scripts or whatnot are significantly re-written or decommissioned in – pulling…

> Porting Python 2 code to Python 3 was never the problem. You're, frankly, high as a kite. As the author of one such port on a large codebase, porting Python 2 code to Python 3 was absolutely a problem. The issue with 2to3 is that it assumed you'd do the conversion as a one-shot and straight flip over with entirely separate codebases (or dropping Python 2 entirely) from there on, which is completely insane. What the…

Isn't "migrating progressively through cross-version codebases" exactly what a 3to2 compiler would allow you to do? You'd write new code (and incrementally port old code) to Python 3, and then when you build the project you'd compile the Python 3 code down to Python 2 before building the entire project as Python 2, including all dependencies.

So for over a decade, you'd be writing Python 3 code but building a Python 2 project. After a decade, one would hopefully have managed to port everything over to Python 3, and can then drop the "compile to Python 2" step.

A 3to2 tool would solve exactly the problems you're bringing up, with no intoxication needed!

----

I don't see why 3to2 would not have worked. It's a compiler. We have written compilers before. We can write compilers. Compilers work. Both languages are Turing complete and about the same in expressiveness, so clearly compiling one to the other is something we can do.

What we cannot (reasonably) do is produce nice, human-looking target code from a compiler. This is what 2to3 attempted to do, and what a 3to2 would never need to do.

Who cares if the Python 2 code it produced looked a little wonky, as long as it was correct and roughly recogniseable as being built from the original Python 3 code? The Python 3 code is the one humans write and edit. The Python 2 code is just compiler output to make it compatible with all of the Python 2 ecosystem until one is ready to go fully Python 3.

----

This is also not something completely foreign and unheard of. This is exactly how I've been porting legacy JavaScript applications to a more modern ECMAScript approach: write new code in the modern way, compile it down to legacy code compatible with the existing legacy code, and then run that.

Slowly but surely, more and more things are being written in the modern fashion, and eventually, when everything has caught up, the compatibility conversion to the legacy code can be dropped.

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#85
post #79

Earlier quoted context omitted.

I think people (like the author of this article) should explain the syntax of destructuring assignment {x} briefly to make it make more sense for the unfamiliar. [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

This looks similar to detructuring assignment, but it isn't: - no recursive destructuring - `import {y} from 'x'` is not the same as `import x from 'x'; const {y} = x`

[deleted]

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#86
post #37

I’ll conclude with three guidelines for library authors to follow: Provide a CJS version of your library Provide a thin ESM wrapper for your CJS Add an exports map to your package.json This seems like the same fallacy of python’s 2to3, and why they should have written 3to2 instead. If ESM is the new way, library authors should instead have a CJS shim to prevent fossilization of cruft.

PSF wanted people to migrate to python3 for reasons including not wanting to support multiple languages. Why would they write some tool to convert new python3 code back to python2 that would ultimately work against their agenda? It doesn't make sense to me.

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#87
post #79

Earlier quoted context omitted.

I think people (like the author of this article) should explain the syntax of destructuring assignment {x} briefly to make it make more sense for the unfamiliar. [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

This looks similar to detructuring assignment, but it isn't: - no recursive destructuring - `import {y} from 'x'` is not the same as `import x from 'x'; const {y} = x`

Thanks for pointing it out, didn't know that.

But I think your second point is more to do with the fact `import` by default imports `default` instead of the whole thing.

If you use import * as x from 'x'; then `const {y} = x;` works.

I personally will consider it is `import from` being different from a typical object instead of {x} here being different from typical destructuring assignment (by that I mean they still follow the same pattern; not necessarily say they're the same thing.)

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#88
post #76

Earlier quoted context omitted.

The reason why async/await is a good fit for Javascript is that it neatly wraps up asynchronous callback hell and Promises, which were already ubiquitous in Javascript code, because the web runtime enforces non-blocking IO.

It may be the reason, but not the only option. function cpsfoo(cb) {...} function foo() { var thread = this_thread() cpsfoo(x => thread.resume(x)) return thread.yield() } Error and immediate cb invokation handling omitted for clarity. Similar wrapper or wrapper-generator (uncps(f), unpromise(f)) may be done for other primitives.

well coroutines are a way better option and working way better than promises, unfortunatly promises were introduced since it's easier to convert a callback function to a promise based one

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#89
post #76

Earlier quoted context omitted.

The reason why async/await is a good fit for Javascript is that it neatly wraps up asynchronous callback hell and Promises, which were already ubiquitous in Javascript code, because the web runtime enforces non-blocking IO.

It may be the reason, but not the only option. function cpsfoo(cb) {...} function foo() { var thread = this_thread() cpsfoo(x => thread.resume(x)) return thread.yield() } Error and immediate cb invokation handling omitted for clarity. Similar wrapper or wrapper-generator (uncps(f), unpromise(f)) may be done for other primitives.

Sure, it's not the only option, just the better option for Javascript. Your example isn't representative of Javascript.

This is an example for something that might come up in Javascript, using async/await:

    const a = await fetchA();
    const b = await fetchB(a);
    const stuff = await fetchStuff(a, b);
    const transformedStuff = await Promise.all(stuff.map(someAsyncTransform));
Error handling is just try/catch.

Note that you generally have no control over most of these function being asynchronous. Threads or coroutines do nothing to help you write straightforward code when all your libraries are based on fine-grained callbacks/promises. Writing out that chain of dependencies in CPS would be a nightmare.

Re: Node Modules at War: Why CommonJS and ES Modules Can’t Get Along

#90
post #58

Earlier quoted context omitted.

The last time I asked here why async-await is even a thing, when you can just introduce light/green threads, coroutines, you name it, everyone jumped that it is even better when you mark awaiting points explicitly. Now that it goes from a blog post of some popular language designer, seems no one gonna disagree. >async computation mature over the years A good thing, really, but would be much better if the experience f…

The reason why async/await is a good fit for Javascript is that it neatly wraps up asynchronous callback hell and Promises, which were already ubiquitous in Javascript code, because the web runtime enforces non-blocking IO.

Never should have had to worry about that stuff to begin with, unless you were actively using it. I’ve read and written way more code that had to wrestle async back to sync than code that’s actually been able to let async happen at the local scope. Of course if you’re working in a framework (Express, say) one might expect that it would run your entire mostly-sync pile of code async, so it can hop over and process another request. And that’s fine. That’s how it should have worked. Most things should execute in an async context so other things can happen while they’re waiting on IO or whatever, but within them async behavior should have been what required a keyword or a bunch of hideous nesting or whatever.
Post reply on HN