[HN Gopher] ES modules are terrible
       ___________________________________________________________________
        
       ES modules are terrible
        
       Author : theprotocol
       Score  : 150 points
       Date   : 2021-11-07 09:03 UTC (13 hours ago)
        
 (HTM) web link (gist.github.com)
 (TXT) w3m dump (gist.github.com)
        
       | Chyzwar wrote:
       | Problem is bothed node.js implementation that leaves most of
       | existing applications without migration path. Even today it is
       | not possible to create full ESM application front or backend. It
       | is worse than python 2 to 3.
        
         | throw_m239339 wrote:
         | I would argue that Node way of doing things isn't in the
         | ecmascript spec. The problem isn't ES modules, it's node.js.
         | One could answer "well node.js existed prior es module
         | specification". Irrelevant. DENO doesn't have this problem.
        
         | theprotocol wrote:
         | Absolutely this. It's not necessarily communicated that well in
         | the article, but this is the main reason people are frustrated.
        
         | eyelidlessness wrote:
         | Node ESM support has gotten a lot better through versions
         | 12-17. The biggest problems for workflows that currently work
         | "well" for CJS are:
         | 
         | 1. --experimental-loader is more complex and less stable than
         | --require. But it's also a lot more robust.
         | 
         | 2. There's no equivalent to the require cache, which makes
         | mocking and long running processes like watch mode challenging.
         | This is partly a benefit, as it discourages cache busting
         | patterns like those used in eg Jest which create awful memory
         | leaks.
        
       | incrudible wrote:
       | _" And then people go "but you can use ESM in browsers without a
       | build step!", apparently not realizing that that is an utterly
       | useless feature because loading a full dependency tree over the
       | network would be unreasonably and unavoidably slow - you'd need
       | as many roundtrips as there are levels of depth in your
       | dependency tree - and so you need some kind of build step anyway,
       | eliminating this entire supposed benefit._
       | 
       | That build step is _ideally_ performed by something like rollup
       | or esbuild, which means I use import /export anyway. If you still
       | use Babel, I feel bad for you son, I've got 99 problems but Babel
       | ain't one. I don't care if the old stuff is not supported, simply
       | deleting 98% of the code in the JS ecosystem would be a step
       | forward. Perhaps that's a minority view, but none of these
       | arguments fly with me.
        
       | emersion wrote:
       | I'm using ES modules for a webapp I maintain, and it's just nice
       | to be able to run it without any build step. Just fire off a
       | local static HTTP server and you're good to go. There's an
       | optional production build step which can be used if desirable.
        
         | incrudible wrote:
         | I would admit that this gets pretty slow even with a modest
         | amount of files. If you use rollup/esbuild, you can have a very
         | fast build step that may amortize over the increased page load
         | times.
        
       | xg15 wrote:
       | > _And then people go "well you can statically analyze it
       | better!", apparently not realizing that ESM doesn't actually
       | change any of the JS semantics other than the import/export
       | syntax, and that the import/export statements are equally
       | analyzable as top-level require/module.exports.
       | 
       | ...
       | 
       | "But in CommonJS you can use those elsewhere too, and that breaks
       | static analyzers!", I hear you say. Well, yes, absolutely. But
       | that is inherent in dynamic imports, which by the way, ESM also
       | supports with its dynamic import() syntax. So it doesn't solve
       | that either! Any static analyzer still needs to deal with the
       | case of dynamic imports somehow - it's just rearranging deck
       | chairs on the Titanic._
       | 
       | I think while OP's right in theory, there is still a lot of
       | difference between the two: ESM has dedicated syntax for static
       | loading of modules and that syntax is strongly communicated to be
       | the standard solution to use if you want to load a module. Yes,
       | dynamic imports exist but they are sort of an exotic feature that
       | you would only use in special situations.
       | 
       | In contrast, CommonJS imports are dynamic by default and only
       | happen to be statically analysable if you remember to write all
       | your imports at the beginning of the module. That's a convention
       | that's enforced through nothing and is not part of the language
       | or even of CommonJS.
       | 
       | As an exercise, try to write a static analyser that simply
       | ignores dynamic imports and just outputs a dependency graph of
       | your static imports - and compare how well this works with
       | CommonJS vs ESM.
        
         | jokethrowaway wrote:
         | You can detect from the AST whether a require can be statically
         | resolved or depends on non static variables.
         | 
         | It may not be as simple, but I venture it's easier to implement
         | than having all the software ever written needing to be
         | migrated
        
           | xg15 wrote:
           | You can detect simple cases like require("some string
           | literal") on top-level. Things become harder if you have
           | require() in init functions or wrapped in (function(){})() or
           | if the module name is defined in a string constant elsewhere.
           | 
           | I can't say how common those forms are, but my point is that
           | there is nothing immediately discouraging a programmer from
           | using them as all of it is "just javascript". So even if you
           | just restrict yourself to static imports, its hard to be sure
           | you caught all of them without running the program.
        
             | wruza wrote:
             | Wrong, things do not become harder when require() is nested
             | somewhere. It may not get called in node, but any bundler
             | looks at it as required-to-be-bundled-anyway. The only case
             | when it's hard is when require() accepts a non-literal, and
             | that's symmetric to import(). No extra cases.
             | 
             | The programmer is discouraged to use dynamic
             | requires/imports by the common sense, because that makes
             | their app/lib incompatible with most of the cross-end
             | usage. But then we have server-side only packages like pg
             | or express where it doesn't matter, because browsers
             | provide no runtime (tcp/listen) for them to function and
             | will never do.
        
         | agumonkey wrote:
         | now I see files as syntactic module level lambdas where args
         | comes first and the body is the rest :)
        
         | crooked-v wrote:
         | > that you would only use in special situations
         | 
         | There are web frameworks pushing this pretty hard for basic
         | stuff (for example: loading React components with dynamic
         | import) to build page-content-streaming functionality around
         | it.
        
         | tolmasky wrote:
         | This isn't a real concern, and yes I've written a static
         | analyzer for requires (as have many build tools). The fact of
         | the matter is no one is trying to trick the analyzer by passing
         | variables to require, or even more mischievously trying to
         | rename require or something (there aren't a lot of "(a =>
         | a)(require)(path + "/x.js")" out there).
         | 
         | In practice, it is used like a static feature, and when it
         | isn't, it's for a good reason that import doesn't solve and
         | just expects you find a harder solution to. For example, if you
         | want to load a platform specific file depending on your host
         | environment. With import, the entire function now has to become
         | needlessly async just because _any use of the import expression
         | needs to be async_. Another good example is modules that put
         | their requires _inside the calling function_ to avoid
         | needlessly increasing the startup time of an app for a feature
         | it may not use. This way, only if you call that specific
         | function will you have to suffer the require /parse/runtime hit
         | for it. Notice all these cases are in node, so they wouldn't
         | result in some complicated decision as to whether to include
         | these "dynamic requires" into the main bundle or not -- it just
         | doesn't come up in bundling since they are use cases that are
         | specific to node. But because of ESM, node now needs to make a
         | bunch of synchronous functions be asynchronous to accommodate a
         | set of restrictions designed with the browser in mind. And
         | again, at the end of the day import _does_ still have an
         | expression form so you haven't actually resolved the static
         | analysis problem, just made dynamic imports more annoying in
         | non-browser contexts.
        
           | spankalee wrote:
           | > to accommodate a set of restrictions designed with the
           | browser in mind
           | 
           | That's the whole point, and a very good thing.
        
             | tolmasky wrote:
             | That was not the whole point, the whole point was to have a
             | feature that could work in a variety of different
             | environments. That's why this _language_ feature is in the
             | ECMAScript spec and not in the W3C or whatwg, unlike
             | something like "fetch" which _is_ defined by the whatwg and
             | thus has every right to not take other environments into
             | consideration. There is a tremendous amount of subtlety
             | that results from this fact, like how the spec can thus
             | only define a small portion of this feature (syntax and
             | basic semantics), but ultimately needing to leave
             | everything related to fetching, resolving, and executing
             | the code up to the environment (in HTML's case, the whatwg
             | HTML spec). This really complicates things and creates an
             | unfortunate mismatch in expectations, where most _users_
             | who have only a passing understanding of this feature and
             | have been sold on the promise of something that will
             | finally "just work" everywhere discover that this isn't the
             | case at all. There's a reason why despite being introduced
             | in ECMAScript over 5 years ago it still barely has support
             | in node (and not great support in browsers either btw, but
             | certainly better)-- it's because the reality is that this
             | feature is supremely complicated to implement (despite
             | providing very little tangible benefit), especially in the
             | context of the unrealistic expectations users have
             | developed for it as it continues to be pitched as being the
             | magic tool that will make your code work everywhere without
             | a build tool.
        
               | spankalee wrote:
               | It really is the whole point. TC39 was not going to
               | define a feature that didn't work in browsers, full stop.
               | 
               | That Node has to take browser into consideration is a
               | very good thing for universal JavaScript. We can now
               | write code that works in browsers, Node and Deno and
               | that's a great thing.
               | 
               | The support in browsers is excellent btw. All current
               | browsers support standard JS modules now. Chrome is
               | leading the way with import maps, import assertions, and
               | JSON and CSS modules, but the other browsers will get
               | there and CJS had nothing comparable to those anyway.
        
               | tolmasky wrote:
               | It is absolutely not excellent, unless you restrict
               | yourself solely to whether there is a checkmark next to
               | the browser name in mdn. It is very buggy, very difficult
               | to debug, and as I mentioned in another comment, missing
               | serious features (like no subresource integrity which,
               | means we're encouraging people to use a much less secure
               | system of importing scripts in many cases!)
               | 
               | > It really is the whole point. TC39 was not going to
               | define a feature that didn't work in browsers, full stop.
               | 
               | No one is saying they shouldn't have considered the
               | browser! We're saying they should have _also_ considered
               | other major environments, like node! That's the way to
               | design a language feature and absolutely what they
               | _wanted_ to do. There were a number of reasons it was
               | rushed out the door, but they're pretty upfront about the
               | fact that they would do things differently now and
               | basically no other feature would be allowed into the spec
               | in the state ESM made its way in then. I am currently a
               | TC39 delegate and can assure you that it's OK to admit
               | when things aren't great so that we can learn from it.
               | It's how we've gotten JS to such a better state than
               | where we were 20 years ago, not by bending over backwards
               | to defend the with() statement.
        
               | spankalee wrote:
               | I've been using almost exclusively standard modules for
               | years and they work quite well. Old crashers and cache
               | problems I was aware of have been fixed for many years
               | now.
               | 
               | I don't know what debugging problems there are that
               | wouldn't exist in CJS. At least with native modules you
               | can see individual requests in the network panel while
               | you're working and individual files in the sources panel
               | while debugging. That alone is a huge increase in
               | debuggability to me.
               | 
               | SRI really should be done out of band. Inline SRI would
               | require far to frequent cache invalidation and isn't
               | compatible with package manager workflows where you don't
               | know the exact version of a file you'll depend on. A tool
               | rather should build up an SRI manifest similar to a
               | package lock. This has been discussed several times in
               | module and import map threads.
               | 
               | And I don't think JS modules were actually rushed. They
               | were languishing for _years_ with an overly complex
               | loader API and cut down to an MVP with the core agreed
               | upon semantics and that 's what finally got everyone to
               | ship. They're really fine, and with import maps, CSS
               | modules, and eventually web bundles, will be far, far
               | superior to any previous alternative. They already are.
               | 
               | The fact that Node has some JS module / CJS interop
               | issues (and really only when you try to use JS modules
               | from CJS, the other way is fine) is _Node 's_ problem,
               | not TC39's. There's really nothing that TC39 could have
               | done here because synchronous require() is the
               | fundamental problem. It was a bad design from the start
               | and we shouldn't burden ourselves with that bad decision
               | forever. The sooner CJS goes away the better.
        
               | tolmasky wrote:
               | > SRI really should be done out of band. Inline SRI would
               | require far to frequent cache invalidation and isn't
               | compatible with package manager workflows where you don't
               | know the exact version of a file you'll depend on. A tool
               | rather should build up an SRI manifest similar to a
               | package lock. This has been discussed several times in
               | module and import map threads.
               | 
               | If you are introducing a tool, then you should be
               | bundling your code, not creating more metadata files to
               | load! Bundled code has repeatedly proven to load faster
               | than ESM code using whatever HTTP 3 prefetch mumbo jumbo
               | you throw at it, that no one actually uses in practice
               | anyways. If you have a tool chain, then the answer is
               | easy: don't delay fetches and create more HTTP requests
               | and roundtrips! As I mentioned in another comment, the
               | sheer ridiculousness of this is demonstrated in the <link
               | rel="modulepreload"> feature [1], where I kid you not the
               | actual recommendation for having performant dependencies
               | is to litter your HTML file with a link tag for ever JS
               | file that's imported. We're right back to where we
               | started with a top level script tag for every script!
               | Argh! But I know, now the recommendation is "oh no silly,
               | your build tool should just create your 100 link tags".
               | Again, why? If you're using a build tool a bundled file
               | is way faster than waiting for link tags to get parsed to
               | issue a bunch prefetches and on and on. It's so
               | frustrating to discuss ESM because the goal posts kerp
               | bouncing between this being an "easy to use feature that
               | removes the need for build tools" only to have every
               | issue with it hand-waived away as trivially solvable by a
               | build tool that ultimately creates a worse end-user load
               | experience than what we already have.
               | 
               | > The fact that Node has some JS module / CJS interop
               | issues (and really only when you try to use JS modules
               | from CJS, the other way is fine) is Node's problem, not
               | TC39's. There's really nothing that TC39 could have done
               | here because synchronous require() is the fundamental
               | problem.
               | 
               | I think part of the disagreement stems from the fact that
               | you believe my position is that we should have "done
               | nothing" or that the only options were "the system we
               | shipped" or "just do it the node way" or something.
               | That's not the case at all. I am all for _a standard
               | system_ and I recognize the problems with require(). It
               | is simply the case that a design that took into account
               | the requirements of node could very well have served both
               | systems. These aren't the only two possible require
               | systems imaginable, but an API that exclusively looks at
               | only one set of constraints, despite billing itself as a
               | general purpose solution, will not generate the best end
               | result. This is shown by the several bandaids that had to
               | be added after the fact to import and could have been
               | avoided if considered beforehand.
               | 
               | 1. https://developers.google.com/web/updates/2017/12/modu
               | leprel...
        
               | spankalee wrote:
               | Your complaints seem to have far less to do with JS
               | modules and much more to do with the lack of native
               | bundling in the platform.
               | 
               | From my point of view, the goal posts that keep moving
               | are the ones put up by anti-JS-modules folk because they
               | keep comparing unbundled JS modules vs bundled CJS.
               | Unbundled CJS would perform far worse than standard JS
               | modules by all these measures, but somehow gets a pass
               | because it _has_ to be bundled.
               | 
               | What module feature could _possibly_ have solved the
               | waterfall problem? The only options are manifests and
               | bundling. IE, you can 't magically tell a browser what it
               | should load without telling it what it should load.
               | modulepreload is essentially a manifest and Web Bundles
               | are bundles, so there are solutions covering the space.
               | Browsers should get behind Web Bundles, asap, since that
               | would also solve many problems for caching, CSS and other
               | assets.
               | 
               | The problem I have with this debate is that JS modules
               | get criticized for being _able_ to work without bundling
               | at all, when that 's purely a positive and they still can
               | be bundled for performance.
               | 
               | If you don't like the unbundled workflow, don't use it.
               | It's still useful to have a standardized syntax and
               | semantics, and very useful for those of us who do want to
               | use them unbundled: for simple cases, dev environments,
               | or combined with features like prefetch/preload.
        
               | tolmasky wrote:
               | > From my point of view, the goal posts that keep moving
               | are the ones put up by anti-JS-modules folk because they
               | keep comparing unbundled JS modules vs bundled CJS.
               | Unbundled CJS would perform far worse than standard JS
               | modules by all these measures, but somehow gets a pass
               | because it has to be bundled.
               | 
               | A feature that by default encourages use that is both
               | slower and less secure on the web is a bad feature, full
               | stop. The SRI problem is not even solved yet. Shipping
               | import statements in production leads to slower websites.
               | This is exacerbated by the fact that the syntax _looks_
               | synchronous but _behaves_ asynchronously. As someone who
               | clearly cares much more about the browser environment
               | than the node environment, these problems should resonate
               | with you, regardless of the situation with node. import
               | isn't good even if you only consider the web.
               | 
               | > What module feature could possibly have solved the
               | waterfall problem?
               | 
               | I will give one example of an alternative approach that
               | could have been taken: start with the expression form of
               | import(), ship it alongside top-level await, and hold off
               | on the statement form until after we could see how this
               | was used (you could even restrict it to only taking
               | string literals if you want to begin with, doesn't make a
               | super difference for this argument, but I can see
               | arguments for that). Here are the benefits:
               | 
               | 1. You get everything you get with normal import, you
               | just type await import() and use normal declarations with
               | destructuring. There's no ergonomic difference except for
               | a couple extra characters, and it is less syntax to learn
               | since you don't need to learn the _almost_ , but not
               | quite, identical importing declaration destructuring.
               | 
               | 2. There's no weird bifurcation of "load semantics" left
               | as an exercise to the implementer, it's well defined
               | under Promise semantics and gives us time to determine if
               | we need something fancier.
               | 
               | 3. This would have punted a lot of meta issues until
               | later, including "what happens if a module throws an
               | error during load?" And "what happens with recursive
               | imports?" All of these questions are less critical in the
               | expression form where the user has recourse (they can
               | wrap it in try/catch! There can be a user accessible
               | cache, etc.) However, with a top level black box
               | statement these become must fix blockers because you
               | can't just say "oh the user has many good options", you
               | have to determine some one-size-fits-all complicated
               | behavior.
               | 
               | 4. The fundamental asynchronous nature of import() is not
               | hidden from you in a way that makes you feel like you're
               | doing something fast, and is the _actual_ issue I have
               | with "the bundling debate". The await makes it clear that
               | this is a "blocking" (to code after it) asynchronous
               | operation that you probably shouldn't ship in production,
               | as opposed to the situation now where not only do people
               | not understand this, but we continue to add more features
               | that perpetuate this myth (like module preload link tags
               | that are still slower than bundling but probably require
               | you to use a build tool so what's the point?).
               | 
               | 5. We would have had REPL support on day 1! Both in the
               | browser console and in node. This would have been so
               | helpful for debugging.
               | 
               | All in all, this would have been a great incremental
               | approach that solved the immediate problem of needing
               | something better than evaling the text result of an
               | XMLHttpRequest. It gives you all the functionality of the
               | import statement without the facade of a synchronous
               | feature. It would have prioritized top-level await, made
               | features we're thinking about now much easier to
               | introduce too (like inline modules), and would have
               | almost certainly already been incorporated in node since
               | there would have been no years of going back and forth
               | since you can't tell what's a module without parsing the
               | whole file first problem which is what lead to the whole
               | .mjs mess.
               | 
               | This probably would have had to wait until after ES6
               | shipped since it fundamentally relies on async/await, but
               | that's actually a huge part of the point: we wouldn't
               | have shipped a fundamentally async feature prior to
               | getting our async story straight in the language. A post-
               | async JS mindset would have lead to many different
               | decisions. Despite this delay though, there's a good
               | chance it would have been fully adopted everywhere much
               | sooner, and would have been much easier to transpile,
               | since it's "just a function" with syntax restrictions.
               | 
               | Just because something took a long time doesn't mean it
               | wasn't rushed.
        
         | brundolf wrote:
         | > Yes, dynamic imports exist but they are sort of an exotic
         | feature that you would only use in special situations.
         | 
         | They're also decidedly inconvenient to use in most situations,
         | because they return a Promise (unlike require()), which means
         | anything that depends on them has to itself be deferred. This
         | further discourages using them unless you really need to.
        
         | wruza wrote:
         | How browserify, webpack transformers and others are able to
         | parse require()-s in the middle of a source file, but static
         | analysers are not?
         | 
         | These subtly erroneous arguments are the essence of this push.
         | Look, we are maintaining X, Y and Z, and they're unable to do
         | that R, so it's bad. No, it's you making them unable to do that
         | consciously.
        
           | nosianu wrote:
           | 1) importing module
           | 
           | "require()" can take a non-static string. A variable or a
           | string calculated at runtime.
           | 
           | You can only statically check if that particular feature is
           | not used, but there is no checking the entirety of what
           | require() can be used for/with.
           | 
           | require() is more like dynamic imports in Es modules that are
           | awaited and not like the static ES modules.
           | 
           | 2) exporting module
           | 
           | The other issue is on the exporting module's side: You can do
           | strange things with the "exports" object. ES module exporting
           | is more strict to make it guaranteed statically analyzable.
        
             | wruza wrote:
             | _Es modules that are awaited and not like the static ES
             | modules._
             | 
             | But this is a non-argument. Awaited or not, you can't
             | statically check them either. Developers aren't idiots and
             | they can read "if you pass a string to require/import,
             | tooling can't figure that out" in a manual.
             | 
             | You can statically check "require(literal)" and cannot
             | "require(variable)". You can statically check "import from
             | literal" and cannot "import(variable)".
             | 
             | And awaited imports are essentially just memoize(name =>
             | (fs.readFile || fetch)(name).then(wrapAndEval)))(name).
             | It's not a black magic that is available only to "imports".
             | 
             |  _do strange things with the "exports" object_
             | 
             | It's just a value. Somehow analyzers/checkers can work with
             | "var x = do_strange_things()", but can't with exports. How
             | is a module boundary different from any other expression
             | boundary?
             | 
             | I really don't want to think that this is pure zealous
             | smoke blowing, but these arguments are as weak as nil, and
             | leave no options.
        
         | kabes wrote:
         | It's not the fact that imports are top level that makes it
         | statically analyzable. It's the fact that the imports can't be
         | variables like commonjs allows.
        
           | andrew_ wrote:
           | Say what now?                 const batman = await
           | import('batcave' + version);
        
             | kabes wrote:
             | That's only valid for dynamic imports. But if we consider
             | dynamic imports half of the rant in the post is wrong,
             | since these can appear nested.
        
           | tolmasky wrote:
           | But they can. I can construct any hard to find use case just
           | as easily with import. Your analyzer probably won't find
           | this: "(a => a)(path => import(path))(ROOT_PATH + "/x.js")".
           | 
           | "But no one would do that!" Yeah, and no one does it with
           | require either. The times people pass in variables to require
           | are well warranted and usually not in a browser context
           | (listed this in my other post, but for example loading
           | "x-mac.js" vs. "x-windows.js" in node, where there is no
           | problem since you're not bundling).
           | 
           | So neither in the practical sense nor the hypthetical sense
           | has the "static analysis problem" been solved any more than
           | it was already solved with require. People used require
           | basically like a static keyword before and you could
           | basically make the same usage assumptions as import in those
           | cases. Similarly, you can get _as dynamic_ as require with
           | import, so in the purely theoretical sense not much has been
           | made better either.
        
         | tentacleuno wrote:
         | > Yes, dynamic imports exist but they are sort of an exotic
         | feature that you would only use in special situations.
         | 
         | I wouldn't necessarily describe dynamic imports as an exotic
         | feature. They are basically required if you're building a semi-
         | large app (and heavily advised by most frameworks in that
         | case!). Otherwise, your homepage is going to load in some
         | complicated AuthorPage / HeavyComponentWithLotsOfDependencies
         | component the user might not even want.
         | 
         | The main advantage of ESM in this context would be its
         | asynchrony, which you won't get with require. Webpack tried to
         | tack this on with require.ensure, but it was nonstandard and
         | eventually deprecated.
         | 
         | Another advantage of ESM in general would be that you wouldn't
         | have to use preprocessors and compilers like Webpack. My main
         | reasoning here is that, prior to the import spec, web didn't
         | have any form of imports aside from loading in scripts and
         | polluting the global namespace (which isn't necessarily bad).
         | The majority of websites IME use some sort of bundler though,
         | so this isn't really a major change so much as a nice to have,
         | I suppose?
        
         | forty wrote:
         | Unless I'm missing something, the exercise is pretty simple,
         | and could be done with grep in a sane codebase (find all
         | require without indentation and with a single string literal
         | inside the parenthesis)
        
           | joepie91_ wrote:
           | It's a bit more complicated than that because you'd not want
           | to match such things if eg. they exist _inside_ of another
           | string literal; but the actual implementation of  'dependency
           | collection' in various bundlers isn't too far off from this.
           | They just often do an AST match instead of a string match.
        
       | jitl wrote:
       | The things I dislike the most in software development is dogma,
       | holy wars, and religious crusades about technology practices. I'm
       | not sure to the extent that this happens in other ecosystems, but
       | it seems to happen quite a bit in JavaScript circles. You can
       | ignore these for the most part if you use boring tools and don't
       | chase the new frameworks-du-jour, but in the case of ESM versus
       | CommonJS I am starting to feel the fire of this war in my
       | dependency graph.
       | 
       | My solution in NodeJS programs for now is to use an `esbuild`
       | -based require hook to transpile all the files we import or
       | require into CommonJS on the fly. We need esbuild anyways to run
       | Typescript code without a build step, and combined with basic
       | mtime based caching, it's fast enough that you really don't
       | notice extra build latency especially on a second run -- much
       | MUCH faster than a Babel require hook.
       | 
       | I plan to tune back into this issue once the average comment is
       | more measured and thoughtful, and the ecosystem tooling for
       | dealing with the migration has evolved more.
        
       | axismundi wrote:
       | Don't bundle. The only reason for bundling is too many requests
       | to the server. Use HTTP/2 instead.
        
         | crooked-v wrote:
         | If you do that you're stuck with round trip times all the way
         | down the dependency tree. HTTP/2 reduces the overhead of that,
         | but doesn't eliminate it, so now you've added a bunch of
         | loading time to your site.
        
           | axismundi wrote:
           | I thought that as well, until I read this:
           | 
           | https://www.sitepoint.com/file-bundling-and-http2/
           | 
           | https://medium.com/@asyncmax/the-right-way-to-bundle-your-
           | as...
        
             | crooked-v wrote:
             | I don't see a counterargument here. "It's fast" doesn't
             | change the fact that using 'native' imports instead of
             | bundles means you're still adding the round trip time for
             | the browser to request each set of dependencies all the way
             | down the dependency tree.
        
         | draw_down wrote:
         | Madness.
        
         | RedShift1 wrote:
         | Works... as long as you are next to the server...
        
       | [deleted]
        
       | jeabays wrote:
       | Everything JS is terrible.
        
       | somehnacct3757 wrote:
       | I keep thinking how QUIC / HTTP/3 would go nicely with ESM in the
       | browser (via script tags with type=module) for simple sites.
       | 
       | A webmaster could totally avoid the complexity of learning a JS
       | tool chain. Right now even 1000 lines of JS has you reaching for
       | a bundler. It would make shipping a small html+css+js site again
       | as simple as dragging files to your webserver.
        
         | unilynx wrote:
         | QUIC / HTTP3 does not fix the roundtrip wait times caused by
         | dependencies - if 'a.mjs' imports 'b.mjs' which in turn imports
         | 'c.mjs'... you still need two roundtrips to load all three.
         | 
         | only a bundler can fix that (or a hypothetical process which
         | would set up preloads for all required libraries, but that
         | would just be a bundler without the actual bundle creation
         | step)
        
           | TheCoelacanth wrote:
           | An ES module-aware server can avoid that problem by using
           | HTTP Server Push[1], but I haven't seen a production-quality
           | implementation of that, only proof-of-concepts.
           | 
           | [1] https://medium.com/@nikita.malyschkin/the-future-of-web-
           | depl...
        
             | spankalee wrote:
             | Server push is being deprecated because it was far to hard
             | to implement correctly. A server that knows the dependency
             | graph could inject <link rel=modulepreload> into the HTML
             | though.
        
         | e1g wrote:
         | Bundling will not go away as it solves a different problem of
         | how to best distribute the app to final users. When authoring
         | code, you want to have many small files so you can keep related
         | logic blocks isolated from the rest. When distributing the
         | code, you want to ship a few larger files to reduce network
         | overheads. Any non-trivial frontend app will call code from
         | 100+ files, and your browser is tuned to request these files
         | ~serially (or in serial batches of 8-10) which becomes
         | frustrating very quickly even on localhost.
        
           | joepie91_ wrote:
           | Worse, even if they were all fetched in parallel, you would
           | _still_ see terrible loading times simply because a
           | dependency graph can only be traversed depth-wise serially.
           | Doesn 't matter what network protocol you use or how parallel
           | it is.
        
             | spankalee wrote:
             | This is not true at all. For every module you can parse out
             | and load the module's imports in parallel. As you traverse
             | the graph the known and loadable module frontier can grow
             | much wider.
             | 
             | The only way it would be serial is if every module only
             | imported one other module.
        
               | joepie91_ wrote:
               | Note how I was specifically talking about _depth-wise_.
        
               | spankalee wrote:
               | Fair, though that wasn't clear.
               | 
               | So then even without modulepreload or bundling, it's not
               | always going to be true that the longest import depth is
               | the limiting factor. Earlier loaded modules can still be
               | parsed in parallel while dependencies are fetched, and
               | completed subtrees can be linked and evaluated. Given
               | that modules are deferred and can be imported early,
               | there's often time for parallel work.
        
               | joepie91_ wrote:
               | I'm not following. Dependencies can be nested, so you
               | cannot assume that a dependency by a given name can be
               | satisfied by a previously loaded dependency by the same
               | name. Which means you _still_ need as many serial
               | roundtrips as there are depth levels, whether you parse
               | in parallel or not.
               | 
               | Sure, you can cut down on the delay introduced by the
               | parsing, but 10 depth levels over a 100ms connection is
               | still going to take a second to fetch, because you simply
               | can't know the N+1th dependency until you have at least
               | completed the Nth roundtrip.
        
       | andrew_ wrote:
       | I really, really loathe how major packages in the ecosystem are
       | "We're ESM now, deal with it, sorry about your luck," and forcing
       | the issue. It's arrogant as hell. A hard fork of Node for ESM
       | would have been a much better path (e.g. Deno) That said, the
       | OP's rant is more emotion than fact.
       | 
       | > And then there's Rollup, which apparently requires ESM to be
       | used, at least to get things like treeshaking. Which then makes
       | people believe that treeshaking is not possible with CommonJS
       | modules. Well, it is - Rollup just chose not to support it.
       | 
       | Rollup was created specifically for ESM. It's not been thrust
       | onto the ecosystem or into anyone's tool chain. One uses it
       | specifically for ESM, and plugins that bolt on for added
       | functionality if they apply. Trying to hammer a nail with a
       | paintbrush doesn't make the paintbrush a bad thing - you just
       | chose the wrong tool.
        
         | devmunchies wrote:
         | > _loathe how major packages in the ecosystem are "We're ESM
         | now, deal with it, sorry about your luck," and forcing the
         | issue_
         | 
         | I wasn't able to use the latest version of node-fetch in a
         | node.js script since it doesn't support commonjs. The project
         | literally has "node" in the name and doesn't support default
         | node.js.
        
           | theprotocol wrote:
           | I just encountered this. FYI You can use v2 which still
           | retains commonjs support.
        
             | andrew_ wrote:
             | I've ended up using last major versions as well. I plan to
             | move to Deno anyhow, and authors like sindresorhus are at
             | least applying security updates to the major version before
             | the switch.
        
           | spankalee wrote:
           | Were you not able to convert the script to a module, or
           | dynamically import() node-fetch?
           | 
           | Since you're using it for an async operation anyway, dynamic
           | import should have worked quite well.
        
       | throwaway2077 wrote:
       | question:                 // ...            if (condition)
       | {         const x = require('../../../hugeFuckingLibraryThatTakes
       | SeveralSecondsToLoadUponColdStart')         // do something with
       | x       }            // ...
       | 
       | assume I don't give a fuck about nerd bullshit and I just want
       | the code to be simple and the program to run fast (which it does
       | when !condition because it doesn't need to load
       | hugeFuckingLibrary), can I replicate this behavior with ESM?
        
         | jitl wrote:
         | You can replace that require with `await
         | import('giantLibrary')` but now your function needs to be
         | async, and so do all of its callers. This is needed because
         | it's unacceptable to block the UI thread synchronously
         | importing code in the browser, but in CLI programs not being
         | able to synchronously require is a bit annoying.
        
         | tehbeard wrote:
         | Well since you asked so "fuckin" nicely /s
         | if( condition ){           import('../../../hugeFuckingLibraryT
         | hatTakesSeveralSecondsToLoadUponColdStart').then( x => {
         | //do something with x           })         }
         | 
         | > ...nerd bullshit...
         | 
         | I hate to break it to you darling, but programming is nerd
         | bullshit.
         | 
         | edit: alternate that might too much "nerd bullshit", but uses
         | async/await if the surrounding code is an async function:
         | async function doSomeStuff()         {             if(
         | condition ){               const x = await import('../../../hug
         | eFuckingLibraryThatTakesSeveralSecondsToLoadUponColdStart');
         | //do something with x             }         }
        
       | junon wrote:
       | This is a criticism of the tooling, not of the language feature.
       | 
       | This is like saying "binding two pieces of wood together is
       | terrible" and using the fact that screwdrivers are poorly
       | designed as your main argument.
        
       | aurelianito wrote:
       | Both CommonJS and ES6 modules suck. The way things should have
       | been is requirejs. Modules are defined and loading using an API
       | instead of having reserved words. It's really sad what happened
       | to modules in JavaScript.
        
       | eyelidlessness wrote:
       | Here is why ESM is better for static analysis than CJS:
       | module.exports = {             get foo() {                 const
       | otherModule = require('equally-dynamic-cjs')                 if
       | (otherModule.enabled) {                     return
       | any.dynamic.thing.at.all                 }             },
       | get bar() {                 this.quux = 'welp new export!'
       | return 666             },             now: 'you see it',
       | }              setTimeout(() => {             console.log(`now
       | you don't!`)             delete module.exports.now         },
       | Math.random() * 10000)              if (Date.now() % 2 === 0) {
       | module.exports = something.else.entirely         }
       | 
       | You can, of course, achieve this sort of dynamism with default
       | exports. But default exports are only as tree-shakeable as CJS.
       | Named exports are fully static and cannot be added or removed at
       | runtime.
       | 
       | Edit: typed on my phone, apologies for any typos or formatting
       | mistakes.
        
       | dgb23 wrote:
       | In my opinion as a working web developer, ES modules are half-
       | backed, deceptively simple, do not solve problems consistently
       | and are not built on hard acquired wisdom from other languages.
       | 
       | 1) JavaScript could have simply stolen an already good solution.
       | For example namespaces (ex: Clojure/Script, Typescript, even PHP
       | to some degree) provide a powerful mechanism to modularize names
       | - by disentangling them from loading code. They make it straight
       | forward to avoid collisions and verbose (noisy) names. In Clojure
       | namespaces are first class and meant to be globally unique. This
       | implies long-term robustness.
       | 
       | 2) Loading modules dynamically should be the _default_. The whole
       | point of JavaScript is that it is a dynamic language. The
       | caveats, hoops and traps that we have to sidestep to for example
       | run a working, real REPL during development is astounding. If you
       | want to be dynamic, go _all_ the way and understand what that
       | means. Yes, it's a tradeoff to be a dynamic language, but why
       | take the worst of both worlds?
       | 
       | 3) Like 'async/await', 'class' and many browser features such as
       | IndexedDB it is neither designed from first principles nor fully
       | based on past wisdom. Many things in the JS world smell of
       | "lowest common denominator". Way too much effort is focused on
       | the convenience side of things and way too little on the leverage
       | side.
        
       | arh68 wrote:
       | Agreed. So much breakage for so little. If I were teaching JS
       | today I don't know if ESM is worth covering while CJS works at
       | least as well. Maybe next year.
        
       | TekMol wrote:
       | ES Modules are great. Building JS applications is so much
       | speedier, leaner and more fun now that they are supported widely.
       | 
       | One fallacy the author falls for is that they think one needs a
       | build step "anyway" because otherwise there would be too many
       | requests to the backend.
       | 
       | Loading an AirBnB listing causes 250 requests and loads 10MB of
       | data.
       | 
       | With a leaner approach, using ES Modules, the same functionality
       | can be done with a fraction of those requests. And then - because
       | not bundled - all the modules that will be used on another page
       | will be cached already.
       | 
       | I use ES Modules for all my front end development and I get
       | nothing but praise for how snappy my web applications are
       | compared to the competition.
        
         | catern wrote:
         | >And then - because not bundled - all the modules that will be
         | used on another page will be cached already.
         | 
         | Why isn't anyone else mentioning this feature? I'm not a
         | browser developer but this seems like a clear win, and indeed
         | makes bundling unnecessary. I'm assuming that it's shared
         | between domains, too - or are people's dependencies so
         | fragmented that there's basically no sharing between domains?
        
           | forty wrote:
           | In practice I think downloading many small files is actually
           | slower than a single bigger file. It might not be so true
           | today with http2 for example though.
        
             | kevingadd wrote:
             | Yes, it's much slower pre-http2 since no modern browser
             | actually does pipelining, so it's going to be a socket per
             | file
             | 
             | HTTP2 fixes this by allowing multiple requests to occur in
             | parallel on a single connection.
        
               | forty wrote:
               | In the specific case of JS import, it's still going to be
               | pretty bad though, I guess, since you have to download a
               | file, parse it to figure out the deps, then fetch them,
               | parse them, etc, so you are limited in what you can do in
               | parallel.
        
             | acdha wrote:
             | HTTP/2 definitely changed that and is near-universally
             | supported now. The biggest win is the ability to cache
             | things independently: with bundles your servers and clients
             | have to retransfer everything if a single byte changes, and
             | most of the sites I've worked on don't change most of their
             | files every time they ship an update. A cold visit will
             | usually be immeasurably different from a bundle but a warm
             | visit is noticeably faster.
        
           | qudat wrote:
           | We already do this with a build step using a vendor bundle
           | and an app bundle and it can be configured to create a bundle
           | for each page.
        
           | simonw wrote:
           | I believe sharing cached code between domains has been almost
           | entirely eliminated by browsers now, because it turned out to
           | be a huge privacy leak: a malicious domain could attempt to
           | load code that was used by another domain, time how long it
           | took to load and use that to determine if the user had
           | visited that other site.
           | 
           | Browsers fixed this by making the browser cache no longer
           | shared between domains.
        
             | catern wrote:
             | Hm, I wonder if this could be circumvented by doing timing
             | attacks against the CDN cache? That's still shared between
             | domains...
        
               | jakelazaroff wrote:
               | It is not:
               | 
               | https://developers.google.com/web/updates/2020/10/http-
               | cache...
               | 
               | https://developer.mozilla.org/en-
               | US/docs/Web/Privacy/State_P...
        
               | catern wrote:
               | That's the cache in the browser, not the cache in the
               | CDN.
        
               | jakelazaroff wrote:
               | Oh, I see what you're saying. How could that possibly be
               | exploited for a side channel attack, though? All it would
               | tell an attacker is that _someone_ requested the file
               | before.
        
           | tofflos wrote:
           | The cache used to be shared between domains but that's no
           | longer the case due to privacy concerns and limited
           | effectiveness.
           | 
           | > As of Firefox v85 and Chrome v86 the browser cache will be
           | partitioned, this means that the same resource included on
           | two sites will have to be downloaded from the internet twice
           | and cached separately.
           | 
           | Source https://www.peakhour.io/blog/cache-partitioning-
           | firefox-chro....
        
             | acdha wrote:
             | This still helps other requests to the same origin, which
             | is a very common situation if you don't have the extra
             | resources needed to get an SPA up to the same performance
             | and reliability levels.
        
         | forty wrote:
         | I think the trick is mostly not to have a shitload of
         | dependencies. If you have to load a bunch of huge frameworks,
         | whether it's bundled or you have to download thousands of files
         | one by one, it's going to be slower than not doing it at all :)
        
           | [deleted]
        
         | incrudible wrote:
         | > Loading an AirBnB listing causes 250 requests and loads 10MB
         | of data.
         | 
         | How many of these requests are dependent? Lazily loading
         | hundreds of images doesn't impact page responsiveness, but
         | loading an import of an import of an import before your page
         | does anything is unacceptable.
         | 
         | > I use ES Modules for all my front end development and I get
         | nothing but praise for how snappy my web applications are
         | compared to the competition.
         | 
         | So you actually ship unbundled ES modules? How much code is
         | that? I dare you to bundle it up (rollup/esbuild) and tell me
         | that doesn't improve load times. Comparing to the average
         | website overloaded with crap is a very low bar.
        
           | TekMol wrote:
           | tell me that doesn't improve load times
           | 
           | It will negatively impact load times.
           | 
           | Either you only bundle what is needed on the current page.
           | Then the next page will load slower because it needs to
           | bundle all those modules again as it uses a slightly
           | different set of modules.
           | 
           | Or you bundle everything used on any page your users might go
           | to during their session. This will give you a giant blob that
           | has to be loaded upfront which contains a ton of modules the
           | user never need during their browsing session.
        
             | martpie wrote:
             | That's true if you create your own bundle yourself. Famous
             | frameworks like Next.js or Nuxt make much smarter bundles,
             | with common dependencies grouped together, and bundling the
             | rest by page/view, then loading each bundle when needed.
        
               | jakelazaroff wrote:
               | It's still a tradeoff, though. Let's say a website has
               | three pages: /a, /b and /c. Two of those pages, /a and
               | /b, each use the module `foo`. Where should `foo` get
               | bundled? If you put it in the "common" bundle, it'll get
               | served to /c even though it's not needed. If you put it
               | in both of the bundles for /a and /b, the client will
               | download it twice.
        
         | alerighi wrote:
         | Any moderately complex frontend application already has to have
         | some sort of build system. One common example is using
         | TypeScript (and these days I don't see the point of using
         | JavaScript and spending hours to fix bugs generated by its
         | missing type safety), or using JSX syntax that must be
         | transpiled, or even if you use plain JS, to transpile it to
         | support older browsers (yes, there is still too much people
         | using Internet Explorer to ignore it).
         | 
         | If you already have a build system, the most sensible thing to
         | me is letting the build system do their stuff and not worry
         | about it. When I write a web application in React with
         | TypeScript (the setup that I usually use) I don't worry about
         | dependencies, and I use the ES modules import syntax (that is
         | better than the CommonJS one) that gets transpiled to CommonJS
         | without I even notice. So why bother changing that? It works,
         | it produces a minified and optimized single .js file that is
         | easy to serve from a webserver, I don't see points against it.
        
       | Aeolun wrote:
       | I kind of have to agree with the point about loading ESM in the
       | browser.
       | 
       | I tried doing this with one of the new fangled frameworks and
       | seeing my browser work through like 5000ish required files was
       | quite comical.
        
         | emersion wrote:
         | If you just have a handful of dependencies (which themselves
         | have few to no transitive deps) then it works just fine.
        
         | wereHamster wrote:
         | Unprocessed ESM in the browser makes sense for local
         | development. We are collectively wasting millions of CPU hours
         | (and developer time) waiting for our mostly unchanging
         | dependencies to be processed. For production deployment though,
         | I'd still prefer ESM in the browser, but not verbatim as they
         | are coming from npm, but compiled, minified, and bundled in a
         | way that strikes a balance between total number of modules,
         | code duplication inside the modules, long-term cacheability
         | etc.
        
           | javajosh wrote:
           | The more people start to internalize the truth that "if you
           | ship it you own it" and stop adding dependencies and start
           | removing them, especially if they come with their own
           | wasteful dependencies, then ESM will make sense for everyone.
           | Until then, you're right, devs have to go through unctuous
           | mitigations.
        
             | crooked-v wrote:
             | It would be easier to do that if proposals for things like
             | standard library functionality (not the contents of the
             | standard library itself, _just_ the syntax and
             | technicalities of using it) were to go anywhere in, say,
             | under five years.
        
           | noduerme wrote:
           | Just as an aside... every web app I write these days starts
           | with an index.html page that has a window["deploy"] bool at
           | the top. If that's false, the first script just requires the
           | unbuilt files. If true, it requires the compiled and minified
           | version. I only rebuild when I'm ready to upload.
        
       | lucideer wrote:
       | TL;DR:
       | 
       | 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
        
         | nuerow wrote:
         | > _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 _
         | 
         | 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.
        
         | djrockstar1 wrote:
         | 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.
        
           | bricss wrote:
           | dynamic imports for that matter, or:                   import
           | { createRequire } from 'module';              const require =
           | createRequire(import.meta.url);         const cjsOrJson =
           | require('./somewhat/module/pathway');
        
           | Ginden wrote:
           | > lazily loaded imports are both missing from the ES Modules
           | spec.
           | 
           | What do you mean?
        
           | wereHamster wrote:
           | 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 now
           | 
           | Actually, the first example could be rewritten as
           | const someInitializedModule = (await import("module-
           | name")).default(someOptions);
           | 
           | That <<simply not possible>> statement is simply not true
        
             | presentation wrote:
             | Or don't even bother with the awaited import and instead
             | import it at the top of the file, I fail to see why this is
             | even an issue lol
        
         | addicted wrote:
         | Let's flip this.
         | 
         | What are the advantages of ESM that led to selecting that over
         | CJS modules for standardization in the first place?
        
           | Ginden wrote:
           | Statically analyzable tree. In CJS you have to depend on
           | people not doing strange things to their `module.exports` and
           | you are left with heuristics.
        
             | joepie91_ wrote:
             | This is not actually a meaningful benefit that ESM has in
             | practice, as I've explained in my article.
        
               | lucideer wrote:
               | > _as I 've explained in my article_
               | 
               | 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.
        
         | Aeolun wrote:
         | There is one. Which is that you can't easily do an inline
         | require any more.
         | 
         | The rest of the text can mostly be summarized as
         | https://xkcd.com/927/
        
           | nrabulinski wrote:
           | I've been working with node for roughly 5 years and I've
           | never liked the hackieness CJS let people incorporate.
           | 
           | 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.
        
           | tehbeard wrote:
           | Inline require(...), Are we back to bad old days of PHP
           | include '...'; midway through a classes function already?
        
           | bricss wrote:
           | inline require is most likely a bad design, imo
        
         | crooked-v wrote:
         | The total inability to properly mock ES modules without
         | experimental Node flags is a big one. It can turn unit testing
         | into a nightmare if even one ESM dependency creeps in.
        
       | cookiengineer wrote:
       | This thread and summary are written by someone who has no clue
       | what they're doing in ECMAScript; and who's probably enjoying the
       | fucked up mess that the babel ecosystem created. I'm not gonna
       | dig into that, because reading any polyfill in babel's ecosystem
       | speaks for themselves on how messy, hacky, and actually not
       | working-as-specified most parts are.
       | 
       | Instead I'm gonna try to go back to the topic.
       | 
       | I think that in practice these are my pain points in using ESM
       | regularly without any build tool. I'm using ESM modules both in
       | node.js and in the Web Browser via <script type module>:
       | 
       | - package.json/exports "hack" works only in node.js and not in
       | the Browser as there's also no equivalent API available. This
       | hack allows to namespace entry points for your library, so that
       | you can use "import foo from 'bar/qux';" without having to use
       | "../../../" fatigued paths everywhere (that also might be
       | different in the Browser compared to the nodejs entry points).
       | 
       | - "export * from '...';" is kind of necessary all the time in
       | "index" files, but has a different behaviour than expected
       | because it will import variable names. So export * from something
       | won't work if the same variable name was exported by different
       | files; and the last file usually wins (or it throws a
       | SyntaxError, depending on the runtime).
       | 
       | - Something like "import { * as something_else, named as foobar }
       | from 'foo/bar';" would be the killer feature, as it would solve
       | so many quirks of having to rename variables all the time.
       | Default exports and named exports behave very differently in what
       | they assign/"destruct", and this syntax would help fix those
       | redundant imports everywhere.
       | 
       | - "export already_imported_variable;" - why the HECK is this not
       | in the specification? Having to declare new variable names for
       | exports makes the creation of "index" files so damn painful. This
       | syntax could fix this.
        
         | Ginden wrote:
         | > - "export already_imported_variable;" - why the HECK is this
         | not in the specification? Having to declare new variable names
         | for exports makes the creation of "index" files so damn
         | painful. This syntax could fix this.
         | 
         | You can do:                  export {already_imported_variable}
        
           | cookiengineer wrote:
           | ...which is a default export, not a named export, as I
           | already explained. My point was about the lack of exporting
           | named exports without the need to declare variable names.
           | 
           | Your solution will work only once in a file, therefore it is
           | useless to batch-export lots of imports for the mentioned use
           | case of an "index" file that exports all your classes and
           | definitions.
        
       | tbrock wrote:
       | We recently went through this hell converting a NodeJS codebase
       | to TypeScript. One reason many people willingly enter this
       | hellscape is because we need ES modules for typescript.
       | 
       | I say "need" because Typescript wont ingest types from "required"
       | files, you have to import them as modules.
       | 
       | So before we converted a single file to TS we has to audit all
       | commonjs imports and exports to convert them to ES modules.
       | 
       | I agree wholeheartedly that the end result was a fools errand. I
       | would have rather spent the time adding support for importing
       | types via a require which for some reason returns any "any"
       | today.
        
         | crooked-v wrote:
         | I think you've confused Typescript's own import/export system
         | with ESM. It uses the `import` syntax, but it's not ESM
         | internally, it's its own thing designed to ingest and export to
         | multiple module types.
        
           | draw_down wrote:
           | Really, you think GP just made this problem up?
        
         | theprotocol wrote:
         | The incompatibility is indeed exponentiated with TypeScript.
         | 
         | There is currently no non-hacky way for using both legacy
         | modules and ES Modules in the same project, and many libraries
         | on NPM have moved to ESM-only. TypeScript's transpilation needs
         | to know what to target as regards modules and JS version, which
         | makes things even crazier than they already are.
        
       | vbg wrote:
       | Rather a negative outlook.
        
       | rado wrote:
       | HTTP/2 uses a single connection for all modules, no?
        
         | crooked-v wrote:
         | If you use 'native' imports in that way instead of bundles,
         | you're still stuck with artificially delayed loading times
         | because the browser has to parse your code, request the first
         | layer of dependencies, parse that code, request the second
         | layer of dependencies, etc.
        
       | cryptica wrote:
       | It just sucks that development community decided to double down
       | on bulky build tools instead of trying to optimize server
       | environments to leverage advances like HTTP2 server push to
       | optimistically serve dependencies without latency. It's
       | particularly strange when you consider how popular Node.js is as
       | a server-side environment and how easy it would be to accomplish
       | this since the server is able to interpret JavaScript natively to
       | quickly figure out the client-side dependency tree.
       | 
       | My inner conspiracy theorist suspects that maybe the powers that
       | be don't want to allow plain JavaScript to extend its primacy
       | over the web. The way things went makes no sense. Computing
       | dependency trees on the server-side and using it to
       | optimistically push scripts to the browser would have been be far
       | simpler and less hacky than computing source maps, for example.
       | Optimistically pushing server-side resources was supposed to be
       | the whole point of HTTP2...
        
       | austincheney wrote:
       | The reasoning presented is only valid if you are stuck holding a
       | bunch of dependencies making use of old conventions. At that
       | moment the complaints about the module approach become a very
       | real concern.
       | 
       | That said the problem isn't modules are all. It's reliance on a
       | forest of legacy nonsense. If you need a million NPM modules to
       | write 9 lines of left pad these concerns are extremely important.
       | If, on the other hand, your dependencies comprise a few
       | TypeScript types there is nothing to worry about.
       | 
       | So it's a legacy death spiral in the browser. Many developers
       | need a bunch of legacy tools to compile things and bundles and
       | build tools and all kinds of other extraneous bullshit. Part of
       | that need is to compensate for tooling to handle modules that
       | predates and is not compatible with the standard, which then
       | reenforces not using the module standard.
       | 
       | When you get rid of that garbage it's great. ES modules are fully
       | supported in Node and the browser.
        
         | fullstackchris wrote:
         | And yet, the reality IS that 90% of the web is using legacy
         | stuff - heck, even something like 50% of the web still has
         | jQuery on it. (haven't checked the figure in a while, but I
         | guess it is still close to that figure).
         | 
         | I think the true anger is that something so essential and basic
         | to JS development has this giant breaking change if you want to
         | switch over to ESM - there's no reverse compatibility or
         | fallback - it just breaks.
        
           | austincheney wrote:
           | The solution is some soul searching. Do you really need Babel
           | and Webpack to build a web app? The answer is of course an
           | astounding YES! Most developers cannot add text to a page
           | without JSX, which therefore means React and everything it
           | requires.
           | 
           | So when you dig even deeper this is really a people and
           | training problem.
        
       | amadeuspagel wrote:
       | > And then people go "but you can use ESM in browsers without a
       | build step!", apparently not realizing that that is an utterly
       | useless feature because loading a full dependency tree over the
       | network would be unreasonably and unavoidably slow - you'd need
       | as many roundtrips as there are levels of depth in your
       | dependency tree - and so you need some kind of build step anyway,
       | eliminating this entire supposed benefit.
       | 
       | That's not true with skypack, right?
        
         | javajosh wrote:
         | It's important not to ignore the possibility that perhaps
         | front-end dependencies are out-of-hand, and need to be reduced.
         | ESM cannot fix a decade of bad practices enabled by front-end
         | build bundlers. ESM isn't there to be a viable alternative to
         | webpack. It's there to enable a different vision of application
         | deployment where apps are smaller, and javascript gets css's
         | transitive import() sub-resource distribution, avoiding the
         | headache of a linear list of global scripts.
         | 
         | I really like ESM because I like where it's trying to steer the
         | community of browser application builders. I think front-end
         | builds are terrible on many levels, not the least of which is
         | the obfuscation of code that undermines one of the best
         | features of the web's software distribution, which is its
         | openness. And another major benefit of webapps is that none of
         | the front-end languages require a build step! This makes
         | iteration very fast; if you can make do without the the safety
         | net of a compiler, you can enjoy the speed of not using the
         | bundler.
        
           | crooked-v wrote:
           | I think the basic problem with that comparison is that people
           | use bundlers with CSS all the the time for exactly the same
           | basic-physics-of-round-trips reasons. It's common for
           | `import()` in CSS to end up exactly what it usually in
           | frontend JS, a dev organization tool rather than something
           | actually exposed to the browser.
        
         | joepie91_ wrote:
         | It's a fundamental technical constraint of any tool-less setup.
         | At some point you _need_ to traverse the dependency tree by
         | parsing modules and following imports, and your choice is to do
         | that either:
         | 
         | 1) on the client, across the network, one roundtrip for every
         | level of depth, or 2) in a build environment, directly on the
         | filesystem
         | 
         | Option 2 means you need _some_ kind of build tool to make it
         | work, and by that point it doesn 't really matter anymore
         | whether the tool just traverses the dependencies and makes a
         | list of filenames, or also concatenates their contents into a
         | bundle.
         | 
         | And that is why the fundamental premise of ESM cannot work;
         | there are no technical options besides those two. If you want
         | to avoid network roundtrips, you _must_ have build tooling. No
         | way around it.
        
       | forty wrote:
       | I have been doing nodejs backend development for the past 10
       | years and I have no idea why ES module are needed and who is
       | using them. I assume this is a front end thing.
       | 
       | We are using typescript which is using "import" syntax, but as
       | far as I know, it's still transpiling to good old "require".
        
         | throw_m239339 wrote:
         | > I have been doing nodejs backend development for the past 10
         | years and I have no idea why ES module are needed and who is
         | using them
         | 
         | ES modules are part of the Ecmascript spec. DENO uses them.
         | Node.js modules aren't part of the ES spec.
        
           | forty wrote:
           | I see, so a front end thing indeed :) (and yes deno, which I
           | feel the main purpose is to bring the problems and debates of
           | the front end devs, in an otherwise much saner js backend end
           | world ^^ )
        
             | [deleted]
        
         | andrew_ wrote:
         | You can tell TS to output ESM. Try using top-level await with
         | TS and you'll run into that. The configuration options are
         | vast.
        
       | tolmasky wrote:
       | The "it works without a tool chain" is in fact a ridiculous
       | impractical hypothetical that no one should actually attempt, and
       | yet it continues to make this spec more complicated and unwieldy.
       | For example, to address the obvious performance problem of
       | dealing with loading dependencies, the "<link del=modulepreload>"
       | tag was added, which you're supposed to include for each
       | individual dependency in your html to let it know to start
       | fetching it ahead of time. So we've literally gone full circle
       | and arrived right back to where we started with a script tag for
       | every JS file being replaced with a link tag for every JS file.
       | "But you don't have to manually do that! Your build tools can
       | just insert the 100 link tags in your HTML file!" I thought all
       | this was to avoid a JS tool chain! If I'm running a build tool
       | I'll just have it generate one concatenated and minified artifact
       | that performs way better, not this mess! Here's the documentation
       | if you're interested in this hilarious feature:
       | https://developers.google.com/web/updates/2017/12/moduleprel...
       | 
       | Not to mention the security aspects: there is no subresource
       | integrity for imports, so it's less secure than bundling or using
       | a script tag with CDNs.
       | 
       | The point about it being a new syntax is also very valid.
       | Everything import patterns do is _almost_ identical to
       | destructuring, so we should have just extended that feature
       | instead, especially because I do wish destructuring could do
       | those things. For example, if destructuring had an "everything"
       | pattern to complement the "rest" pattern:                   const
       | { x, ...rest, *original } = something();
       | 
       | Where "original" now just contains a reference to the actual
       | returned object, instead of having to break that pattern up into
       | two declarations since the moment destructuring takes place the
       | original object becomes inaccessible. This would have of course
       | given us the "import * as" ability, but is again a feature I
       | regularly find myself wanting everywhere. Not to mention this
       | makes writing code transformations even harder as JavaScript's
       | huge syntax keeps growing and requiring tons of special cases for
       | almost identical statements.
       | 
       | The semantics of imports are also very confusing to beginners, as
       | they implement yet another unique form of hoisting. It is _so
       | weird_ that despite being allowed anywhere in your code, they
       | _run_ first. Notice I didn't say they _fetch_ first, they _run_
       | first. So for example, the following code is broken:
       | process.env.S3_LOCATION = "https://..."; // The below library
       | expects this as an environment variable.         import download
       | from "s3-download";
       | 
       | Oops! Your env variable gets set _after every line of code in the
       | import chain of s3-download runs!_ So bizarrely, the solution is
       | to put the first line _in its own import_ , and now it _will_ run
       | first                    import unused from "./set-env-
       | variable.js"          import download from "s3-download"
       | 
       | If the rule is that imports _must_ run before any code in the
       | file, then why not restrict the statement to only being at the
       | top of the file? What is the purpose of allowing you to put all
       | your imports at the bottom? Just to make JavaScript even more
       | confusing to people? Imagine if "use strict" could appear
       | _anywhere_ in the file, even 1000 lines in, but then still
       | affected the whole file. It was already the case that people
       | found function hoisting,  "var undefined" hoisting, and the
       | temporal dead zone of let/const (3 different kinds of subtly
       | different hoists) to be confusing in a language that prides
       | itself for being able to be read "top to bottom", why add a
       | _fourth_ form of hoisting?
       | 
       | Anyways, the list of problems actually continues, but there is
       | widespread acceptance that this feature would not have been
       | accepted in its current form if introduced today. But for some
       | reason everyone just takes a "but it's what we got" position and
       | then continues piling more junk on top of it making it even
       | worse.
        
       | bricss wrote:
       | First you create problem with an article, then you fix it with
       | your own magic tool, bravo!
       | 
       | https://www.npmjs.com/package/fix-esm
        
         | NicoJuicy wrote:
         | Joepie91 did some work for me long ago
         | 
         | If he says there is a problem, he didn't invent it. He knows
         | his stuff
        
         | [deleted]
        
         | joepie91_ wrote:
         | Believe me, I would much rather not have had to build that
         | hack. But considering that I want my development tools to
         | actually, y'know, _work_ , what would you expect me to do?
        
       | spankalee wrote:
       | This post is terrible, actually.
       | 
       | CommonJS was _never_ going to be natively supported in browsers.
       | The synchronous require semantics are simply incompatible with
       | loading over a network, and the Node team should have known this
       | and apparently (according to members of TC39 at the time) were
       | told their design would not be compatible with future a JS module
       | standard.
       | 
       | So the primary thing that JS modules fix is native support, and
       | for that you need either dedicated syntax or an AMD-style
       | dependencies / module body separation. AMD is far too loose (you
       | could run code outside the module body), so dedicated syntax it
       | is.
       | 
       | Everything else flows from there. I really hate how people blame
       | the standards instead of the root cause which is Node not having
       | taken the browser's requirements into consideration. Culturally,
       | I think that's mostly fixed now, but it was a big problem early
       | on in Node's evolution.
        
         | alerighi wrote:
         | Yes but who cares of native support in the browser? I mean,
         | most JS stuff nowadays is transpiled, written in TypeScript, or
         | if written in plain JS still transpiled anyway to support older
         | browsers, and bundled in a single optimized file.
         | 
         | Loading all the dependencies over the network to me is just
         | inefficient, you will have hundreds of requests instead of a
         | single one, you will load the full source not a minified and
         | optimized one, I just don't see the point.
        
           | spankalee wrote:
           | Native support matters so that we're not eternally _required_
           | to use tools for even the simplest of cases. Being able to
           | write two files with one importing the other with no npm or
           | bundler in sight should absolutely be a feature of the native
           | platform.
           | 
           | And yes, in production you probably will want to bundle, but
           | you probably also want to minify. Does that imply that we
           | should require a minifier to even run any code at all, even
           | in dev? No, of course not.
           | 
           | By adding a standard and native support we allow for sites
           | that work without bundling and bundling that can adhere to
           | the standard and not have to even be configured because the
           | input is standard and the output must preserve those standard
           | semantics. That gives tool independence and simplifies usage
           | of the toolchains, and that's a great goal to shoot for.
        
             | alerighi wrote:
             | If the project is not so complex, you don't need modules at
             | all. You can just do like we did in the old days (and I
             | still indeed do for simple projects like mostly static
             | sites) and load your JS with `<script>` tags. You can have
             | multiple files, of course everything must be in the global
             | scope to use that but still you can do that.
        
       | davnicwil wrote:
       | > for some completely unclear reason, ESM proponents decided to
       | remove that property. There's just no way anymore to directly
       | combine an import statement with some other JS syntax
       | 
       | This is one of those 'worse is better' things in language design,
       | I believe. It guarantees simplicity, traded off against extra
       | verbosity. In fact, when it comes to the common and probably most
       | valuable case of reading and understanding code written by others
       | quickly, it is not even a tradeoff really, as _both_ are good.
       | 
       | Whether or not that was one of the driving reasons, it certainly
       | is a benefit in my opinion. The two examples given in the post of
       | an inline require don't demonstrate this well, as they're both
       | really simple. I'd say the benefit isn't to stop things examples
       | like that being written and replace them with two lines of code,
       | which admittedly might sometimes be slightly cumbersome. It's
       | that it stops the long tail of much more complex/unreadable
       | statements being written.
        
         | eyelidlessness wrote:
         | > This is one of those 'worse is better' things in language
         | design, I believe. It guarantees simplicity, traded off against
         | extra verbosity.
         | 
         | And with top-level await the restriction goes away (albeit the
         | ESM equivalent is still a bit more verbose).
         | (await import('anything'))(...yup)
        
         | joepie91_ wrote:
         | I would have considered this a valid argument _if_ overly-
         | clever use of `require` was actually a problem in JS. But it 's
         | not! These 'simple' types of obvious cases are the only types
         | of cases that people actually use this syntax for in practice.
        
       | aliswe wrote:
       | i have sympathy for your projects that will need maintenance. but
       | i am making the observation that you're starting to get old -
       | you're being quite bitter over something which is clearly bigger
       | than all of us.
       | 
       | on a personal note, im making a cms with es modules and couldnt
       | be happier.
        
       | api wrote:
       | It's puzzling to me why there isn't more effort on developing
       | front end Go or Rust frameworks that compile to JS or WASM. It
       | would be a chance to work in a real language instead of this
       | trash.
        
       | jokethrowaway wrote:
       | ES Modules was what turned node.js into legacy for me, same as
       | python 2/3.
       | 
       | Plus transpilers are so slow, it's embarrassing (albeit things
       | are improving with tools written in rust).
       | 
       | As someone who's been doing frontend for 20 years and node.js for
       | 10 years, JS development has never been so crap like now.
       | 
       | After attending a conference talk about how the TC39 works, I
       | understand why that's the case. TC39 is basically a bunch of
       | engineers from big tech companies who can afford to waste
       | productivity to follow the whims of whatever the group decide.
       | It's completely detached from reality.
       | 
       | They operate on a full consensus basis, which means everyone
       | needs to be onboard with the decisions - and if you want your
       | changes to be approved in the future, you'd better play nice with
       | the current change as well.
       | 
       | To be honest, I can't wait until browsers get replaced with some
       | native crossplatform toolkit or frameworks in other languages
       | become popular so that we can finally leave JS alone.
        
         | [deleted]
        
         | paufernandez wrote:
         | > To be honest, I can't wait until browsers get replaced with
         | some native crossplatform toolkit
         | 
         | That sounds like Flutter to me.
        
       | lampe3 wrote:
       | Not sure it the author tried a new build tool like vite, esbuild
       | and so on.
       | 
       | Working on large projects and having everything first loaded and
       | then you can load it in the browser is a waste of time that every
       | web developer has every day.
       | 
       | Some real world times FOR DEVELOPMENT: Storybook first load: 90
       | sec, Storybook after first load changes: 3 sec, Vue App first
       | load: 63 sec, Vue app change after that: 5 sec, Vue App with Vite
       | first load: 1sec, Vue App with Vite after that: the time it takes
       | me to press command+tab to switch to the browser
       | 
       | Do we really have people that use unminfied unbundled esm in
       | production? If Yes, please comment why?
       | 
       | I would also ask the author what about cyclic dependencies? ES
       | Modules resolve them automatically. Something which in large code
       | bases can happen.
       | 
       | Why do we still put it through babel? Because most of us don't
       | have the luxury of not supporting old browser...
       | https://caniuse.com/?search=modules Even if the not supported
       | browsers for our company is 1% it is still a big chunk of money
       | in the end.
       | 
       | and this example: ``` app.use("/users",
       | require("./routers/users")); ```
       | 
       | Really? this is "good code" having a require in the middle of a
       | file?
       | 
       | Also funny: The author is annoyed that rollup did not support
       | tree shaking in commonjs and then complains that people are
       | wasting time on esm. Maybe the rollup team does not want to waste
       | time on commonjs? Also then he points to a package which did not
       | got any update in 3 years and would make the hole process he
       | complains is to complex even more complex by introducing a new
       | dependencies.
       | 
       | Sorry but the more I read that thing the more it sounds to me
       | like a Junior Dev that does not want to learn new things and just
       | likes to rant about things.
        
         | joepie91_ wrote:
         | Hi, author here. I'm going to ignore the personal attacks and
         | simply point out that my dev build processes typically have a
         | startup time of under 5 seconds even for large projects, and a
         | rebuild time of under 500ms. This is with Browserify.
         | 
         | If you are having very slow build times with your existing
         | toolchain, the problem isn't the bundling, which is an
         | extremely fast operation. It's almost certainly going to be one
         | specific computationally-intensive plugin that you either don't
         | need, or would also need if using ESM.
        
           | lampe3 wrote:
           | "Wie man in den Wald hinein ruft, so schallt es heraus" since
           | your from NL it should be easy to translate.
           | 
           | These heavy plugins are usually for old browsers to also run
           | in them. That is the only job ob babel.
           | 
           | CJS has some deeper problems. - Check if your fav CJS lib
           | freezes the objects? - ESM is more http friendly (mime type)
        
             | joepie91_ wrote:
             | The build times I listed are _including_ a Babel plugin.
        
         | wruza wrote:
         | It's usually senior devs who don't want to learn new things
         | just because these are new. And aren't afraid to use 3 years
         | old packages (how dare they!). Imagine having a subroutine
         | which is "done" doesn't get updates for years, laughable! Who
         | makes it "done" when you can fuck it up at the start and fix a
         | little every day, filling that activity grid with green dots.
         | 
         | All of this js-related stuff is just a fast fashion, the bad
         | part is web developers are locked into it with no chance to
         | relax and to just create their boring services and apps.
        
       | aravindet wrote:
       | There is a valid discussion to be had about whether the Node.js
       | ecosystem disruption of moving from CJS to ESM is worth the
       | benefits, but the assertion that it's technically worse isn't
       | accurate. A few things ESM does better _in Node.js_ :
       | 
       | 1. Asynchronous dynamic import() vs. blocking require(): allows
       | the program to continue while a module is being dynamically
       | loaded.
       | 
       | 2. Circular dependencies: ESM correctly resolves most of them,
       | while CJS does not. [example below] I believe this is possible
       | because ESM top-level imports and exports are resolved _before_
       | JS execution begins, while require() is resolved when called
       | (while JS is already executing.)
       | 
       | 3. Reserved keywords `import` and `export` vs. ordinary
       | identifiers require, exports and module: Allows tooling to be
       | simpler and not have to analyze variable scope and shadowing to
       | identify dependencies.
       | 
       | I haven't really encountered #3, but I can say I've benefited
       | from #1 and #2 in real-world Node.js projects using ESM.
       | 
       | ----
       | 
       | Circular dependencies example:                  // a.js
       | const b = require('./b.js');        module.exports = () => b();
       | // b.js        const a = require('./a.js');        module.exports
       | = () => console.log('Works!');        a();
       | 
       | Running this with "node b.js" gives "TypeError: b is not a
       | function" inside a.js, while the equivalent ESM code correctly
       | prints 'Works!'. To solve this in CJS, we have to always use
       | "named exports" (exports.a = ... rather than module.exports =
       | ...) and avoid destructuring in the top-level require (i.e.
       | always do const a = require(...) and call it as a.a() elsewhere)
        
       ___________________________________________________________________
       (page generated 2021-11-07 23:02 UTC)