Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for custom resolvers #251

Merged
merged 11 commits into from
Oct 13, 2023
Merged

Conversation

iambumblehead
Copy link
Owner

@iambumblehead iambumblehead commented Oct 12, 2023

per discussion iambumblehead/resolvewithplus#58, this PR adds support for using different resolvers with esmock. Specifically, a yarn PnP resolver be specified, so that esmock would yarn PnP module resolution rather npm.

const modulePnP = await esmock('../src/modulePnP.js', {
  '../src/moduleIdChildPnP.js' : () => ['a', 'b']
}, null, {
  resolver: pnpapi.resolveRequest
})

@iambumblehead iambumblehead force-pushed the add-support-for-optional-resolvers branch from efbc5e2 to c4c5825 Compare October 12, 2023 21:19
@iambumblehead
Copy link
Owner Author

When the pipelines pass ... lets consider the changes a bit before merging.

Instead of a list of resolvers, a single resolver definition is probably sufficient, eg,

const modulePnP = await esmock('../src/modulePnP.js', {
  '../src/moduleIdChildPnP.js' : () => ['a', 'b']
}, null, {
-  resolvers: [ pnpapi.resolveRequest ],
+  resolver: pnpapi.resolveRequest
})

also, the configured resolver should probably fully replace resolvewithplus, so that resolvewithplus is never called when a custom resolver is defined.

@iambumblehead iambumblehead requested a review from koshic October 12, 2023 23:09
@iambumblehead
Copy link
Owner Author

The added test is a little cryptic and only runs in recent node environments where module.register is defined, but maybe this is sufficient. @koshic what do you think? Please review.

@iambumblehead
Copy link
Owner Author

@koshic you have more skill with typescript than I do and I would be glad if you would update the type definitions file

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

@koshic you have more skill with typescript than I do and I would be glad if you would update the type definitions file

Sure

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

Type definitions diff:

// or export it at the end of file
export type Resolver = (id: string, parent: string) => string

type Options = {
  strict?: boolean | undefined,
  purge?: boolean | undefined,
  isModuleNotFoundError?: boolean | undefined,
  resolver?: Resolver | undefined
}

For PnP it works like that:

import { pathToFileURL } from "node:url";
import { type Resolver } from "esmock";

const resolver: Resolver = (id, parent) =>
  pathToFileURL(pnpapi.resolveRequest(id, parent)).href; // esmock works with file URLs internally, so...

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

@iambumblehead or you prefer that I make changes in the branch?

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

Custom resolver should process the following special module ids:

  • builtin Node module id -> should return id as is or 'node:' + id
  • 'import' -> should return null (or other falsy) value.

It's ok for first implementation, but in the future we should remove such unnecessary calls - in my understanding, resolver is responsible to resolve only things can be resolved.

@iambumblehead
Copy link
Owner Author

agreed and thank you

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

@iambumblehead fixed resolver return type. I'm moving my project to new esmock version in parallel, and found that mistake.

@iambumblehead
Copy link
Owner Author

@koshic thanks for helping me! esmock is updated so that the resolver is not called with builtin moduleIds

@iambumblehead
Copy link
Owner Author

changes here might be "ready"

I can't think of anything else to change or add

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

changes here might be "ready"

Let me to update my local project and re-run all tests with that version, it will take ~15m

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

@iambumblehead works fine, thx! lets merge it

do you plan to publish new version today?

@koshic koshic merged commit d3ca335 into main Oct 13, 2023
8 checks passed
@koshic koshic deleted the add-support-for-optional-resolvers branch October 13, 2023 18:16
@iambumblehead
Copy link
Owner Author

@koshic the resolver spec does specify handling of builtin module names https://nodejs.org/api/esm.html#resolution-algorithm-specification

PACKAGE_RESOLVE(packageSpecifier, parentURL)
...
3. If packageSpecifier is a Node.js builtin module name, then
. 1. Return the string "node:" concatenated with packageSpecifier.

iow the resolver should be called with builtin moduleIds

@iambumblehead
Copy link
Owner Author

@koshic yes let's publish and we could publish again any follow up changes we want

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

@koshic the resolver spec does specify handling of builtin module names https://nodejs.org/api/esm.html#resolution-algorithm-specification

Good catch ) So, we should answer to the following question: should esmock have some optimizations based on internal knowledge and skip useless queries?

My opinion - why not? I'm trying to imagine situation when we may need to process builtins differently, but without success.

@iambumblehead
Copy link
Owner Author

[email protected] is published.

If your resolver is later updated to handle core modules, I would be interested to remove iscoremodule condition from esmock. This way, esmock would be as flexible as possible for any resolver that might be used.

iscoremodule(id) ? addprotocolnode(id) : opt.resolver(id, parent)

psuedo-code using node:module isBuiltin

import { isBuiltin } from 'node:module'

const protocolNodeRe = /^node:/
const addprotocolnode = p => protocolNodeRe.test(p) ? p : `node:${p}`

const resolver = (moduleId, parent) => {
  if (isBuiltin(id)) return addprotocolnode(id)
  // ...
}

@iambumblehead
Copy link
Owner Author

Good catch ) So, we should answer to the following question: should esmock have some optimizations based on internal knowledge and skip useless queries?

I don't have a strong opinion actually. maybe we should leave esmock as it is following this PR and not make any changes...

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

I removed that code (4 lines., hehe) right after you make "do not call custom resolver with builtin moduleId" commit ))

BTW, I'll make PR to the wiki to describe how to use custom resolver with esmock & yarn pnp

@iambumblehead
Copy link
Owner Author

I suppose the best reasons to remove the iscoremodule condition would be

  • slightly simplifies esmock, esmock calls the resolver with any moduleId
  • anticipates that the resolver is spec compliant and handles builtin moduleIds

@iambumblehead
Copy link
Owner Author

I removed that code (4 lines., hehe) right after you make "do not call custom resolver with builtin moduleId" commit ))

ok did not see that

@koshic
Copy link
Collaborator

koshic commented Oct 13, 2023

  • anticipates that the resolver is spec compliant and handles builtin moduleIds

It makes sense... So if we both have concerns about 'too smart esmock', let's remove that magic. For me, It's better to redo some work than keep some non-ideal code.

@iambumblehead
Copy link
Owner Author

ok will make a PR shortly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants