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.