WICG / import-maps

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

Question: How are relative extension-less specifiers resolved inside a package? #201

Closed nalply closed 4 years ago

nalply commented 4 years ago

Edit: s/bare/extension-less/

First let me give a detailed example:

{
    "imports": {
        "some_pkg": "/node_modules/some_pkg/a.js",
        "some_pkg/b": "/node_modules/some_pkg/b.js"
    }
}

The web server delivers b.js for the URL http://example.com/node_modules/some_pkg/b.js.

Should a.js be able to import b.js with this setup? Or did I miss something?

Sorry if this is a noob question, however for a project of mine with an import map, the application is trying to load http://example.com/node_modules/some_pkg/b (on macOS Google Chrome Canary version 81.0.4020.0 with experimental flag enabled).

domenic commented 4 years ago

Relative specifiers are resolved as URLs, so the result you're observing in Chrome matches the spec. To make use of a bare specifier mapping like you have in your map, you need to use bare specifiers in your import statement.

Hope this helps!

nalply commented 4 years ago

Yes, this is what I have done, see "some_pkg/b": "/node_modules/some_pkg/b.js and import * as b from './b' in a.js, however Chrome doesn't load with the extension.

Could you give an universal explanation how bare relative specifiers inside packages are resolved?

domenic commented 4 years ago

You can read the spec for a universal explanation.

Because you are using ./, it will not apply your mappings, because your mappings are bare specifier mappings, but bare specifiers do not start with ./

nalply commented 4 years ago

Thanks for the clarification, I see now my mistake. What I meant is extension-less relative specifiers. Sorry for the confusion.

However I still don't see how to apply an import map to relative extension-less imports inside a package. Is this possible at all?

nalply commented 4 years ago

Closing this because I found another way to map extension-less specifiers: let the webserver redirect to the file with the extension instead.

domenic commented 4 years ago

To remap URLs, you need to specify the full URL, not a bare specifier. E.g.

{
    "imports": {
        "/node_modules/some_pkg/b": "/node_modules/some_pkg/b.js"
    }
}
nalply commented 4 years ago

Thanks for helping me to understand import map better! However after rereading the spec, especially the section https://github.com/WICG/import-maps#extension-less-imports, I feel that handling extension-less specifiers in the import map is not a good idea (combinatorial explosion). My small experimental project already has more than 100 mappings, and I have just begun.

Tarp.require inspired me to let the web server help resolving, because the web server already knows about the files it serves (details: https://github.com/letorbi/tarp.require#http-redirects). It doesn't have to be redirects (in fact I plan to let just deliver the source without any redirects to save round-trip time).

Edit: In fact I think now that a hot dev server should do both: generate an import map (walk through dependencies, read their package.json files and evaluate the "main", "files" and "exports" fields to get mappings) but also handle some typical cases (like extension-less specifiers) directly.

domenic commented 4 years ago

I would suggest instead just including the file extensions in your import statements. Making your users sit through a server round trip for the 100+ files is going to be a bad experience.

jkrems commented 4 years ago

let the webserver redirect to the file with the extension instead.

For future reference: This may not do what most people are expecting. Redirects may lead to two different instances of the "same" module, executing the module multiple times. Because module execution is determined by the URL before redirects happen.

Example:

// target.mjs
console.log('target executed');

// app.mjs
import './target';
import './target.mjs';

If /target redirects to /target.mjs, the console statement will be logged twice. If target.mjs exports symbols / classes / etc. there will be two conflicting versions of those. So generally it's not a great developer experience to redirect and it may lead to subtle bugs.

As @domenic wrote above, the most reliable option is to add extensions to imports.

nalply commented 4 years ago

Thanks for the head-ups. This seems a lot more complicated than I thought.