WICG / import-maps

How to control the behavior of JavaScript imports
https://html.spec.whatwg.org/multipage/webappapis.html#import-maps
Other
2.65k stars 69 forks source link

Specify package.json path in <script>, and then generate the import map from package.json. #290

Closed grainstackdev closed 1 year ago

grainstackdev commented 1 year ago

My wish is for no build step to be required to make it possible to use node_modules without build tools.

Because NPM and package.json is so common, and because if you are someone in need of import-maps, there is a high likelihood you have a package.json, it would be very convenient if instead of specifying imports in <script type="importmap">, you specify the path to the package.json.

For example, instead of using import-maps:

<script type="importmap">
{
  "imports": {
    "moment": "/node_modules/moment/src/moment.js",
    "lodash": "/node_modules/lodash-es/lodash.js"
  }
}
</script>

You would specify the package.json:

<script type="importmap">
{
  "package.json": "/package.json"
}
</script>

Then, the browser will fetch the file at that location, and then will do what is necessary to generate the import map given that package.json.

Implementing this feature in the browser is necessary to making it possible to import bare specifiers without a build step. Currently, there is no way to import from node_modules in the browser. I made web-imports as a solution, but it requires a build step. If import-maps were to be implemented as is, without the ability to specify a package.json which generates the import map, then using import-maps, I would still need a build step, and compared to something like web-imports or webpack, there is no advantage with respect to build process.

domenic commented 1 year ago

A build step to generate your import maps is part of the import maps vision, so I don't think we'll be adding code to browsers to do that for you :)

ljharb commented 1 year ago

Is there anywhere that "you are expected to always have a build step when using import maps" is documented? I'd love to link to that.

FlippieCoetser commented 1 year ago

A build step to generate your import maps is part of the import maps vision, so I don't think we'll be adding code to browsers to do that for you :)

I use import maps without build step by leveraging a NPM post install script to creste or add an appropriate mapping

ljharb commented 1 year ago

That's still a build step, you're just doing it at install instead of at deploy.

grainstackdev commented 1 year ago

Is there a standard import map that would be generated from package.json? I imagine most people are using import maps in order to import from node_modules using bare specifiers. While import maps is flexible to handle use cases outside of this one, it might be worth looking at making the standard solution the most user friendly. I.E. A build step is not needed if you are doing the standard thing. Since some code is already being added to the browser, perhaps a default import map generator from package.json is worth putting into the browser. I feel it would have been a missed opportunity to release import maps without a quick path for the main usage.

grainstackdev commented 1 year ago

Sorry, I realized there is a mistake in the initial conception. What I wrote was a dream. To correct that, and to put things into perspective more, here's a scope:

Starting from here:

<script type="importmap">
{
  "imports": {
    "moment": "/node_modules/moment/src/moment.js",
    "lodash": "/node_modules/lodash-es/lodash.js"
  }
}
</script>

It looks like the currently planned implementation for parsing the import map is to look for this importmap script, and then look for the imports key at the top level of the object. The value of imports is itself an object which is passed along to the next part of execution.

To support package.json, this would be intercepted. Instead, if no imports key is found, but there is a package.json key, then a fetch is made to the value of that key which is another resource on the origin server. The contents of package.json come back from the fetch, and then it goes through dependencies, and for each dependency, it creates a new key, value pair in the import map. The key is the name of the dependency, and the value is a path to the dependency's package.json.

When parsing JS files, all imports are to those found in the first package.json, the top-level package.json. When performing import resolution, it would use the import map to resolve the dependency's package.json. A fetch is made, and then once that fetch is done, the dependency's dependencies are added to the import map.

So the import map grows each time a never-before-seen dependency is found, making a bunch of package.json fetches along the way. This is similar to how node_modules grows over time when doing npm install. In the end, both the import map and the node_modules folder are flat lookup tables.

Another note to make; the import map would be a flat object where each key is the package name, and the value is the package.json location. Subpath exports need to be supported, so to get this, when resolving an import statement, only the first part of the import path is used to lookup in the import map for the package.json. Then, import.meta.resolve uses the package.json#exports and the import's full specifier, including the subpath, to resolve the full filepath which is passed to the origin file server, allowing SSG.

I agree. It is quite a bit of code to add to the browser, and all of it is for the goal of there not being any build step needed. But it would be nice to have :D

FlippieCoetser commented 1 year ago

@grainstackdev I agree, adding mappings for installed NPM packages is a common use case. I created several ES6 modules, which I published to NPM and then use those ES6 modules in other ES6 modules as dependencies. I struggled for a long time as I did not want to add bundler to my pipeline. In the end, what I did was leverage NPMs post-install script to inject appropriate mappings into my main HTML file. The post-install script basically does what you described above. Although it might still be considered a build step, it is automated: NPM install will automatically run the post-install script after installing the package. It is by no means perfect and does make a lot of assumptions, but it works. Feel free to take a look: Machine. See the package.json file and the tasks folder to get an idea of how I put everything together. I am sharing this with you since I do not think your feedback here will be considered. I am also open to collaborating and improving the mapping injection. For one, I was thinking of using an external importmap and not injecting the mappings into a HTML file. Anyway, I'm just trying to be constructive here. Good Luck and Happy coding!