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

Feature: Expose resolve function #1652

Closed
lukeed opened this issue Oct 2, 2021 · 2 comments · Fixed by #1881
Closed

Feature: Expose resolve function #1652

lukeed opened this issue Oct 2, 2021 · 2 comments · Fixed by #1881

Comments

@lukeed
Copy link
Contributor

lukeed commented Oct 2, 2021

Hey,

Similar to formatMessages (#1058 🙇) would it be possible to expose esbuild's resolver logic?

Currently, this is useful when writing an ESM loader hook. Node allows the developer to override/determine what file to load. It passes down its own conditions array, allowing you to modify its array and/or override it.

If esbuild exposed this method, we could forward Node's conditions list and also make use of the resolveExtensions option. Even using something like my resolve.exports pkg as a stand-in, a developer would still have to reimplement the extensions logic.

Imagine the API would look something like this, taking just a subset of the existing options:

export function resolve(ident: string, importer: string, options: {
  // picked from BuildOptions, no changes
  conditions?: string[];
  resolveExtensions?: string[];
  mainFields?: string[];
  absWorkingDir?: string;
  nodePaths?: string[];
}): Promise<string | void>;
// absolute file path
// or `node:<module>` for builtins
// or void for no match
@evanw
Copy link
Owner

evanw commented Oct 17, 2021

See also #641, which is about exposing this to plugins. I'm not sure that I would ever expose an API like this for independent use since that's not the purpose of esbuild. However, you can already do this with a hack (modified from #641 (comment)):

const esbuild = require('esbuild')

async function esbuildResolveImport(id, {
  conditions,
  resolveExtensions,
  mainFields,
  nodePaths,
  absWorkingDir = process.cwd(),
} = {}) {
  let _resolve
  const resolvedPromise = new Promise((resolve) => (_resolve = resolve))
  return Promise.race([
    resolvedPromise,
    esbuild
      .build({
        sourcemap: false,
        write: false,
        bundle: true,
        logLevel: 'silent',
        conditions,
        resolveExtensions,
        mainFields,
        absWorkingDir,
        nodePaths,
        entryPoints: [id],
        plugins: [
          {
            name: 'esbuildResolveImport',
            setup(build) {
              build.onLoad({ filter: /.*/ }, ({ path }) => {
                id = path
                _resolve(id)
                return { contents: '' }
              })
            }
          }
        ]
      })
      .then(() => id)
  ])
}

(async () => {
  for (const conditions of [[], ['node', 'module'], ['node', 'require'], ['node', 'import']]) {
    console.log(conditions, '=>', await esbuildResolveImport('uuid', { conditions }))
  }
})()

Here's what this prints:

[] => ./node_modules/uuid/dist/esm-browser/index.js
[ 'node', 'module' ] => ./node_modules/uuid/dist/esm-node/index.js
[ 'node', 'require' ] => ./node_modules/uuid/dist/index.js
[ 'node', 'import' ] => ./node_modules/uuid/wrapper.mjs

@lukeed
Copy link
Contributor Author

lukeed commented Dec 23, 2021

Glad some work has been done here! However this request was for a top-level resolve export so I don't have to spin up a fake builder w/ a proxy plugin.

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 a pull request may close this issue.

2 participants