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

[Bug]: esbuild-bundled script crashes with ENOENT (related to vm2?) #10352

Closed
2 tasks
karlhorky opened this issue Jun 8, 2023 · 23 comments
Closed
2 tasks

[Bug]: esbuild-bundled script crashes with ENOENT (related to vm2?) #10352

karlhorky opened this issue Jun 8, 2023 · 23 comments

Comments

@karlhorky
Copy link
Contributor

karlhorky commented Jun 8, 2023

Bug expectation

I expected to be able to bundle my Puppeteer script with esbuild

My esbuild-bundled Puppeteer script failed (see full error output below) because of the transitive dependency on vm2 (via puppeteer-core -> proxy-agent -> pac-proxy-agent -> pac-resolver -> degenerator -> vm2)

$ node abc.mjs
node:fs:601
  handleErrorFromBinding(ctx);
  ^

Error: ENOENT: no such file or directory, open '/home/runner/work/project/project/bridge.js'
    at Object.openSync (node:fs:601:3)
    at Object.readFileSync (node:fs:469:35)
    at ../../node_modules/@puppeteer/browsers/node_modules/vm2/lib/vm.js (file:///home/runner/work/project/project/abc.mjs:52297:66)
    at __require2 (file:///home/runner/work/project/project/abc.mjs:20:50)
    at ../../node_modules/@puppeteer/browsers/node_modules/vm2/lib/main.js (file:///home/runner/work/project/project/abc.mjs:54169:9)
    at __require2 (file:///home/runner/work/project/project/abc.mjs:20:50)
    at ../../node_modules/@puppeteer/browsers/node_modules/vm2/index.js (file:///home/runner/work/project/project/abc.mjs:54197:22)
    at __require2 (file:///home/runner/work/project/project/abc.mjs:20:50)
    at ../../node_modules/@puppeteer/browsers/node_modules/degenerator/dist/index.js (file:///home/runner/work/project/project/abc.mjs:54211:17)
    at __require2 (file:///home/runner/work/project/project/abc.mjs:20:50) {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: '/home/runner/work/project/project/bridge.js'
}

Node.js v18.16.0

It appears to be related to this current problem with bundling vm2:

This has also been reported in the repos for proxy-agent:

Seems like the first version that this happens with is puppeteer-core@20.4.0, which upgraded @puppeteer/browsers from 1.3.0 to 1.4.0, which added proxy-agent.

Bug behavior

  • Flaky
  • PDF

Minimal, reproducible example

Bundle any Puppeteer script such as the simple script below via esbuild like this:

$ esbuild index.ts --bundle --outfile=abc.mjs --platform=node --target=node18 --format=esm --banner:js=\"import { createRequire as createRequire99 } from 'module'; import { dirname as dirname99 } from 'node:path'; import { fileURLToPath as fileURLToPath99 } from 'node:url'; const __filename = fileURLToPath99(import.meta.url); const __dirname = dirname99(__filename); const require = createRequire99(import.meta.url);\"

index.ts

import puppeteer from 'puppeteer';

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export async function puppeteerWorkflow(urlToTest: string) {
  const browser = await puppeteer.launch({
    ...(process.platform === 'linux'
      ? {
          executablePath: '/usr/bin/google-chrome-stable',
        }
      : {
          // If we're not on Linux, then maybe we're in development,
          // where we don't want a headless browser (we want to see what
          // is going on)
          headless: false,
        }),
  });
  const page = await browser.newPage();

  await page.goto(urlToTest);
  await wait(5000);
  await page.close();
  await browser.close();
}

puppeteerWorkflow('https://example.com');

Error string

ENOENT: no such file or directory, open '/home/runner/work/project/project/bridge.js'

Puppeteer configuration

No response

Puppeteer version

20.5.0

Node version

18.16.0

Package manager

pnpm

Package manager version

8.6.1

Operating system

macOS

@karlhorky karlhorky added the bug label Jun 8, 2023
@karlhorky karlhorky changed the title [Bug]: [Bug]: esbuild-bundled script crashes with ENOENT (related to vm2?) Jun 8, 2023
@github-actions
Copy link

github-actions bot commented Jun 8, 2023

This issue was not reproducible. Please check that your example runs locally and the following:

  • Ensure the script does not rely on dependencies outside of puppeteer and puppeteer-core.
  • Ensure the error string is just the error message.
    • Bad:

      Error: something went wrong
        at Object.<anonymous> (/Users/username/repository/script.js:2:1)
        at Module._compile (node:internal/modules/cjs/loader:1159:14)
        at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
        at Module.load (node:internal/modules/cjs/loader:1037:32)
        at Module._load (node:internal/modules/cjs/loader:878:12)
        at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
        at node:internal/main/run_main_module:23:47
    • Good: Error: something went wrong.

  • Ensure your configuration file (if applicable) is valid.
  • If the issue is flaky (does not reproduce all the time), make sure 'Flaky' is checked.
  • If the issue is not expected to error, make sure to write 'no error'.

Once the above checks are satisfied, please edit your issue with the changes and we will
try to reproduce the bug again.


Analyzer run

@cnlewis3
Copy link

cnlewis3 commented Jun 8, 2023

Having this same issue when trying to deploy to our AWS Lambda. Worked fine with previous versions, but getting a ENOENT: no such file or directory, open '/var/task/bridge.js which we have also narrowed down to vm2, which is only in the dependency tree through puppeteer-core for us.

@Lightning00Blade
Copy link
Collaborator

Hey I tried to reproduce but I keep getting this error:

Error: Dynamic require of "stream" is not supported

Would it be possible to provide a repo with the issue you are seeing?

@karlhorky
Copy link
Contributor Author

karlhorky commented Jun 9, 2023

To be clear, you're seeing the following error?

$ node abc.mjs
file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:13
  throw new Error('Dynamic require of "' + x + '" is not supported');
        ^

Error: Dynamic require of "stream" is not supported
    at file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:13:9
    at node_modules/.pnpm/ws@8.13.0/node_modules/ws/lib/stream.js (file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:11585:22)
    at __require2 (file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:19:50)
    at node_modules/.pnpm/ws@8.13.0/node_modules/ws/wrapper.mjs (file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:15071:29)
    at __init (file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:16:56)
    at node_modules/.pnpm/puppeteer-core@20.4.0_typescript@5.1.3/node_modules/puppeteer-core/lib/esm/puppeteer/common/NodeWebSocketTransport.js (file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:15096:5)
    at __init (file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:16:56)
    at file:///Users/k/p/puppeteer-esbuild-vm2/abc.mjs:79315:1
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

I'm working on making a reproduction repo that shows the no such file or directory error.

@karlhorky
Copy link
Contributor Author

karlhorky commented Jun 9, 2023

Sorry, this error was actually related to globals not available in esbuild by default, I forgot to copy in the banner that is required.

Here's a new reproduction (run pnpm build and node abc.mjs) - I've also updated the description above:

https://github.com/karlhorky/puppeteer-esbuild-vm2-reproduction

@OrKoN
Copy link
Collaborator

OrKoN commented Jun 12, 2023

As you pointed out the problem is in the vm2 module as it requires __dirname. For example, in Webpack it means __dirname: true needs to be set. I am not sure if the corresponding feature is available in esbuild but you can probably safely exclude the module from the bundler as it is only required by the installation.

@karlhorky
Copy link
Contributor Author

__dirname is not the issue here, that is already fixed using the --banner option:

https://github.com/karlhorky/puppeteer-esbuild-vm2-reproduction/blob/99d9478af4896c735eba19e1faad3fbba397927c/package.json#L6

@karlhorky
Copy link
Contributor Author

The issue is that vm2 expects that there are extra files which it can fs.readFileSync at runtime:

Error: ENOENT: no such file or directory, open '/home/runner/work/project/project/bridge.js'
    at Object.openSync (node:fs:601:3)
    at Object.readFileSync (node:fs:469:35)

@OrKoN
Copy link
Collaborator

OrKoN commented Jun 12, 2023

@karlhorky I believe it is the issue https://github.com/patriksimek/vm2/blob/1663f231ec02db473ed5b743e3b91b8a2ffc7982/lib/vm.js#L145 the __dirname in the esbuild-produced bundle appears to be incorrect

@OrKoN
Copy link
Collaborator

OrKoN commented Jun 12, 2023

The issue is that vm2 expects that there are extra files which it can fs.readFileSync at runtime:

yeah it expects the node_modules to be around and the __dirname to point to the vm2's lib folder.

@karlhorky
Copy link
Contributor Author

karlhorky commented Jun 12, 2023

The way that we're bundling + deploying, we do not have a node_modules folder anymore which contains bridge.js (similar approach to a Single Executable Application, except we still use Node.js)

So anyone taking a similar approach with their bundling will also have this issue

@karlhorky
Copy link
Contributor Author

karlhorky commented Jun 12, 2023

Workaround 1

A possible (ugly) workaround would be to require all of your users who want to bundle to copy the necessary files out from Puppeteer's transitive dependency vm2, similar to the approach described here:

1. add post build in NPM script
"build": "webpack --config ./webpack.prod.config.js && node prepare-package.js postBuild",

2. copy files from node_modules to output directory after build in prepare-package.js
const postBuild = async () => {
  await cpvm2("bridge.js");
  await cpvm2("events.js");
  await cpvm2("setup-sandbox.js");
  await cpvm2("setup-node-sandbox.js");
};

3. add them in package.json files property to bundle them

@karlhorky
Copy link
Contributor Author

But maybe Puppeteer could do something about this so that bundling is zero-config 🤞

@OrKoN
Copy link
Collaborator

OrKoN commented Jun 12, 2023

@karlhorky you can probably work around it by specifying --external:vm2 as you are unlikely to need those files at runtime.

@karlhorky
Copy link
Contributor Author

karlhorky commented Jun 12, 2023

Workaround 2

Confirmed, using the esbuild --external flag with --external:vm2 also works for us:

"build": "esbuild index.ts --bundle --external:vm2 --outfile=abc.mjs --platform=node --target=node18 --format=esm --banner:js=\"import { createRequire as createRequire99 } from 'module'; import { dirname as dirname99 } from 'node:path'; import { fileURLToPath as fileURLToPath99 } from 'node:url'; const __filename = fileURLToPath99(import.meta.url); const __dirname = dirname99(__filename); const require = createRequire99(import.meta.url);\""

Description of the --external flag:

You can mark a file or a package as external to exclude it from your build. Instead of being bundled, the import will be preserved (using require for the iife and cjs formats and using import for the esm format) and will be evaluated at run time instead.

I guess this may not work for everyone, but represents a simpler workaround for the ENOENT: no such file or directory ... bridge.js errors with bundling Puppeteer with esbuild

@tranlehaiquan
Copy link

I add external and got this error, I'm using puppeteer-core 20.7.4

image

@gabrielo91
Copy link

Having the exact same issue, we are using node v16.17.0 and puppeteer 20.7.4, we run and API which calls the function that runs const browser = await puppeteer.launch();, however we get the exact same error: Error: ENOENT: foo/dist/apps/api/bridge.js. We run the project using nx, just like this:

npx nx run api:serve

@gabrielo91
Copy link

How we solve was changing build section in project.json, just removed the "externalDependencies": "none" option

@PluviaCon
Copy link

Workaround 2

Confirmed, using the esbuild --external flag with --external:vm2 also works for us:

"build": "esbuild index.ts --bundle --external:vm2 --outfile=abc.mjs --platform=node --target=node18 --format=esm --banner:js=\"import { createRequire as createRequire99 } from 'module'; import { dirname as dirname99 } from 'node:path'; import { fileURLToPath as fileURLToPath99 } from 'node:url'; const __filename = fileURLToPath99(import.meta.url); const __dirname = dirname99(__filename); const require = createRequire99(import.meta.url);\""

Description of the --external flag:

You can mark a file or a package as external to exclude it from your build. Instead of being bundled, the import will be preserved (using require for the iife and cjs formats and using import for the esm format) and will be evaluated at run time instead.

I guess this may not work for everyone, but represents a simpler workaround for the ENOENT: no such file or directory ... bridge.js errors with bundling Puppeteer with esbuild

I've also encountered the same issue and have tried both methods. It seems that this problem occurs in versions above 20.0. Temporarily switching to version 19.10.0 seems to work for now.

@edw1882
Copy link

edw1882 commented Jul 12, 2023

Workaround 1

A possible (ugly) workaround would be to require all of your users who want to bundle to copy the necessary files out from Puppeteer's transitive dependency vm2, similar to the approach described here:

1. add post build in NPM script
"build": "webpack --config ./webpack.prod.config.js && node prepare-package.js postBuild",

2. copy files from node_modules to output directory after build in prepare-package.js
const postBuild = async () => {
  await cpvm2("bridge.js");
  await cpvm2("events.js");
  await cpvm2("setup-sandbox.js");
  await cpvm2("setup-node-sandbox.js");
};

3. add them in package.json files property to bundle them

A more straightforward approach if you use CopyPlugin in your webpack is

new CopyPlugin({
		patterns: [
			{ from: '../node_modules/vm2/lib/bridge.js', to: 'server' },
			{ from: '../node_modules/vm2/lib/events.js', to: 'server' },
			{ from: '../node_modules/vm2/lib/setup-node-sandbox.js', to: 'server'	},
			{ from: '../node_modules/vm2/lib/setup-sandbox.js', to: 'server' },
		],
}),

@OrKoN
Copy link
Collaborator

OrKoN commented Jul 13, 2023

fixed via #10548

@OrKoN OrKoN closed this as completed Jul 13, 2023
@karlhorky
Copy link
Contributor Author

karlhorky commented Jul 13, 2023

Nice, thanks for circling back around here @OrKoN! 🙌 (and for making the PR)

I can confirm that building with esbuild works after:

  1. upgrading to puppeteer-core@20.8.2 (which uses @puppeteer/browsers@1.4.5 internally)
  2. removing Workaround 2 (the --external:vm2 flag)

@gabrielo91
Copy link

thanks so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants