Давайте немного повысим сложность. Мы рассмотрели три очень простых, прямолинейных транзакции, где отношения сводятся к тому, что я даю вам информацию, вы даете мне ответ.
- Онлайн-демонстрация скриншотов
- Исходный код в репозитории
- веб-компонент для демонстрации
Этот сценарий использования / требование было намного сложнее, чем другие (немного неожиданно) — это создание сервиса, способного удаленно рендерить веб-страницу. Я уже экспериментировал с этой концепцией раньше, поскольку среди новостных и медиа изданий популярно делать скриншоты из twitter и других источников, чтобы проиллюстрировать изменение сообщения, удаление и т.д.
Шаги в мышлении на пути к большому успеху
«Я хочу делать скриншоты», — сказал я. Затем я подумал…
- Что в NPM позволяет мне это сделать?
- «омг, насколько велик кукловод?!»
- «Вау, это работает фантастически локально»
- «вау, это совсем не работает в продакшене»
- «WTF ПОЧЕМУ ЭТО НЕ РАБОТАЕТ В ПРОДАКШЕНЕ»
- делает новое репо
- Ааа, вот оно, отлично работает в продакшене!
Итак, давайте немного распакуем эти проблемы и решения.
Что такое puppeteer и почему я должен им стать?
Puppeteer — это, по сути, безголовый браузерный бегун. Как они заявляют: «Большинство вещей, которые вы можете сделать вручную в браузере, можно сделать с помощью Puppeteer!».
Подумайте, если вы выполняете ряд задач: открываете браузер, набираете URL, ждете, пока он загрузится, прокручиваете и находите что-то, то кукловод может получить инструкции, что и в каком порядке делать, чтобы добиться того же самого. У сайта есть много общих сценариев использования, среди которых популярность для тестирования среды и замечения изменений CSS / макета в Pull-запросах.
Наше требование заключалось в том, чтобы делать скриншоты URL-адресов, чтобы мы могли обеспечить предварительный просмотр, эффективно указывая на то, что веб-сайт находится в процессе создания. Для ясности, на момент написания статьи эта функция не была подключена к нашему производственному приложению, поскольку ее добавление не планируется в ближайшее время, но возможность решить эту проблему с помощью Vercel помогла продемонстрировать возможности команды.
Код для меня
Используя подходы, аналогичные предыдущим трем конечным точкам, служба скриншотов выглядит следующим образом:
import { getBrowserInstance } from '../getBrowserInstance.js';
import { stdResponse, invalidRequest, stdPostBody } from "../requestHelpers.js";
// this requires its own service instance and can't live with the monorepo
// due to the size of the dependencies involved
export default async function handler(req, res) {
const body = stdPostBody(req);
const urlToCapture = body.urlToCapture;
// Perform URL validation
if (!urlToCapture || !urlToCapture.trim()) {
res = invalidRequest(res, 'enter a valid url');
}
else {
if (!urlToCapture.includes("https://")) {
// try to fake it
urlToCapture = `https://${urlToCapture}`;
}
// capture options
var browserGoToOptions = {
timeout: 60000,
waitUntil: 'networkidle2',
};
var screenshotOptions = {
quality: body.quality ? parseInt(body.quality) : 75,
type: 'jpeg',
encoding: "base64"
};
var base64 = '';
let browser = null
try {
browser = await getBrowserInstance();
let page = await browser.newPage();
await page.goto(urlToCapture, browserGoToOptions);
// special support for isolating a tweet
if (urlToCapture.includes('twitter.com')) {
await page.waitForSelector("article[data-testid='tweet']");
const element = await page.$("article[data-testid='tweet']");
base64 = await element.screenshot(screenshotOptions);
}
else {
screenshotOptions.fullPage = true;
base64 = await page.screenshot(screenshotOptions);
}
res = stdResponse(res,
{
url: urlToCapture,
image: base64
}, {
methods: "GET,OPTIONS,PATCH,DELETE,POST,PUT",
cache: 1800
}
);
} catch (error) {
console.log(error)
res = invalidRequest(res, 'something went wrong', 500);
} finally {
if (browser !== null) {
await browser.close()
}
}
}
}
Магия» здесь заключена в том, что называется getBrowserInstance, см. ниже:
import chromium from 'chrome-aws-lambda'
export async function getBrowserInstance() {
const executablePath = await chromium.executablePath
if (!executablePath) {
// running locally
const puppeteer = await import('puppeteer').then((m) => {
return m.default;
});
return await puppeteer.launch({
args: chromium.args,
headless: true,
defaultViewport: {
width: 1280,
height: 720
},
ignoreHTTPSErrors: true
});
}
return await chromium.puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: executablePath,
headless: chromium.headless,
ignoreHTTPSErrors: true
});
}
Эта простая функция помогает устранить разницу между vercel в продакшене (ака вызовы, управляемые Lambda) и локальными вызовами vercel dev
(которые используют локальную копию Chromium для рендеринга).
Возникшие проблемы
В процессе производства Vercel позволяет скомпилировать и запустить только такое количество кода. Из-за размера пакетов, сервис скриншотов пришлось вырезать из нашего монорепо и запускать отдельно. Об этом говорилось в предыдущем сообщении, но в результате определение в нашем промежуточном ПО выглядело следующим образом:
// screenshot - kept by itself bc of size of getBrowserInstance
MicroFrontendRegistry.add({
endpoint: "https://screenshoturl.elmsln.vercel.app/api/screenshotUrl",
name: "@core/screenshotUrl",
title: "Screenshot page",
description: "Takes screenshot of a URL and returns image",
params: {
urlToCapture: "full url with https",
quality: "Optional image quality parameter"
}
});
Это привязывает вызов @core/screenshotUrl
к очень специфическому адресу. Я не большой поклонник этого решения, так как мне хотелось бы, чтобы оно было более согласовано с остальной структурой URL, но это не конец света.
Видео
Здесь демонстрируется инструмент для создания скриншотов, а также рассказывается о том, как код способен обрабатывать удаленный URL и даже имеет поддержку для изоляции твитов в twitter, чтобы показать некоторую повышенную сложность 💪.