evanw / esbuild

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

re-create package.json with only externalized dependencies #3744

Open macrozone opened 6 months ago

macrozone commented 6 months ago

Problem 1:

i am using esbuild with externalized packages:

esbuild ./src/index.ts --bundle --outfile=./dist/index.js --platform=node --keep-names --packages=external

externalizing packages is needed because some modules may rely on a certain folder structure and may not work correctly when bundled.

Local dependencies in a monorepo get bundled with this, which is nice.

However you still need to install certain packages, but if you use the original package.json, you may have local dependencies there that can't be installed after bundling with esbuild.

Problem 2

When you just externalize some packages and bundle others, you still have a package.json that contains all dependencies.

Solution:

JQuezada0 commented 3 days ago

I've run into this a few times as well and finally made a plugin for it, here's the link if helpful.

Unfortunately I don't think there's a way to know the full list of dependencies that were externalized (for ex. by other plugins), so it only uses whatever is passed to esbuild's build function external property manually.

hyrious commented 3 days ago

Usually for Node.js users the devDependencies are ok to be bundled in and dependencies peerDependencies are externalized because they will be installed by npm when used in production (i.e. being used as a dependency of other project). Therefore, it is a common practice to only externalize these packages:

external: Object.keys({
  ...pkg.dependencies,
  ...pkg.peerDependencies
})

Sometimes this external list works the same as --packages=external. This needs the author to check out.

This way, you can just use the same package.json as you write. That's why tsup does this by default.

On the other hand, you can also run a simple scanner on the final build to collect external deps like using esm-module-lexer or with a custom esbuild plugin that gather args.path with args.kind != entry-point in the onResolve() callback.

JQuezada0 commented 3 days ago

@hyrious The concern with using the package.json as is, is that it means potentially including a lot of code that'll never get used. In the case of something like a serverless bundle, the size starts to matter eventually. The optimal goal becomes targeting ESM, bundling as much as possible, installing the bare minimum external modules needed only for those that would otherwise break the bundle, and then code-splitting on intentional async dynamic imports.

This was my use-case at least, the final package size is a lot smaller than it would be compared to externalizing all production dependencies, but sentry's profiling integration and a few other indirect dependencies don't behave properly if not in their original directory structure.

hyrious commented 3 days ago

@JQuezada0 What you said doesn't conflict with my comment. If including some dependencies into the bundle is by purpose, the common practice is just move those dependencies to devDependencies. Then on the server it runs npm i --omit=dev to not install dev dependencies (which are already in the bundle).

JQuezada0 commented 3 days ago

@hyrious ohhh sorry I misunderstood, good idea! Definitely simpler