Taking Browser Screenshots with Deno
Today, I needed to turn SVGs into PNGs.
I decided to use Deno to do it.
Some cursory searching showed Puppeteer should be up to the task.
I also found deno-puppeteer
which seemed like it would provide a reasonable way to make this work.
To start, let’s set up a deno
project
deno init deno-browser-screenshotsdeno-browser-screenshots
Using puppeteer
Now, add some code to render an SVG with Chrome via puppeteer
.
import puppeteer from 'https://deno.land/x/puppeteer@16.2.0/mod.ts';
const svgString = `<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="#87CEEB"/> <circle cx="256" cy="256" r="100" fill="#FFD700"/> <path d="M 100 400 Q 256 300 412 400" stroke="#1E90FF" stroke-width="20" fill="none"/></svg>`;
if (import.meta.main) { try { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'], });
const page = await browser.newPage();
await page.setViewport({ width: 512, height: 512 }); await page.setContent(svgString);
await page.screenshot({ path: 'output.png', clip: { x: 0, y: 0, width: 512, height: 512, }, });
await browser.close(); } catch (error) { console.error('Error occurred:', error); console.error('Make sure Chrome is installed and the path is correct'); throw error; }}
When we run this code, we get the following error
deno run -A main.ts
Error occurred: Error: Could not find browser revision 1022525. Run "PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts" to download a supported browser binary. at ChromeLauncher.launch (https://deno.land/x/puppeteer@16.2.0/src/deno/Launcher.ts:99:30) at eventLoopTick (ext:core/01_core.js:175:7) at async file:///Users/danielcorin/dev/lab/deno-puppeteer/main.ts:12:21Make sure Chrome is installed and the path is correcterror: Uncaught (in promise) Error: Could not find browser revision 1022525. Run "PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts" to download a supported browser binary. if (missingText) throw new Error(missingText); ^ at ChromeLauncher.launch (https://deno.land/x/puppeteer@16.2.0/src/deno/Launcher.ts:99:30) at eventLoopTick (ext:core/01_core.js:175:7) at async file:///Users/danielcorin/dev/lab/deno-puppeteer/main.ts:12:21
Using npm
’s puppeteer
, we can install Chrome via npx
npx puppeteer browsers install chrome
However, this still did not resolve the error for me.
It turns out the npx
command install the browser to ~/.cache/puppeteer
.
To use it, we need the following modifications to our code to provide an executablePath
.
import puppeteer from 'https://deno.land/x/puppeteer@16.2.0/mod.ts';
const svgString = `<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="#87CEEB"/> <circle cx="256" cy="256" r="100" fill="#FFD700"/> <path d="M 100 400 Q 256 300 412 400" stroke="#1E90FF" stroke-width="20" fill="none"/></svg>`;
if (import.meta.main) { try { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'], executablePath: Deno.env.get('HOME') + '/.cache/puppeteer/chrome/mac_arm-131.0.6778.108/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing', });
const page = await browser.newPage();
await page.setViewport({ width: 512, height: 512 }); await page.setContent(` <style> body { margin: 0; background: transparent; } svg { display: block; } </style> ${svgString} `);
await page.screenshot({ path: 'output.png', clip: { x: 0, y: 0, width: 512, height: 512, }, });
await browser.close(); } catch (error) { console.error('Error occurred:', error); console.error('Make sure Chrome is installed and the path is correct'); throw error; }}
With that, the code runs successfully and we get output.png
in our working directory.
Using astral
There is another, Deno-native library called astral
(no relation to the Python tooling company).
I was curious to see how the DX compared for this simple use case.
The following code worked for me without issue
import { launch } from 'jsr:@astral/astral';
const svgString = `<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="#87CEEB"/> <circle cx="256" cy="256" r="100" fill="#FFD700"/> <path d="M 100 400 Q 256 300 412 400" stroke="#1E90FF" stroke-width="20" fill="none"/></svg>`;
if (import.meta.main) { try { const browser = await launch(); const page = await browser.newPage();
await page.setViewportSize({ width: 512, height: 512 }); await page.setContent(` <style> body { margin: 0; background: transparent; } svg { display: block; } </style> ${svgString} `);
const screenshot = await page.screenshot(); Deno.writeFileSync('output_astral.png', screenshot);
await browser.close(); } catch (error) { console.error('Error occurred:', error); throw error; }}
On first run, astral
manages the downloading of Chrome for you.
deno run -A main_astral.ts
Downloading chrome 125.0.6400.0 100.00%Download complete (chrome version 125.0.6400.0)Inflating /Users/danielcorin/Library/Caches/astral/125.0.6400.0 100.00%Browser saved to /Users/danielcorin/Library/Caches/astral/125.0.6400.0
This code works but unexpectedly outputs a PNG with size 1024 x 1024 and I’m not entirely sure why.
Adding the following not-very-nice-looking code seemed to fix this issue - maybe there is a better way.
await page.unsafelyGetCelestialBindings().Emulation.setDeviceMetricsOverride({ width: 512, height: 512, deviceScaleFactor: 1, mobile: false,});
With each approach, there were different rough edges. Both were able to fit my use case with a few tweaks.
Recommended
Intro to Deno
I tried out Deno for the first time. Deno bills itself as the most productive, secure, and performant JavaScript runtime for the modern programmer
Intro to Astro
I'm aiming to setup a space for more interactive UX experiments. My current Hugo blog has held up well with my scale of content but doesn't play...
Calling Deepseek with `llm` using OpenAI Compatible APIs
Deepseek V3 was recently released: a cheap, reliable, supposedly GPT-4 class model.