Skip to content

Ambiguous steps in PACKAGE_IMPORTS_EXPORTS_RESOLVE reference documentation #53206

Description

@Mrtenz

Affected URL(s)

https://nodejs.org/api/esm.html#resolution-algorithm-specification

Description of the problem

I was looking at the ESM resolution algorithm documentation, and stumbled upon this:

PACKAGE_IMPORTS_EXPORTS_RESOLVE:

  1. [...]
  2. Let expansionKeys be the list of keys of matchObj containing only a single "*", sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity.
  3. [...]

(2) could be interpreted in several ways, including:

  1. List of keys of matchObj, sorted, filtered by keys containing only a single "*".
  2. List of keys of matchObj, filtered by keys containing only a single "*", sorted. I think the end result is the same in this case, though.
  3. List of keys of matchObj, where the value contains only a single "*", sorted by keys.

Assuming (1) or (2) here is correct, the implementation of PATTERN_KEY_COMPARE seems odd:

[...]
3. Let baseLengthA be the index of "" in keyA plus one, if keyA contains "", or the length of keyA otherwise.
4. Let baseLengthB be the index of "" in keyB plus one, if keyB contains "", or the length of keyB otherwise.
[...]
7. If keyA does not contain "", return 1.
8. If keyB does not contain "
", return -1.
[...]

From my understanding, neither keyA nor keyB can not contain a "*" at this point since it's filtered out. I looked at implementations of the module resolution, like Webpack's enhanced-resolve, and it seems like previously these imports or exports could end with a / instead of containing a *:

https://github.com/webpack/enhanced-resolve/blob/e38970852a7f89b694f1053e285195511764a900/lib/util/entrypoints.js#L314-L322

However, this does not seem to work in the current Node.js version (tested on v20.12.2 at least), and is not described in the reference of PACKAGE_IMPORTS_EXPORTS_RESOLVE. Are the docs outdated? Is there some ambiguity here? Or am I simply misunderstanding the docs?

Activity

  1. added
    docIssues and PRs related to Node.js documentation.
    on May 29, 2024
  2. added
    esmIssues and PRs related to the ECMAScript Modules implementation.
    loadersIssues and PRs related to ES module loaders.
    on May 29, 2024
  3. avivkeller commented on May 30, 2024

    @avivkeller
    Member

    @nodejs/loaders

    (BTW 1 & 2 return the same result)

  4. Mrtenz commented on May 30, 2024

    @Mrtenz
    ContributorAuthor

    (BTW 1 & 2 return the same result)

    I figured that as well after writing it. 😅 Still, there is some ambiguity here, since it could also be interpreted as (3).

  5. aduh95 commented on May 30, 2024

    @aduh95
    Contributor

    I personally don’t find it confusing (we’re talking about the key, that would make little sense to involve the value at this point), and even if you misinterpret it, it wouldn’t be too bad because the value would also typically contain a single *. As always, PRs welcome to clarify the docs.

  6. Mrtenz commented on May 30, 2024

    @Mrtenz
    ContributorAuthor

    Fair enough. But if it's the case that the key always contains a single "*", the reference of PATTERN_KEY_COMPARE seems to have unnecessary steps. Happy to open a PR to update this.

  7. aduh95 commented on May 30, 2024

    @aduh95
    Contributor

    Let me rephrase that: value associated with keys that contains a single * char will typically also contain a single * char. Keys do not always contain a single * char.

    Example of a valid "exports" map:

    {
      "exports": {
        "./someKey/noStar": "./someFile.mjs",
        "./somePatternKey/*": "./someDir/*.mjs"
    
      }
    }
  8. Mrtenz commented on May 30, 2024

    @Mrtenz
    ContributorAuthor

    I understand that some keys don't have a "*", but the reference for PACKAGE_IMPORTS_EXPORTS_RESOLVE mentions:

    Let expansionKeys be the list of keys of matchObj containing only a single "*"

    So I assume an implementation for this in JS could be:

    const expansionKeys = Object.keys(matchObj).filter(key => count(key, "*") === 1);

    After which expansionKeys is sorted by PATTERN_KEY_COMPARE. Yet PATTERN_KEY_COMPARE includes cases for when a key does not contain "*", which is never the case, right? It's never called for keys that don't contain a "*".

    Object.keys(matchObj)
      .filter(key => count(key, "*") === 1)
      .sort(PATTERN_KEY_COMPARE); // The keys here always contain a "*"
  9. aduh95 commented on May 30, 2024

    @aduh95
    Contributor

    Ah I see, sorry for the misunderstanding! I think that's a relic from a time where there was another another way to define subpath patterns which was removed in #40121, and I guess PATTERN_KEY_COMPARE was never updated, good catch. If you want to PR a fix, that would be much appreciated.

  10. Mrtenz commented on May 30, 2024

    @Mrtenz
    ContributorAuthor

    Opened a PR here: #53215.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to Node.js documentation.esmIssues and PRs related to the ECMAScript Modules implementation.loadersIssues and PRs related to ES module loaders.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions