6 ms·
It's really wild seeing the tremendous amount of work being put in to ESM just to come up with a solution that still isn't as fast, secure, or ergonomic as bund
by tolmasky 3y ago
It's really wild seeing the tremendous amount of work being put in to ESM just to come up with a solution that still isn't as fast, secure, or ergonomic as bundling.
1. Import maps are basically a compile-time feature. I thought the point of all of this was to not have to have toolchains?
2. Imports significantly lag behind "old school" scripts in security. You can't use integrity hashes with them, and yet most the examples you see are about importing random scripts from all over the web. This is actually hilariously related to point (1), since you can only escape the realization that this is a feature a compiler is supposed to spit out if you direct the imports to some third party resource. So in order to contrive a situation where you would manually craft an import map, they are forced to show an example that is less secure than the stuff that has worked in every browser for 10 years.
3. Not directly related to import maps, but you'll probably end up using them together: in order to attempt to achieve similar performance to just bundling your code, the amazing solution we've come up with is to... put a <link rel="modulepreload"> tag in your head element for each script you want to be fetched immediately. Yes, so much better than the ridiculous old method of using a script tag for each script. Now you just use a link tag for each script instead. I'm sure someone will tell you that this is better because you don't have to do that. Yes, yes, so much better that the default behavior of script loading is slow unless you go read about a bunch of "tips and tricks" to get it working with reasonable performance.
It's always great to take one concept like "importing a script" and turn it in a an entire ecosystem of terminology that spans languages and platforms, custom JavaScript syntax for the imports themselves, a set of different HTML tags for actually getting them into your page (script, link, etc.), and JSON for mapping names to URLs. Great. Super simple.
ESM has always suffered from a weird shifting argument. Whenever you point out that the performance and security isn't great, all of a sudden it's a developer ergonomics feature, not meant to be for production. Then when you point you the ergonomics suck (like not being able to do bare imports), you're told you should be using it in tandem with a toolchain that auto-generates import maps or something. So what's the benefit? I already had a tool that could do that, and it worked in every browser and didn't ignore the the security advancements we made 10 years ago.
- MatthewPhillips 3y agoYou really don't have to worry about these things until your module graph is > 100 modules. You are falling into "all or nothing" thinking here, because you need tools in some use-cases doesn't mean that a feature is worthless. Not needing tools for small projects and then needing them for large projects is pretty good tradeoff.
- tolmasky 3y agoI'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.