evanw / esbuild

An extremely fast bundler for the web
https://esbuild.github.io/
MIT License
38.22k stars 1.16k forks source link

Plugin callback for unresolved imports #3907

Closed commonuserlol closed 2 months ago

commonuserlol commented 2 months ago

How I can replace unresolved import (e.g. node's fs with polyfill) when bundling for browser? My current code:

build.onResolve({ filter: new RegExp(`^import .*${module}`) }, async () => {
                    const result = await build.resolve(`${module}`, {
                        kind: "import-statement",
                        resolveDir: dir
                    });
                    if (result.errors.length > 0) return { errors: result.errors };

                    return { path: result.path, external: true };
                });
hyrious commented 2 months ago

You can use --alias:fs=browserify-fs to replace modules without using plugins.

commonuserlol commented 2 months ago

This would require a huge cmdline in my case. The alias in esbuild option also exist, but it would use module from local node_modules right? I need to use specified path instead

hyrious commented 2 months ago

In that case you can make use of the "browser" field of package.json to guide esbuild to choose a shim file. This is also documented.

"browser": {
  "fs": "./shims/fs.js"
}
commonuserlol commented 2 months ago

No, the reason why I'm using esbuild instead of package.json - I don't want to paste that to every project. I need import replacement exactly with esbuild

hyrious commented 2 months ago

This would require a huge cmdline in my case.

It won't be less code than the huge cmdline if you want to handle every unresolved case. If your target is only node builtins, the list at least is enumerable and things can be done with well-known polyfills.

If you want to handle every unresolved module, the first task is to find these modules' names -- by bundling it once. You will get errors with contents like [ERROR] Could not resolve "fs" and just extract the module name from that message:

build({
  entryPoints: ['...'],
  bundle: true,
  write: false,
  logLevel: 'silent'
}).then(() => {
  console.log('no unresolved imports')
}, (err) => {
  let unresolved = new Set(), m
  for (let {text} of err.errors) {
    if (m = text.match(/^Could not resolve "([^"]+)"$/)) {
      unresolved.add(m[1])
    }
  }
  console.log({ unresolved })
})

Then in the main bundling process you can match them with onResolve({ filter: /^{moduleName}$/ }) and do the replace work.

commonuserlol commented 2 months ago

What? I'm already have a list of modules, the issue - onResolve is not called when import is unresolved. What I need is replace node builtin with polyfill (what my code snippet in first message supporsed to do).

hyrious commented 2 months ago

onResolve is not called when import is unresolved

What? Maybe you're just using wrong filter.. Try

{ filter: new RegExp(`^${module}$`) }

The filter regex is used to match the import module's name, not the whole statement.

commonuserlol commented 2 months ago

Well, ^${module}$ is worked, but it doesn't get bundled like with --alias.

hyrious commented 2 months ago

It doesn't because you returned external: true.

commonuserlol commented 2 months ago

Ohh my fault. Thanks for help, everything is replaced now