Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active April 4, 2023 20:42
Show Gist options
  • Save domenic/f2a0a9cb62d499bcc4d12aebd1c255ab to your computer and use it in GitHub Desktop.
Save domenic/f2a0a9cb62d499bcc4d12aebd1c255ab to your computer and use it in GitHub Desktop.
import.meta.resolve() mini-explainer

import.meta.resolve() mini-explainer

import.meta.resolve() is a new function that allows resolving a given module specifier. It was originally proposed and discussed in whatwg/html#3871, resulting the spec PR whatwg/html#5572.

In more detail, import.meta.resolve(specifier), returns the URL to which specifier would resolve in the context of the current script. That is, it returns the URL that would be imported if you did import(specifier). It will throw an exception if an invalid specifier is given (including one not mapped to anywhere by an import map).

The main use case for this is to get URLs relative to the current script, or relative to a current page. For example:

// Inside https://example.com/my/deep/path/app.mjs
const url = import.meta.resolve("./foo.txt");
// -> "https://example.com/my/deep/path/foo.txt"

or

const url = import.meta.resolve("somepackage/resource.json");
// -> "https://example.com/node_modules/somepackage/lib/resource.json", based on
// an import map that sends "somepackage/" to "https://example.com/node_modules/somepackage/lib/".

This makes it easier to write module code that does not depend on exactly where the current module is located, or how an application's imports have been set up.

Relationship to other features

import.meta.url

import.meta.url is an existing feature that gives the URL of the current script. In simple cases, where specifier is "relative URL-like" (e.g. "./foo.mjs"), import.meta.resolve(specifier) is equivalent to

(new URL(specifier, import.meta.url)).href

However, this is not true for bare specifiers (like "foo"), or when import maps are involved.

Import maps

Import maps allow controlling module specifier resolution for a given page. This includes remapping bare specifiers, like "foo" or "foo/bar", to URLs.

import.meta.resolve() helps make import mappings more usable for other parts of the application, which want to get mapped URLs but don't necessarily want to import() them.

Design decisions

Sync vs. async

The issue and pull request contain some debate about whether this function should be synchronous or asynchronous. @domenic believes synchronous is the correct choice for any kind of resolution procedure, whether it be URL resolution or module specifier resolution. That is, in general when resource importing is involved, resolving an input string into a URL is a synchronous process, whereas fetching that URL is asynchronous.

Various people arguing for an async version indicated that it might be useful for the function to account for concerns like HTTP redirects or waiting for import maps to load. @domenic believes this is the wrong layering and does not match how URL resolution works for the rest of the web platform. URL resolution (whether starting with a module specifier, or starting with a relative URL string) is a point-in-time synchronous question. The answer can change over time (due to import maps, or <base> elements, or...). But if you want to wait for a given change to the resolution process, you should do so and then perform the synchronous resolution process. There's no need to make the resolution process itself async and put the waiting inside of that.

Node.js in particular seemed interested in allowing arbitrary async developer code to run as part of the resolution process, and indeed already ships a "Stability 1: Experimental" async version of import.meta.resolve(). Allowing such arbitrary code during resolution is not a good idea on the web. Node.js is discussed changes to their architecture or APIs which would allow them to align with the web and be sync in this regard. Based on this change it appears they are already executing on that plan, and thus aligning with the synchronous hook. Meanwhile another prominent server-side runtime, Deno, has implemented the sync import.meta.resolve().

Other pieces of evidence supporting the decision for this to be sync:

  • A comment from a WebKit engineer about how they started with async resolution in their internal (non-web-exposed) module code, but eventually changed it to be synchronous.

  • The JavaScript specification hook HostResolveImportedModule is synchronous, indicating that TC39 expects module resolution to be synchronous. Similarly the early-stage TC39 compartments proposal has a synchronous resolveHook.

Name

The name resolve() has been long-favored by the module community for this operation, going all the way back to the AMD and CommonJS projects, and continuing into the popular Node.js module system.

In web specs (not APIs), we mostly talk about "module specifier resolution" (in the JavaScript specification), but also "URL parsing" (in the URL standard). In web APIs, no particular verb is used for URL parsing today: we instead use new URL(relativeURL, baseURL). So using "resolve" for this type of operation is somewhat novel, but it doesn't necessarily set a precedent for URL parsing in general, just for module specifier resolution.

Another name proposed was resolveURL(), back when this feature was just sugar for (new URL(specifier, import.meta.url)).href. With its new more general resolution capabilities, that name no longer seems as good.

Other names were proposed in an attempt to avoid conflicting with Node.js's async import.meta.resolve(), but evaluated independently from that they do not seem as good.

Security and privacy

This feature has no security or privacy implications. Questionnaire answers:

What information does this feature expose, and for what purposes? No information beyond what the website itself put there.

Do features in your specification expose the minimum amount of information necessary to implement the intended functionality? Yes.

Do the features in your specification expose personal information, personally-identifiable information (PII), or information derived from either? No.

How do the features in your specification deal with sensitive information? They do not.

Do the features in your specification introduce state that persists across browsing sessions? No.

Do the features in your specification expose information about the underlying platform to origins? No.

Does this specification allow an origin to send data to the underlying platform? No.

Do features in this specification enable access to device sensors? No.

Do features in this specification enable new script execution/loading mechanisms? No.

Do features in this specification allow an origin to access other devices? No.

Do features in this specification allow an origin some measure of control over a user agent’s native UI? No.

What temporary identifiers do the features in this specification create or expose to the web? None.

How does this specification distinguish between behavior in first-party and third-party contexts? It does not.

How do the features in this specification work in the context of a browser’s Private Browsing or Incognito mode? The same as in normal mode.

Does this specification have both "Security Considerations" and "Privacy Considerations" sections? This is a small pull request to the larger HTML Standard, which has its own security and privacy considerations.

Do features in your specification enable origins to downgrade default security protections? No.

How does your feature handle non-"fully active" documents? The same as fully active ones.

What should this questionnaire have asked? Existing questions seem fine for this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment