lukeed / resolve.exports

A tiny (952b), correct, general-purpose, and configurable `"exports"` and `"imports"` resolver without file-system reliance
MIT License
368 stars 15 forks source link

Advanced Wildcard Handling #21

Closed gpickell closed 1 year ago

gpickell commented 2 years ago

Related issue: #9

lukeed commented 1 year ago

Hey, thanks, but what you're calling x*y and x*y*z isn't actually supported by Node.js nor the algorithm. Verified this with Node 18.x and 19.4:

{
  "name": "foo",
  "exports": {
    // any value variation
    "./features/*/*/index": "./import/*/dist/*.mjs"
  }
}
import * as foo from 'foo/features/foo/bar/index';
console.log({ foo });
//=> Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './features/foo/bar/index' is not defined by "exports" in /.../node_modules/foo/package.json imported from /.../test.mjs

Perhaps it was supported at one time, but as is/has been the case a few times, it must have changed 🙃

For now, at least, only 1 * may be present in the entry name, whose matched substring may be repeated as many times as defined in the resolved value. For example:

pkg = {
  name: "hello",
  exports: {
    "./features/*": "./build/esm/*/spacer/*.mjs"
  }
};

resolve(pkg, "hello/features/foo/bar");
//=> "./build/esm/foo/bar/spacer/foo/bar.mjs"

What does currently need fixing is:

  1. repeating the *-matched substring as many times as referenced in the target value

    9

  2. matching the * character anywhere within the entry name, not just at the end (which used to be the only supported pattern, lol)

    22

As a significant portion of this PR appears to be here for /*/*/ support, I'm going to close this & lean on the other PRs to solve both of these.

Sorry for the long delay, but thank you!

gpickell commented 1 year ago

I'm confused because I just replicated it again in a bare mono repo: https://github.com/gpickell/wildcards-in-exports-demo

works in node v18.12.1 and v19.4.0 (remember to run yarn):

lukeed commented 1 year ago

It works because your target directory structure matches the import statements in a way that the structure is reassembled correctly based on 1st replacement. It's not working because it's matching 2 * characters:

// Real:
// node_modules/package-a/dist/folder/spacer/sub/index.mjs
// node_modules/package-a/dist/folder/index.mjs

Patterns: {
  "./*/index": "./dist/*/index.mjs",
  "./*/*/index": "./dist/*/spacer/*/index.mjs"
}

// Imports
// ---

import "package-a/folder/index"; // "*" 
// matches "./*/index" ~> "*" = "folder" ~> "./dist/<folder>/index.mjs"

import "package-a/folder/spacer/sub/index";
// ALSO matches "./*/index" ~> "*" = "folder/spacer/sub" ~> "./dist/<folder/spacer/sub>/index.mjs"

If you remove the "./*/index" export pattern (and the "package-a/folder/index" import, which we agree is meant to match that export) then you'll see that the second "./*/*/index" export pattern correctly throws an error:

// Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './folder/spacer/sub/index' is not defined by "exports" in /.../node_modules/package-a/package.json imported from /.../index.mjs

This is all because the "./*/index" pattern is present, was a match, and the resolver algorithm didn't find any other (valid) longer matches, so kept "./*/index" as the candidate. You can swap the order of your package-a exports and see the same thing happen, since pattern-order doesn't matter.

gpickell commented 1 year ago

Yup. Fears alleviated. I agree with the above synopsis.