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

externalize all but one package? #2811

Closed
mayank99 opened this issue Jan 10, 2023 · 7 comments
Closed

externalize all but one package? #2811

mayank99 opened this issue Jan 10, 2023 · 7 comments

Comments

@mayank99
Copy link

The packages: 'external' option is super handy to externalize all packages, but I would like to add one or two packages as exceptions to this.

I tried using regex and complex globs in external but it doesn't work.

Would be nice if there was something like Vite's noExternal property.

esbuild.buildSync({
  //...
  packages: 'external',
  noExternal: [
    'dont-externalize-pkg',
    // ...
  ]
})
@evanw
Copy link
Owner

evanw commented Jan 10, 2023

That's just what the --packages=external flag does, sorry. The flag means "make any import path external that looks like a package path in the source code."

The --packages=external flag only exists for convenience. You can accomplish what it does with more verbose means such as using --external: or writing a plugin. If you want to do something more custom than what it does, then you'll need to do something more verbose.

@mayank99
Copy link
Author

Thanks for the response. I tried writing this plugin:

{
  name: 'externalize-everything-except-nanoid',
  setup(build) {
    build.onResolve({ filter: /^(?!(nanoid)$).*$/ }, (args) => {
      return { path: args.path, external: true };
    });
  },
}

but I get this error:

 "onResolve" filter is not a valid Go regular expression: "^(?!(nanoid)$).*$

it's a valid expression in javascript at least:
image

is there a way to evaluate the regex in JS before passing it to Go?

@mayank99
Copy link
Author

I was able to make some progress by running javascript inside the plugin.

{
  name: 'externalize-everything-except-nanoid',
  setup(build) {
    build.onResolve({ filter: /(.*)/ }, (args) => {
      if (args.kind === 'import-statement') {
        return { path: args.path, external: /^(?!(nanoid)$).*$/.test(args.path) };
      }
    });
  },
}

but if I do that, it seems like it forgets how to resolve npm packages?

Plugin "externalize-everything-except-nanoid" returned a non-absolute path: nanoid (set a namespace if this is not a file path)

@evanw
Copy link
Owner

evanw commented Jan 10, 2023

{
  name: 'externalize-everything-except-nanoid',
  setup(build) {
    build.onResolve({ filter: /(.*)/ }, (args) => {
      if (args.kind === 'import-statement' && args.path !== 'nanoid') {
        return { path: args.path, external: true };
      }
    });
  },
}

If you return anything, then you're overriding esbuild. You need to not return something to let esbuild do its stuff.

@mayank99
Copy link
Author

thanks, got this to work!

function externalizeAllPackagesExcept(noExternals: string[]) {
  return <esbuild.Plugin>{
    name: 'noExternal-plugin',
    setup(build) {
      if (noExternals.length > 0) {
        build.onResolve({ filter: /(.*)/ }, (args) => {
          if (args.kind === 'import-statement' && !noExternals.includes(args.path.split('/')[0])) {
            return { path: args.path, external: true };
          }
        });
      }
    },
  };
}
esbuild.build({
  entryPoints: ['index.js'],
  bundle: true,
  platform: 'node',
  plugins: [externalizeAllPackagesExcept(['nanoid', 'slash', /* ... */])],
});

@cullylarson
Copy link

I'm working with a monorepo and I want one package to build another package's files (because reasons). I used @mayank99 's code as a basis for something more specific. It basically externalized all external packages except for packages from @myProject/. Posting here in case it's helpful to someone else:

// possible locations of `@myProject/` packages
const MY_PROJECT_BASE_PATHS = ["../../../node_modules", "./node_modules"];

function checkFile(filePath) {
  try {
    fs.accessSync(filePath, fs.constants.F_OK);

    return filePath;
  } catch (error) {
    return undefined;
  }
}

function getAbsoluteMyProjectPath(importPath) {
  for (const basePath of MY_PROJECT_BASE_PATHS) {
    const absolutePath = path.resolve(basePath, importPath + ".ts");

    if (checkFile(absolutePath)) {
      return absolutePath;
    }
  }

  return undefined;
}

// We don't want esbuild to bundle anything in node_modules except for
// `@myProject/` packages. This plugin will externalize all node_modules packages
// except for `@myProject/` packages.
function externalizePackagesExceptMyProject() {
  return {
    name: "noExternal-plugin",
    setup(build) {
      build.onResolve({ filter: /(.*)/ }, (args) => {
        console.log(args);
        if (args.kind === "import-statement" && args.path[0] !== ".") {
          if (args.path.startsWith("@myProject/")) {
            const myProjectPath = getAbsolutemMyProjectPath(args.path);

            if (myProjectPath) {
              return {
                path: myProjectPath,
                external: false,
              };
            }
          }

          return { path: args.path, external: true };
        }
      });
    },
  };
}

@mayank99
Copy link
Author

mayank99 commented Nov 6, 2023

fwiw i ended up making some changes to my previous code and published it as a package. https://github.com/mayank99/esbuild-plugin-noexternal

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

No branches or pull requests

3 participants