Breaking backwards compatibility is always painful but there's not one actual criticism of ES Modules as a spec here other than its incompatibility with CommonJS
Breaking backwards compatibility is always painful but there's not one actual criticism of ES Modules as a spec here other than its incompatibility with CommonJS
What are the advantages of ESM that led to selecting that over CJS modules for standardization in the first place?
Not convincingly.
Firstly, static imports are markedly different to top-level require due to module scoping: e.g. tree-shaking isn't stable with most "statically-analysed" top-level requires due to potential side-effects.
Secondly, handwaving dynamic imports as the ES Modules equivalent to non-top-level require doesn't pass the smell test. Dynamic imports are a deliberately separate syntax and mechanism to static imports, whereas require isn't differentiated. They are also, notably, async.
var x = strange_thing()
but suddenly can not when “x” is renamed to “exports”? module.exports = {foo: 3};
setImmediate(() => module.exports[Math.random() > 0.5 ? 'foo' : 'bar'] = 5);
This is impossible for ES modules, because exports are static and known at parse time.Few widely used packages do something like:
enhanceWithAdditionalProperties(module.exports, mixin); export var foo = 3
setImmediate(() => {foo = Math.random()})
Under ESM, exported values are still not known at parse time, and may be changed by the library (but not created nor deleted). And given that exported may be changed, what prevents library makers from doing: export default var exports = { }
enhance(exports, mixin)
and you are left with heuristics again?I see these subtle differences, but fail to see how they are a solution to the problem of “doing strange things to exports”.
That might sound irrelevant on the face of it, but it has
very real consequences. For example, the following pattern
is simply not possible with ESM:
const someInitializedModule = require("module-name")
(someOptions);
Or how about this one? Also no longer possible:
const app = express();
// ...
app.use("/users", require("./routers/users"));
Configurable modules and lazily loaded imports are both missing from the ES Modules spec.What do you mean?
import someModule from "module-name"
const someInitializedModule = someModule(someOptions)
A bit longer, but meh… const app = express();
app.use("/users", (await import("./routers/users")).default);
top-level await is a thing nowActually, the first example could be rewritten as
const someInitializedModule = (await import("module-name")).default(someOptions);
That «simply not possible» statement is simply not true import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjsOrJson = require('./somewhat/module/pathway');The rest of the text can mostly be summarized as https://xkcd.com/927/
People, especially a few years ago, were trying to get clever with the require calls, were fiddling around with require cache and while with ESM we no longer can “easily” do stuff like dynamic reloads, I genuinely feel it’s for the better.
I strongly agree with privatenumber’s point that import() syntax is the true first class citizen here.
This.
Also, the bulk of the rant is focused on how the author struggles with configuring his pick of JavaScript bundlers and transpilers, and proceeds to come up with excuses to justify not migrating away from CommonJS .
This article was a waste of a perfectly good click.