vercel / pkg

Package your Node.js project into an executable

Home Page:https://npmjs.com/pkg

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Puppeteer support?

astefanutti opened this issue · comments

I've been able to package an application that requires Puppeteer. However the package application is unable to spawn the Chromium instance process from the bundled Chromium binary:

events.js:182
      throw er; // Unhandled 'error' event
      ^

Error: spawn /snapshot/decktape/node_modules/puppeteer/.local-chromium/mac-494755/chrome-mac/Chromium.app/Contents/MacOS/Chromium ENOENT
    at exports._errnoException (util.js:1026:11)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:189:19)
    at onErrorNT (internal/child_process.js:366:16)
    at _combinedTickCallback (internal/process/next_tick.js:102:11)
    at process._tickCallback (internal/process/next_tick.js:161:9)
    at Function.Module.runMain (pkg/prelude/bootstrap.js:1282:13)
    at startup (bootstrap_node.js:200:16)
    at bootstrap_node.js:617:3

Pointing to an external Chromium binary works.

So it seems something similar to #147 is required.

@igorklopov , thanks for the quick patch.

Unfortunately, running a pkg packaged puppeteer based script, on Ubuntu 16.04 failed with the following assertion error:
AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run npm install

Any ideas how to solve this?

Did you follow the warning? Please post pkg output during compilation.

No errors whatsoever:

┌─[11:40:30]─[hemulin@neon-machine] [scripts U5]
└─[.../neon-crawlers/src/js]─> pkg main.js 
> pkg@4.2.4
> Targets not specified. Assuming:
  node8-linux-x64, node8-macos-x64, node8-win-x64
┌─[11:40:42]─[hemulin@neon-machine] [scripts U8]
└─[.../neon-crawlers/src/js]─> ./main-linux 
{ AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run npm install
    at Console.assert (console.js:165:23)
    at Function.launch (/snapshot/js/node_modules/puppeteer/lib/Launcher.js:0:0)
    at Function.launch (/snapshot/js/node_modules/puppeteer/lib/Puppeteer.js:0:0)
    at __dirname (/snapshot/js/main.js:0:0)
    at Object.<anonymous> (/snapshot/js/main.js:0:0)
    at Module._compile (pkg/prelude/bootstrap.js:1226:22)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
  generatedMessage: false,
  name: 'AssertionError [ERR_ASSERTION]',
  code: 'ERR_ASSERTION',
  actual: false,
  expected: true,
  operator: '==' }

Tried to run the test you added to the patch

Running directly with node had no problem:

┌─[11:50:42]─[hemulin@neon-machine] [scripts U12]
└─[.../neon-crawlers/src/js]─> node test.js 
ok

But got the same issue when packaged:

┌─[11:50:33]─[hemulin@neon-machine] [scripts U9]
└─[.../neon-crawlers/src/js]─> pkg test.js 
> pkg@4.2.4
> Targets not specified. Assuming:
  node8-linux-x64, node8-macos-x64, node8-win-x64
┌─[11:50:39]─[hemulin@neon-machine] [scripts U12]
└─[.../neon-crawlers/src/js]─> ./test-linux 
(node:32440) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run npm install
(node:32440) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

You should see a warning that asks you to ship .local-chromium directory as puppeteer next to executable. But i don't see that warning in your compilation output.

Not sure what you mean.
I've copied the .local-chromium folder from the node_modules/puppeteer/.local-chromium up the tree and put it in the root of the project, next to the main js files.
renamed it to puppeteer, packaged again and nothing changed. Still the same errors

Tree presentation of the folder:

┌─[12:22:13]─[hemulin@neon-machine] [scripts U13]
└─[.../neon-crawlers/src/js]─> tree -L 1
.
├── main.js
├── main-linux
├── main-macos
├── main-win.exe
├── node_modules
├── package.json
├── package-lock.json
├── puppeteer
├── test.js
├── test-linux
├── test-macos
└── test-win.exe

I'm having the same issue as @hemulin . When trying to run the app:

❯ ./app-macos
(node:48332) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run "npm install"
(node:48332) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Output when compiling:

❯ pkg app.js
> pkg@4.2.4
> Targets not specified. Assuming:
  node8-linux-x64, node8-macos-x64, node8-win-x64

/www/pkg-critical-2
❯ echo $status
0

I've copied the node_modules/puppeteer/.local-chromium as ./puppeteer in the pkg root.

❯ tree -L 1
.
├── app-linux
├── app-macos
├── app-win.exe
├── app.js
├── node_modules
├── package.json
├── puppeteer
└── yarn.lock

2 directories, 7 files

/apps/pkg-critical-2
❯ cd puppeteer/

/apps/pkg-critical-2/puppeteer
❯ tree -L 1
.
└── mac-497674

1 directory, 0 files

I wonder if there is something weird going on with the dynamic directory names for the .local-chromium contents. It seems the directory name mac-497674 changes based off of version.

But, I'm not seeing anything hardcoded for it anywhere in the code base, so, it most likely handles this automatically.

After many attempts, I was able to find a work-around incase anyone needs a temporary solution:

var path = require('path');
if (process.pkg) {
  var puppeteer = require(path.resolve(process.cwd(), 'puppeteer'));
} else {
  var puppeteer = require('puppeteer');
}

In this solution, just put a copy of puppeteer next to your executable. It's not pretty but I hope it helps someone!

Hi @tferullo - Can you please share a sample repo? I still cannot get it working in Windows.

image

EDIT: Works now after using pkg -t node8-win32 package.json to compile along with tferullo's workaround.

@igorklopov I got the same issue, spawn chromium fail after pkg.

download chromium from

const downloadURLs = {
  linux: 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip',
  mac: 'https://storage.googleapis.com/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
  win32: 'https://storage.googleapis.com/chromium-browser-snapshots/Win/%d/chrome-win32.zip',
  win64: 'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/%d/chrome-win32.zip',
};

such as 499413, then

childProcess.spawn(chromiumPath, chromiumArgs)

got

spawn /snapshot/page2pdf/node_modules/puppeteer/.local-chromium/linux-499413/chrome-linux/chrome ENOENT

I have the same problem. Has anyone got a solution? Possibly for electron-builder..

I'm not sure where to write this but I've found an issue with integrating pkg and puppeteer. The puppeteer API has a method page.evaluate, accepting a function to be executed in the context of the page. This entails serializing the function and passing it over the socket to the Chromium process, to executed there. Implementation wise puppeteer calls toString() on said function.

But since pkg compiles scripts using v8::ScriptCompiler the sources are not kept. This means f.toString() will return something like 'function f() { [native code] }' to be passed over the socket. This will obviously fail with a rather cryptic error message: Error: Evaluation failed: SyntaxError: Unexpected identifier One workaround is to only pass strings to be evaluated as expressions to page.evaluate instead.

There is an EcmaScript stage-3 proposal that makes it mandatory to return source code in toString() for any function implemented in Javascript:
http://2ality.com/2016/08/function-prototype-tostring.html

I am getting the following error, any idea what does it mean?

> pkg@4.3.0-beta.1
> Targets not specified. Assuming: node8-linux-x64, node8-macos-x64, node8-win-x64
> Warning Cannot include directory %1 into executable. 
The directory must be distributed with executable as %2. 
  node_modules\puppeteer\.local-chromium
  path-to-executable/puppeteer

@jsappme It means that you have to copy the folder node_modules\puppeteer\.local-chromium to the folder where your executable (the file generated by pkg) is and rename that folder into puppeteer

But I still didn't succeed to use puppeteer with pkg. I get this error when I'm executing my binary:

{ Error: Cannot find module './lib/Puppeteer'
1) If you want to compile the package/file into executable, please pay attention to compilation warnings and specify a literal in 'require' call. 2) If you don't want to compile the package/file into executable and want to 'require' it from filesystem (likely plugin), specify an absolute path in 'require' call using process.cwd() or process.execPath.

I'm still investigating

Final update:
Ok I was wrong, you can just include the code using scripts option like:

"pkg": {
    "scripts": "node_modules/puppeteer/lib/*.js"
  }

But as @doomdavve say a lot of puppeteer function will not work with pkg as the code get compiled and so not usable with evaluate.

So I think we can't use puppeteer with pkg for now

I tried all the solutions above but I am still getting:


> Warning Cannot include directory %1 into executable.
  The directory must be distributed with executable as %2.
  node_modules\puppeteer\.local-chromium
  path-to-executable/puppeteer

here is my packages:

{
  "name": "htest",
  "license": "ISC",
  "bin": "index.js",
  "start": "micro index.js",
  "dependencies": {
    "express": "4.15.2",
    "puppeteer": "^0.13.0"
  },
  "pkg": {
    "scripts": "node_modules/puppeteer/lib/*.js",
    "assets": [
      "views/**/*"
    ],
    "targets": [
      "node8-win32"
    ]
  }
}

@doomdavve can you give an example of what you mean by the potential work around that you list?

commented

This works for me

{
  "pkg": {
    "scripts":  "node_modules/puppeteer/lib/*.js"
  }
}
cp -R ./node_modules/puppeteer/.local-chromium ./puppeteer

Solution from @reggi will partially work, but function like the one to inject CSS in the page will not, because the code that needs to be injected will be compiled and so not injectable anymore

Like here https://github.com/GoogleChrome/puppeteer/blob/master/lib/FrameManager.js#L463

commented

@Uhsac, @reggi
I setup like you did with the copy of the chromium to local source tree
But it still throws the exception.

Could you please explain a bit more in detail how to get this working?

"pkg": {
    "scripts": "node_modules/puppeteer/lib/*.js",
    "assets": [
      "views/**/*"
    ],
    "targets": [
      "node8-win32"
    ]
  }

my build.sh

cp -R ./node_modules/puppeteer/.local-chromium ./puppeteer
pkg index.js --out-path ../build

Change your build.sh to:

pkg index.js --out-path ../build
cp -R ./node_modules/puppeteer/.local-chromium ../build/puppeteer

@voordev If you want to build a Windows build, you need to copy the Windows version of node_modules\puppeteer\.local-chromium, not the one you have on your development platform.

I am getting Error: Evaluation failed: SyntaxError: Unexpected identifier when I try to use page.setContent(). This only happens with the pkg'd application, not when I run it via node main.js. The strangest thing is that there is no JavaScript at all in my HTML, and the CSS is inline.

My code looks something like this:

  const browser = await Puppeteer.launch()
  const page = await browser.newPage()
  console.log(await browser.version()) // "HeadlessChrome/64.0.3264.0"
  await page.setContent(html) // this line results in the error

Anyone have an idea what is going wrong?

Okay, removing the "scripts": "node_modules/puppeteer/lib/*.js" from package.json, and modifying node_modules/puppeteer/index.js to require with the literal string (require('./lib/Puppeteer')) made everything work using pkg@4.2.5 and puppeteer@0.13.0.

So as a workaround for JS folks, you could do this:

const puppeteer = require('./node_modules/puppeteer/lib/Puppeteer')

I have yet to find a decent workaround that would also work nicely with the TypeScript typings.

@ohanhi Hey doing it your way I still get this issue

{ AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run "npm install"

Where do you put the .local-chromium folder so puppeteer is able to find it?

@Uhsac, your directory structure should look like this:
screen shot 2018-01-09 at 15 31 43

that is, copy .local-chromium to folder where your build is located and rename it to puppeteer

@ohanhi path without ./node_modules/ works too:

const puppeteer = require('puppeteer/lib/Puppeteer')
commented

Also getting the UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run "npm install" when trying to run the compiled osx binary.

I followed all instructions above, is this a PATH issues? Since everything is compiled into 1 binary we do not need any external dependencies right?

Anyone have a fully working example?

commented

Can anyone please update on this?
Im getting the same error as before with the latest chromium version.
The problem is we need a binary to minimise the dependency installation; also we would like to attach the latest nodejs version since or host OS does not support this version.

Has anyone got this working?

@voordev: I am using this workaround:

const isPkg = typeof process.pkg !== 'undefined'
const chromiumExecutablePath = (isPkg
  ? puppeteer.executablePath().replace(
      /^.*?\/node_modules\/puppeteer\/\.local-chromium/,
      path.join(path.dirname(process.execPath), 'chromium')
    )
  : puppeteer.executablePath()
)
puppeteer.launch({ executablePath: chromiumExecutablePath })

then, copy contents of node_modules/puppeteer/.local-chromium folder to chromium folder, next to your binaries.

for example:

project/
├── build/
│   ├── app-linux
│   ├── app-macos
│   ├── app-win.exe
│   ├── chromium/
│       ├── mac-526987/
├── index.js
├── package.json

you can automatize this with scripts field in package.json:

"scripts": {
  "build": "rm -rf build && pkg . --out-path build",
  "postbuild": "cp -R ./node_modules/puppeteer/.local-chromium build/chromium"
},
commented

@dodas
Thank you for the explanation
Could you tell me how you install the chromium?
Running on a Mac I Did a
npm install chromium this results however only into some .js files installed in node_modules path.

I used this before

cp -R ./node_modules/puppeteer/.local-chromium ./puppeteer

I guess the latter is correct with path modification in your example code.
Kind regards

@voordev you don't need to manually install "chromium".
Chromium is downloaded when you install puppeteer package, you only need to copy it from puppeteer folder to your build folder.
However, puppeteer only downloads chromium build for your current platform. To download builds for all supported platforms, you could use puppeteer/lib/Downloader class, which offers convenient methods to do that.

So you can use your cp ... command unmodified, but change the path from chromium to puppeteer in executablePath:

const isPkg = typeof process.pkg !== 'undefined'
const chromiumExecutablePath = (isPkg
  ? puppeteer.executablePath().replace(
      /^.*?\/node_modules\/puppeteer\/\.local-chromium/,
      path.join(path.dirname(process.execPath), 'puppeteer') // <--- folder name here
    )
  : puppeteer.executablePath()
)
puppeteer.launch({ executablePath: chromiumExecutablePath })
commented

Im getting this error

pkg/prelude/bootstrap.js:1243
      return wrapper.apply(this.exports, args);
                     ^

ReferenceError: path is not defined
    at Object.<anonymous> (/snapshot/source/index.js:0:0)
    at Module._compile (pkg/prelude/bootstrap.js:1243:22)
    at Object.Module._extensions..js (module.js:650:10)
    at Module.load (module.js:558:32)
    at tryModuleLoad (module.js:501:12)
    at Function.Module._load (module.js:493:3)
    at Function.Module.runMain (pkg/prelude/bootstrap.js:1298:12)
    at startup (bootstrap_node.js:227:16)
    at bootstrap_node.js:649:3

Copied the puppeteer to the build path; am I missing something

@voordev Yes, you need to require path module. At the top of your file:

const path = require('path')
commented

Thank you @dodas missed that

It now works except it throws this exception:

(node:7378) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run "npm install"
(node:7378) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

It does seem to run some of my code and the Chromium is correctly placed in the build folder, any thoughts what might cause this? Kind regards

Please provide your code and directory structure.

@voordev it worked for me after i checked that puppeteer.launch({ executablePath: chromiumExecutablePath }) was the correct path to my chrome

(checked on windows)

commented

It shows

/srv/app/chromium/linux-515411/chrome-linux/chrome

So I copied the puppeteer chrome-linux path from the node_modules puppeteer installation directory,
towards /srv/app/chromium/linux-515411 but it still fails

(node:8409) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run "npm install"
(node:8409) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
dev-2:build me$ npm update pkg -g

It runs fine from source, shows same path even.. but compiled with pkg it fails on both osx and Mac now...

commented

Anyone could please help me resolving this issue.
The suggested fixes above all tried, adding a local version of chromium to the puppeteer folder.
It works fine if I run it on the non compiled version ( osx ) but as soon as I start the binary build it fails.

The chromium path I put to shell on starting the binary

SOURCE:

/srv/test/source/node_modules/puppeteer/.local-chromium/mac-515411/chrome-mac/Chromium.app/Contents/MacOS/Chromium

exists and running OK from source

BINARY:
Copied the /srv/test/source/puppeteer folder which works for the source version.
The path to chromium exists and is correct, I can just click the path In my osx terminal and it opens the chromium browser.

/srv/test/build/puppeteer/mac-515411/chrome-mac/Chromium.app/Contents/MacOS/Chromium

This throws an:

(node:2816) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded. Run "npm install"
(node:2816) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Why is my binary not finding the chromium while it exists clearly in the defined path?

@voordev We are using a version of what I said in #204 (comment) . This does work.

commented

Managed to fix the issue,
Combination of suggestions above, not a single reply worked for me out of the box.

Now it seems I have resolved the chromium issue but getting an issue

(node:8549) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to launch chrome!
[0227/101846.760203:ERROR:nacl_helper_linux.cc(310)] NaCl helper process running without a sandbox!
Most likely you need to configure your SUID sandbox correctly
commented

Anyone any heads on this regarding Ubuntu? we tried everything and are still not able to get it to work.
We need our compiled application installed and this is causing big time delays. any help much appreciated.

I'm on Ubuntu 17.10. I have tried this with suggestions above and I can run Chromium manually:

"pkg": { "assets": "./node_modules/puppeteer/.local-chromium/*" }

It compiles application with Chromium but still giving this even if I have all dependencies

(node:4270) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to launch chrome! spawn ./node_modules/puppeteer/.local-chromium/linux-536395/chrome-linux/ ENOENT
TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

commented

@anyone

Has anyone got a solution for this?
We need to run our compiled code on our server but this has been bogging us down for 2 weeks

We still have an issue in pkg when specifying page.evaluate() (regardless of options) generates the error: Error: Evaluation failed: SyntaxError: Unexpected identifier, while working without packaging. Is there an expected fix for this? This is a must for use with puppeteer.

commented

Perhaps we can combine forces on how to tackle this but I assume extended knowledge of pkg is needed. We really need to be able to compile and use it with puppeteer asap. Can the author of PKG perhaps help us with this issue?

Im planning on trying an installation on CentOS today. as a temp workaround but its not ideal.
on MacOSX it runs fine compiled. Only Ubuntu fails.

commented

@anyone
got a solution or pinpoint why this only seems to happen on Ubuntu while working fine on OSX?

I'll be taking care of these shortly, with some more updates coming to pkg. Thank you!

commented

@igorklopov
Thank you do you have an ETA for these ubuntu issues on pkg to be resolved?
Thank you for all your efforts regarding this and creating pkg

@voordev Regarding your error NaCl helper process running without a sandbox!. You can add the no-sandbox flags when launching Puppeteer to resolve this.

See puppeteer/puppeteer#290 (comment)

@doomdavve @Uhsac @daihashi @Bars92

I solved the problem with page.evaluate Error: Evaluation failed: SyntaxError: Unexpected identifier, just by putting evaluated code in a separate file, and configuring Pkg to not compile that file.

var eval = require('./eval');
frame.evaluate(eval.func1, arg1);

eval.js:

module.exports = {
    func1: function(arg1) {
        return document.querySelector(arg1)
    }
};

package.json:

"pkg": {
  "assets": "eval.js"
}

@jonlinper it worked! Complicated the application a bit, requires package.json and some structure. Nevertheless it's working and it's plenty enough, thanks!

commented

@abhinavrastogi
Regarding your error NaCl helper process running without a sandbox!. You can add the no-sandbox flags when launching Puppeteer to resolve this.
See puppeteer/puppeteer#290 (comment)

this is already implemented; it does not resolve the issue

commented

Any updates on this?
Anyone managed to get pkg working on ubuntu with puppeteer?
We could increase performance and speed and decrease delays if we could use pkg

commented

Any updates regarding this?

commented

Has anyone ever got this working?
No updates anymore since April.

commented

Using Puppeteer to generate dynamically generated PDFs and pkg to wrap it all up. Ran into all the same issues mentioned here. Got it working by copying chromium up and using @dodas suggestion.
To resolve the evaluate issue we created a loader.js that required all the things we needed in our actual.js and "vm" to run the actual.js in context. We then package up the loader file with pkg allowing us to wrap up all our required modules into a single file (loader.js) and still use page.evaluate in actual.js

defining global.require = require; in the loader.js allows you to "require" modules in actual.js per normal pattern as well

@voordev I have it working great: Pkg on Ubuntu with Puppeteer.
Just using my solution: #204 (comment)

This seems to work when you provide the executable path on launching puppeteer. I managed to get it working by placing the .local-chromium folder inside the generated pkg directory.

main.js

const executablePath = process.pkg ? 
    puppeteer.executablePath().replace(__dirname, '.') :
    puppeteer.executablePath();
const browser = await puppeteer.launch({ executablePath });

The above replace operation is required to remove the /snapshot/<projectname> from the path. Check pkg's Snapshot filesystem

package.json

"bin": {
    "main": "main.js"
},
"scripts": {
    "pkg": "rm -rf ./pkg && pkg -t node8-macos-x64 --out-path pkg .",
    "postpkg": "mkdir -p ./pkg/node_modules/puppeteer && cp -R ./node_modules/puppeteer/.local-chromium ./pkg/node_modules/puppeteer"
},

This has been tested on macos with node8. You might want to modify the target according to the platform.

@dodas Thank you for this, just an FYI for for anyone else using this method - if you're running on Windows change that regex from /^.*?\/node_modules\/puppeteer\/\.local-chromium/ to /^.*?\\node_modules\\puppeteer\\\.local-chromium/ (backslash instead of forward slash)

Any news? I have try @dodas solution, but when i launch await page.evaluate inside my code, the source code work right, instead the exe code that go this error!:
`
\node_modules\puppeteer\lib\ExecutionContext.js:97
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
^

Error: Evaluation failed: SyntaxError: Unexpected identifier
at ExecutionContext.evaluateHandle (\node_modules\puppeteer\lib\ExecutionContext.js:97:13)
at
at process._tickCallback (internal/process/next_tick.js:188:7)

`

@dodas @neontuna Correct. I summarize the solution to code https://github.com/rocklau/pkg-puppeteer.

@rocklau syntax error const double dichiaration.... and on windows the code did't work....

@r1si you could follow this comment. #204 (comment)

commented

@voordev: I am using this workaround:

const isPkg = typeof process.pkg !== 'undefined'
const chromiumExecutablePath = (isPkg
  ? puppeteer.executablePath().replace(
      /^.*?\/node_modules\/puppeteer\/\.local-chromium/,
      path.join(path.dirname(process.execPath), 'chromium')
    )
  : puppeteer.executablePath()
)
puppeteer.launch({ executablePath: chromiumExecutablePath })

then, copy contents of node_modules/puppeteer/.local-chromium folder to chromium folder, next to your binaries.

for example:

project/
├── build/
│   ├── app-linux
│   ├── app-macos
│   ├── app-win.exe
│   ├── chromium/
│       ├── mac-526987/
├── index.js
├── package.json

you can automatize this with scripts field in package.json:

"scripts": {
  "build": "rm -rf build && pkg . --out-path build",
  "postbuild": "cp -R ./node_modules/puppeteer/.local-chromium build/chromium"
},

Can you provide a full code example? With your code, I am getting an error when I run the compiled code.
My code:

const path = require('path')
const isPkg = typeof process.pkg !== 'undefined'
const chromiumExecutablePath = (isPkg
	? puppeteer.executablePath().replace(
		/^.*?\/node_modules\/puppeteer\/\.local-chromium/,
		path.join(path.dirname(process.execPath), 'chromium')
	)
	: puppeteer.executablePath()
);


(async () => {
	const browser = await puppeteer.launch({executablePath: chromiumExecutablePath})
	const page = await browser.newPage();
	await page.goto('https://example.com');
	await page.screenshot({path: 'example.png'});

	await browser.close();
})();

compile:

npx pkg index.js
npx: installed 247 in 12.929s
> pkg@4.3.4
> Targets not specified. Assuming:
  node10-linux-x64, node10-macos-x64, node10-win-x64

run compiled file:

index-win.exe
pkg/prelude/bootstrap.js:1252
      return wrapper.apply(this.exports, args);
                     ^

ReferenceError: puppeteer is not defined
    at Object.<anonymous> (C:\snapshot\pupptr-test\index.js:0:0)
    at Module._compile (pkg/prelude/bootstrap.js:1252:22)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:711:10)
    at Module.load (internal/modules/cjs/loader.js:610:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:549:12)
    at Function.Module._load (internal/modules/cjs/loader.js:541:3)
    at Function.Module.runMain (pkg/prelude/bootstrap.js:1307:12)
    at startup (internal/bootstrap/node.js:274:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:608:3)

what is wrong?

try to reinstall puppeteer

Any update on this? I’d love to be able to bundle the embedded Chromium that Puppeteer downloads on install in my packaged app. Thanks!

I can get chromium to start but it doesn't open any url. It hangs on puppeteer launch. Then if I try to open dev tools in chromium, the launch promise resolves, but still no page. :(

Any ideas anyone?

    "pkg": "rm -rf ./pkg && pkg -t node8-macos-x64 --out-path pkg .",
    "postpkg": "mkdir -p ./pkg/node_modules/puppeteer && cp -R ./node_modules/puppeteer/.local-chromium ./pkg/node_modules/puppeteer"

it works

Is there any way to serialize a function to pass to evaluate page?
I tried two different ways without any luck!

function myFunction(){
return true;
}

const result = await browser.page.evaluate(myFunction.toString());
2.
const result = await page.evaluate('(' + myFunction.toString() + ')();');

I get the following error:

Error: Evaluation failed: SyntaxError: Unexpected identifier
at ExecutionContext.evaluateHandle (/snapshot/node/node_modules/puppeteer/lib/ExecutionContext.js:80:15)
at process._tickCallback (internal/process/next_tick.js:68:7)

Use Following for Windows

"pkg": "pkg -t node12-win-x64 --out-path pkg .",
"postpkg": "xcopy node_modules\\puppeteer pkg\\puppeteer /e /h /y"

use puppeteer-core as a solution?

puppeteer-core + download-chromium work pretty good straightforward. In principle it downloads chromium locally each time when requested

    const download = require('download-chromium');
    const os = require('os');
    const tmp = os.tmpdir();

    const exec = await download({
        revision: 694644,
        installPath: `${tmp}/.local-chromium`})

    const browser = await puppeteer.launch({
        executablePath: exec
    });

How would I use puppeteer-extra?

After following instructions in this thread, I get this...

➜ NODE_OPTIONS='' dist/index
(node:21497) UnhandledPromiseRejectionWarning: Error: Chromium revision is not downloaded. Run "npm install" or "yarn install"
    at Launcher.launch (/snapshot/archive-url-browser/node_modules/puppeteer/lib/Launcher.js:120:15)

Okay I finally got a grip on this problem.

To summarize...

pkg cannot bundle chromium with the executable, so we have to distribute it alongside our app. However, this means we need to tell puppeteer where to find it.

This part of the documentation helped a ton to make sure the solution works when invoked from any directory and when you puppeteer.launch() deep within your codebase.

On the other hand, in order to access real file system at run time (pick up a user's external javascript plugin, json configuration or even get a list of user's directory) you should take process.cwd() or path.dirname(process.execPath).


index.js

const path = require('path');
const puppeteer = require('puppeteer');

// Support for pkg
const executablePath =
  process.env.PUPPETEER_EXECUTABLE_PATH ||
  (process.pkg
    ? path.join(
        path.dirname(process.execPath),
        'puppeteer',
        ...puppeteer
          .executablePath()
          .split(path.sep)
          .slice(6), // /snapshot/project/node_modules/puppeteer/.local-chromium
      )
    : puppeteer.executablePath());

const browser = puppeteer.launch({
  executablePath,
});

To build...

# Compile the app like normal
pkg -t linux --out-path dist index.js

# Copy chromium to the dist folder
cp -R node_modules/puppeteer/.local-chromium dist/puppeteer

To run...

dist/index

this work for me

page.evaluate(`() => (window.__PDF_OPTIONS__)`)
const download = require('download-chromium');
const os = require('os');
const tmp = os.tmpdir();

const exec = await download({
    revision: 694644,// change to 722234
    installPath: `${tmp}/.local-chromium`})

const browser = await puppeteer.launch({
    executablePath: exec
});

It has problem with FileChooser feature, to solve this I recommend use the version that Puppeteer uses when it is installed, actually the version is 722234. Thanks for the workaround it works perfectlly

commented

With the --public argument, it works. (But I am not sure the effect of this argument)
The code like await atag.evaluate( (e) => e.click()); is available to run without error like 'Passed function is not well-serializable'.

So learning from the above and with the official puppeteer documentation, it can be done with puppeteer alone:

// path
import { join } from 'path'
import { tmpdir } from 'os'
const tmpPath = tmpdir()
const chromePath = join(tmpPath, '.local-chromium')

// download browser
import puppeteer from 'puppeteer'
const browserFetcher = puppeteer.createBrowserFetcher({
	path: chromePath,
})
const revisionInfo = await browserFetcher.download('809590')

// create browser
const browser = await puppeteer.launch({
	headless: false,
	executablePath: revisionInfo.executablePath,
})

For the build script I used:

# `bin.mjs` is my entry point, it will be different for you
ncc build bin.mjs -o dist && pkg dist/index.js && ./index-macos

Which installed via:

npm i -g @vercel/ncc
npm i -g pkg

Documentation is here:

Just an update here: This is how I am fetching the chromium versions for each platform. Afterwards, you can copy the chromium distributions like above. My only problem is getting latest revision numbers, yet the resources provided by Google isn't too helpful.

const puppeteer = require("puppeteer");
const path = require("path");

(async () => {
  const operatingSystems = [
    {
      platform: "linux",
      version: 818858,
    },
    // "mac",
    {
      platform: "win64",
      version: 555668,
    },
  ];
  operatingSystems.forEach(async (os) => {
    const f = puppeteer.createBrowserFetcher({
      platform: os.platform,
      path: path.join(__dirname, "dist", "puppeteer"),
    });
    await f.download(os.version);
  });
})();

@zMrKrabz wrote:

My only problem is getting latest revision numbers, yet the resources provided by Google isn't too helpful.

I think this works:

puppeteer._preferredRevision
'818858'

This was the complete process with which I managed to compile my nodejs program (with puppeteer) in pkg:

It is a summary for future readers who encounter the same problems as me and can solve them.

1. Copy the following code (by @dodas) in your program (Must be run before puppeteer.launch()):

 const isPkg = typeof process.pkg !== 'undefined';
 const chromiumExecutablePath = (isPkg
   ? puppeteer.executablePath().replace(
       /^.*?\\node_modules\\puppeteer\\\.local-chromium/,      //<------ That is for windows users, for linux users use:  /^.*?\/node_modules\/puppeteer\/\.local-chromium/ 
       path.join(path.dirname(process.execPath), 'chromium')   //<------ Folder name, use whatever you want
     )
   : puppeteer.executablePath()
 );

And add chromiumExecutablePath to puppeteer.launch():

puppeteer.launch({ executablePath: chromiumExecutablePath });

2. Compile your project.js with: (You can choose another name than "dist", And prefer other distributions (read the pkg documentation))

pkg -t win-x64 --out-path dist project.js --public

--public is important, it serialize the functions passed topage.evaluate(). (Ignore the %1 error during compilation)

3. Copy the folder inside (project)\node_modules\puppeteer\.local-chromium, in my case it's called win64-856583,
inside your binary's folder, create a new folder called "chromium" (or whatever name you chose when pasting the code from step # 1), and put win64-856583 inside it.

You will have a folder structure like the following:

project/
├── build/
│   ├── app-linux
│   ├── app-macos
│   ├── app-win.exe
│   ├── chromium/
│       ├── win64-856583/
├── index.js
├── package.json

4. Run the binary without problems

commented

use puppeteer-core and read chrome executablePath from external config writed by end user ?

commented

My error was:

Could not find expected browser (chrome) locally. Run `npm install` to download the correct Chromium revision

I've solved it

const puppeteer = require('puppeteer');
const chromePath = process.argv[2];

if(!chromePath) {
  console.log("Please provide path to Chrome")
  return
}

process.env.PUPPETEER_EXECUTABLE_PATH = chromePath;

(async () => {
  const browser = await puppeteer.launch({headless: false});
  const page = (await browser.pages())[0];
  await page.goto('https://example.com');
  await page.screenshot({ path: './example.png' });
  await page.waitFor(5000);

  await browser.close();
})()
pkg main.js --out-path ./build
cd ./build
./main-macos /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome

This issue is stale because it has been open 90 days with no activity. Remove the stale label or comment or this will be closed in 5 days. To ignore this issue entirely you can add the no-stale label

This issue is now closed due to inactivity, you can of course reopen or reference this issue if you see fit.

Solved with @FabricioTeran solution #204 (comment).

Take a close look at the package.json scripts and also the browserLauncher.js file

Here's exactly what I did:

package.json

{
  "name": "test-nodejs-pkg",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "main": "src/index.js",
  "bin": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "build": "pkg src/index.js --public --targets node14-win-x64 --out-path dist",
    "postbuild": "cp -R node_modules/puppeteer/.local-chromium dist/.local-chromium"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "puppeteer": "^10.4.0"
  }
}

src/browserLauncher.js

//https://github.com/vercel/pkg/issues/204#issuecomment-822015279
const path = require("path");
const puppeteer = require('puppeteer');


const isPkg = typeof process.pkg !== 'undefined';
const chromiumExecutablePath = (isPkg
  ? puppeteer.executablePath().replace(
    /^.*?\\node_modules\\puppeteer\\\.local-chromium/,      //<------ That is for windows users, for linux users use:  /^.*?\/node_modules\/puppeteer\/\.local-chromium/ 
    path.join(path.dirname(process.execPath), '.local-chromium')   //<------ Folder name, use whatever you want
  )
  : puppeteer.executablePath()
);


async function browserLauncher() {
  const browser = await puppeteer.launch({
    executablePath: chromiumExecutablePath,
    args: ["--start-maximized"],
    headless: false,
    defaultViewport: null
  })
  return browser;
}


module.exports = browserLauncher

src/index.js

const browserLauncher = require("./browserLauncher.js");


(async () => {
  const browser = await browserLauncher();

  const page = (await browser.pages())[0];
  const url = "https://accweb.mouv.desjardins.com/identifiantunique/identification?domaineVirtuel=desjardins&langueCible=fr/"
  const username = "test"
  const password = "123"

  await page.goto(url);
  await page.type('#codeUtilisateur', username, { delay: 0 });
  await page.type('#motDePasse', password, { delay: 0 });
  await page.keyboard.press("Enter")

})()

With this I still get errors in the command line from pkg, but the executable actually works.

So learning from the above and with the official puppeteer documentation, it can be done with puppeteer alone:

// path
import { join } from 'path'
import { tmpdir } from 'os'
const tmpPath = tmpdir()
const chromePath = join(tmpPath, '.local-chromium')

// download browser
import puppeteer from 'puppeteer'
const browserFetcher = puppeteer.createBrowserFetcher({
	path: chromePath,
})
const revisionInfo = await browserFetcher.download('809590')

// create browser
const browser = await puppeteer.launch({
	headless: false,
	executablePath: revisionInfo.executablePath,
})

Awesome - works even on macOS when the app has been through Apple's notarisation process.
Good work!

For the specific revision you need when using the BrowserFetcher, you can use this instead of having to keep track of the version whenever it changes:

const { PUPPETEER_REVISIONS } = require('puppeteer/lib/cjs/puppeteer/revisions')
// ...
const revisionInfo = await browserFetcher.download(PUPPETEER_REVISIONS.chromium)

This is internal, though, and could disappear/change on upgrade, so I'd advise adding a test verifying it.

Hello, I have tried the solution of latest message and the one here.

Both gives me this error during packaging process:

> Warning Cannot include directory %1 into executable.
  The directory must be distributed with executable as %2.
  %1: node_modules/puppeteer/.local-chromium
  %2: path-to-executable/puppeteer

and the following error when I try to run the linux binary:

Error: Timed out waiting for sync-rpc server to start (it should respond with "pong" when sent "ping"):

Any idea?

@smartm0use

I have same problem, im using puppeteer-extra plugin, tried several solutions above and it doesn't helps

For the specific revision you need when using the BrowserFetcher, you can use this instead of having to keep track of the version whenever it changes:

const { PUPPETEER_REVISIONS } = require('puppeteer/lib/cjs/puppeteer/revisions')
// ...
const revisionInfo = await browserFetcher.download(PUPPETEER_REVISIONS.chromium)

This is internal, though, and could disappear/change on upgrade, so I'd advise adding a test verifying it.

It has disappeared as of Puppeteer v18 👻

commented

FYI, after updating to 19.3.0 old snippet from @jchook might need a bit of adjustment: instead of slice(6) use slice(8)

this is my idea

app.ts

const browserFetcher = puppeteer.createBrowserFetcher();
const { PUPPETEER_REVISIONS } = require("puppeteer-core/internal/revisions.js");
const revisionInfo = browserFetcher.revisionInfo(PUPPETEER_REVISIONS.chromium);
let executablePath = revisionInfo.executablePath;
if ((process as any)["pkg"] !== undefined) {
  executablePath = path.join(path.dirname(process.execPath), "puppeteer", revisionInfo.executablePath.substring(revisionInfo.folderPath.length));
}

post-bundle.js

const fse = require("fs-extra");
const path = require("path");
const puppeteer = require("puppeteer");

const { PUPPETEER_REVISIONS } = require("puppeteer-core/internal/revisions.js");

async function main() {
  for (const platform of ["linux", "mac", /*"mac_arm", "win32", */"win64"]) {
    const browserFetcher = puppeteer.createBrowserFetcher({
      platform: platform,
    });
    const revisionInfo = browserFetcher.revisionInfo(PUPPETEER_REVISIONS.chromium);
    if (!revisionInfo.local) {
      await browserFetcher.download(PUPPETEER_REVISIONS.chromium);
    }
    fse.copySync(revisionInfo.folderPath, path.resolve(`./exec/puppeteer/`));
  }  
}

main();
{
  "scripts": {
    "clean": "rimraf ./dist/ ./exec/",
    "build": "npm run clean && tsc --pretty",
    "bundle": "npm run build && pkg . --out-dir ./exec/",
    "postbundle": "node scripts/post-bundle.js"
  }
}

Same issue here ...

i can't find the .local-chromium file, can someone help me?

@JaumDarkz

i can't find the .local-chromium file, can someone help me?

From Puppeteer documentation:

Starting in v19.0.0, Puppeteer stores browsers in ~/.cache/puppeteer to globally cache browsers between installation.

So this means that the .local-chromium doesn't exist anymore in newer versions of Puppeteer, and it's replaced by ~/.cache/puppeteer. You can however specify the install location of your local Chrome installation for Puppeteer with config file, for example: your-project-root-directory/.puppeteerrc.cjs:

const {join} = require('path');

/**
 * @type {import("puppeteer").Configuration}
 */
module.exports = {
  // Changes the cache location for Puppeteer.
  cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};

Check the link to documentation for more info on that!

Same issue.

Avoided unexpected behavior of puppeteer by adding the parameter "--public":
pkg --public index.js