esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

Built-in dependencies working with --bundle --platform=node sometimes. Suggested regex to work

Open admartinez-edicom opened this issue 1 year ago • 1 comments

I'm trying to use esbuild in order to bundle into a single file our production node code. However we are having some troubles with the built-in modules of node (http, https and fs but I believe it will happen with all of them).

After analyzing the generated bundle I came up with a very silly solution that is applying this regex to the final bundle: sed 's/moduleName = filename;/moduleName = filename;\\n basedir = \\x27core:\\x27;/g'

While writing this issue we realize that it happens sometimes but I cannot determinate the reason.

I have created a repo with a sample code that behaves similar to my production code.

There are 3 files in this repo and 4 scripts:

  • app-ok1.ts --> starts elastic-apm, it works perfectly after being bundled with esbuild. npm run build-ok1 && node app-ok1.js
  • app-ok2.ts --> starts an express server, it works perfectly after being bundled with esbuild. npm run build-ok2 && node app-ok2.js
  • app-ko.ts --> it has the same code of both files, it fails when running the bundled file. I provide 2 scripts:
    • npm run build-ko && node app-ko.js --> it fails
    • npm run build-ko-with-fix && node app-ko-with-fix --> it applies the regex I provide and the app works perfectly

The error when failing is:

Error: Cannot find module '/home/admartinez/Desktop/esbuild/modules/http.js' Require stack: - /home/admartinez/Desktop/esbuild/app-ko.js at Module._resolveFilename (node:internal/modules/cjs/loader:1140:15) at Module._load (node:internal/modules/cjs/loader:981:27) at Module.require (node:internal/modules/cjs/loader:1231:19) at Module.patchedRequire (/home/admartinez/Desktop/esbuild/app-ko.js:16470:39) at Hook._require.Module.require (/home/admartinez/Desktop/esbuild/app-ko.js:16438:31) at require (node:internal/modules/helpers:177:18) at Instrumentation._patchModule (/home/admartinez/Desktop/esbuild/app-ko.js:22303:23) at /home/admartinez/Desktop/esbuild/app-ko.js:22243:24 at Module.patchedRequire (/home/admartinez/Desktop/esbuild/app-ko.js:16546:32) at Hook._require.Module.require (/home/admartinez/Desktop/esbuild/app-ko.js:16438:31) at require (node:internal/modules/helpers:177:18) at node_modules/methods/index.js (/home/admartinez/Desktop/esbuild/app-ko.js:61564:16) at __require (/home/admartinez/Desktop/esbuild/app-ko.js:11:50) at node_modules/express/lib/router/route.js (/home/admartinez/Desktop/esbuild/app-ko.js:61611:19) at __require (/home/admartinez/Desktop/esbuild/app-ko.js:11:50) at node_modules/express/lib/router/index.js (/home/admartinez/Desktop/esbuild/app-ko.js:61734:17) at __require (/home/admartinez/Desktop/esbuild/app-ko.js:11:50) at node_modules/express/lib/application.js (/home/admartinez/Desktop/esbuild/app-ko.js:64408:18) at __require (/home/admartinez/Desktop/esbuild/app-ko.js:11:50) at node_modules/express/lib/express.js (/home/admartinez/Desktop/esbuild/app-ko.js:66164:17) at __require (/home/admartinez/Desktop/esbuild/app-ko.js:11:50) at node_modules/express/index.js (/home/admartinez/Desktop/esbuild/app-ko.js:66230:23) at __require (/home/admartinez/Desktop/esbuild/app-ko.js:11:50) at Object. (/home/admartinez/Desktop/esbuild/app-ko.js:66236:30) at Module._compile (node:internal/modules/cjs/loader:1364:14) at Module._extensions..js (node:internal/modules/cjs/loader:1422:10) at Module.load (node:internal/modules/cjs/loader:1203:32) at Module._load (node:internal/modules/cjs/loader:1019:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12) at node:internal/main/run_main_module:28:49 { code: 'MODULE_NOT_FOUND', requireStack: [ '/home/admartinez/Desktop/esbuild/app-ko.js' ] }

I came up with this solution after realizing that when the variable basediris empty, the generated file use the current working directory to resolve modules. So in the "core-module-branch" I set this variable to 'core:'

if (core === true) { if (hasWhitelist === true && modules.includes(filename) === false) { debug("ignoring core module not on whitelist: %s", filename); return exports3; } moduleName = filename; basedir = 'core:'; // new code } else if (hasWhitelist === true && modules.includes(filename)) { const parsedPath = path.parse(filename); moduleName = parsedPath.name; basedir = parsedPath.dir; } else {

Could you please give me any feedback? We try the --target argument with all sorts of value but all of them generate the same code

admartinez-edicom avatar Aug 20 '24 15:08 admartinez-edicom

That error was because elastic-apm-node adds a custom hook to replace some builtin and thrid-party modules. It redirects the require call to modules like http and replace that with the file under itself. This code pattern is not bundle-able since it relies on a real file structure during runtime.

Your ok1 script works because it just doesn't hit those hooks. However, when express imports http, it hits and cannot find a ./modules/http.js relative to the bundled file.

In summary, you'd better not bundle elastic-apm-node with either --external:elastic-apm-node or --packages=external.

hyrious avatar Aug 20 '24 23:08 hyrious

I'm closing this as this sounds like it's out of scope. Some packages aren't bundler-friendly, and it's not esbuild's responsibility to try to bundle them anyway. Potential solutions are to not bundle them (either omit all dependencies with --packages=external or omit individual dependencies with --external) or to work with the package author to make their package bundler-friendly.

evanw avatar Dec 20 '24 02:12 evanw