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

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

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 7
  • Comments: 23 (9 by maintainers)

Most upvoted comments

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

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

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.

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