I'm not sure what you mean by all or nothing thinking here. The problem with imports is precisely that it is incredibly cumbersome to use without a bunch of additional "add-ons". You can't just "use a little bit of it." Want to do the supremely simple task of importing a JSON file? Better make sure your browser supports import assertions (assuming one even knows these exist). Want to not have to import a long incomprehensible URL? Better write a manifest file called an import map that only recently got full browser support. You can't even trivially add a script tag with an ESM module because it won't work from a file:// domain, you have to start up a simple server for your simple.html file. So I have no idea where this notion of not needing tools comes from. I think what people actually mean is "my tools support ESM out of the box". It is not trivial to get a nice experience with zero tooling. Again, you at minimum need to run a server (and I believe you have to fiddle with the MIME types for python's one line simple server too, although perhaps that has now been rectified). The simplest projects require strictly more tools to use this thing. And on the opposite end of the scale, it appears that you agree that of course you will use tooling. Only hopefully those tools aren't generating imports but just bundling because if not they are creating slower and less secure resources.

ESM imports have fallen into the C++ trap: every year the determination is made that "if we just add these 5 extra features (both to the the ECMAScript spec and W3C spec), then they'll finally be great out of the box!" This is how we ended up with import.meta and dynamic import and import maps and import assertions and preloadmodule link tags, which are all still less flexible than require/bundler, and somehow still don't have any answer whatsoever to integrity checksums that originally shipped in 2015. It's absurd.

To be clear, I am not saying there shouldn't have been "an" import spec. I am saying this one is tragically flawed and remains realistically a bad option (unless you are using a compiler that is just going to turn it into roughly the same minified mumbo jumbo it was turning your requires into before). This is fairly well accepted at TC39. The spec was rushed. Nothing would be allowed to ship with that level of consideration anymore. Usually someone at this point asks "well what would you have done?!". I would have punted on import until after async/await. Many of the most fundamental issues in imports come from having to have conceived them in a pre-async/await world. They are essentially a "top-level-await-ish" statement, in a world with async/await. However, had async/await and top-level-await shipped first, and then import was introduced, you could have started with the expression form of import (await import()), which would not have required any new syntax (you could just use destructuring, the same way you do now), and not required a "exercise left to the host environment" hand-waivy async-link resolution system, but instead used the actual existing semantic of the language. We ended up having to do it anyways with dynamic import. It is so weird that `x as y` becomes `{ x: y }` and `x` becomes `{ default: x }` merely by moving the entrypoint around in the file. It would also be a lot more transparent to the user: by typing `const x = await import("blah")`, you aren't (erroneously) lead to believe that this is a synchronous action when it isn't. It's also way easier to add additional features to a function (such as integrity hashes or assertions or whatever), vs. in a statement where it requires a parser level change, and for no real benefit other than causing people to have to learn a bunch of weird new syntax that increasingly feels out of place.