1685188680
В этом руководстве мы увидим, как мы можем развернуть бессерверные функции вместе с нашим интерфейсным приложением и создать API, который генерирует изображения и получает метаданные из ссылок.
Благодаря бессерверным функциям разработчики могут создавать и внедрять современные функции и возможности в свои приложения, не заморачиваясь настройкой внутренних серверов и управлением ими. Функции размещаются и развертываются компаниями, занимающимися облачными вычислениями.
Функции Netlify упрощают создание и развертывание бессерверных функций для приложений, размещенных на Netlify .
Чтобы следовать этому руководству, вам необходимо быть знакомым с JavaScript, Vue.js , Git, GitHub и Netlify . У вас также должен быть текстовый редактор — например, VS Code ) с установленным Vetur (для IntelliSense) — и последняя версия Node, установленная на вашем компьютере. Установить Node можно здесь . Вы можете проверить свою версию Node, выполнив команду node -vв своем терминале.
У вас также должна быть учетная запись на Netlify. Вы можете создать его , если еще этого не сделали.
Чтобы показать, как мы можем легко настроить бессерверные функции с нашим интерфейсным приложением, мы создадим приложение с настраиваемым компонентом предварительного просмотра ссылок.
Этот компонент отправляет запрос с URL-адресом нашей бессерверной функции. Затем функция использует Puppeteer для получения метаданных с целевого сайта по URL-адресу и создания снимка экрана сайта.
Функция отправляет метаданные и снимки экрана обратно в компонент на нашем внешнем интерфейсе, чтобы отобразить их в качестве предварительного просмотра ссылки в приложении.
Вот ссылка на пример проекта, развернутого на Netlify . А вот репозиторий GitHub, за которым стоит следить.
Мы собираемся создать приложение Vue 3, используя Vue CLI . Мы также установим и настроим Tailwind CSS , ориентированную на утилиты CSS-инфраструктуру, которая предоставляет классы, которые мы можем использовать для нашего приложения без необходимости писать много пользовательского CSS.
Чтобы быстро создать шаблон приложения Vue, мы будем использовать Vue CLI. Чтобы установить Vue CLI, запустите:
npm install -g @vue/cli
После установки CLI мы можем создать проект , запустив:
vue create link-previewer
Это предложит нам выбрать предустановку для нашей установки. Мы выберем «Выбрать функции вручную», чтобы мы могли выбрать нужные функции. Вот варианты, которые я выбрал:
Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, PWA, Router, Vuex, Linter
? Choose a version of Vue.js that you want to start the project with: 3.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
После выбора этих параметров нас спросят, хотим ли мы сохранить параметры в качестве предустановки для последующего использования. Выберите Y(да) или N(нет) и продолжите установку.
Выполнить cd link-previewer, чтобы войти в только что созданный проект.
Чтобы установить Tailwind, мы будем использовать сборку, совместимую с PostCSS 7 , поскольку Tailwind зависит от PostCSS 8, который на момент написания еще не поддерживается Vue 3. Удалите все предыдущие установки Tailwind и переустановите сборку совместимости:
npm uninstall tailwindcss postcss autoprefixer
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
Далее сгенерируйте tailwind.config.jsи postcss.config.jsфайлы:
npx tailwindcss init -p
Это создаст минимальный tailwind.config.jsфайл в корне проекта.
В tailwind.config.jsфайле настройте purgeпараметр с путями ко всем страницам и компонентам, чтобы Tailwind мог древовидно трясти неиспользуемые стили в производственных сборках:
// ./tailwind.config.js
module.exports = {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
...
}
Создайте ./src/assets/css/main.cssфайл и используйте @tailwindдирективу, чтобы включить стили Tailwind base, componentsи utilities:
/* ./src/assets/css/main.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body{
@apply bg-gray-50;
}
Tailwind заменит эти директивы во время сборки всеми стилями, которые он генерирует на основе настроенной системы дизайна.
Наконец, убедитесь, что файл CSS импортируется в ./src/main.jsфайл:
// ./src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import './assets/css/main.css'
createApp(App).use(store).use(router).mount('#app')
И все, мы можем запустить наш сервер:
npm run serve
Теперь, когда приложение запущено, если мы перейдем по предоставленному URL-адресу, мы должны увидеть демонстрационное приложение по умолчанию для Vue и увидеть, что были применены базовые стили предварительной проверки Tailwind .
Чтобы упростить процесс разработки, установите расширение Tailwind CSS Intellisense для VS Code.
Вот краткий обзор того, как должна выглядеть папка нашего проекта:
link-previewer/
├─ functions/
│ ├─ generate-preview.js
│ └─ hello.js
├─ public/
│ ├─ favicon.ico
│ ├─ img/
│ │ └─ icons/
│ ├─ index.html
│ └─ robots.txt
├─ src/
│ ├─ main.js
│ ├─ App.vue
│ ├─ registerServiceWorker.js
│ ├─ assets/
│ │ ├─ css/
│ │ │ └─ main.css
│ │ └─ logo.png
│ ├─ components/
│ │ └─ LinkPreviewer.vue
│ ├─ router/
│ │ └─ index.js
│ ├─ store/
│ │ └─ index.js
│ └─ views/
│ ├─ About.vue
│ └─ Home.vue
├─ .git
├─ .gitignore
├─ .browserslistrc
├─ .eslintrc.js
├─ babel.config.js
├─ netlify.toml
├─ package-lock.json
├─ package.json
├─ postcss.config.js
├─ README.md
└─ tailwind.config.js
Netlify Functions — это продукт Netlify, упрощающий процесс создания и развертывания бессерверных функций. Согласно домашней странице продукта, он используется для:
Разверните код на стороне сервера, который работает как конечные точки API, автоматически запускается в ответ на события или обрабатывает более сложные задания в фоновом режиме.
Базовый файл функции Netlify экспортирует метод обработчика со следующим синтаксисом:
exports.handler = async function(event, context){
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World!"})
}
}
Netlify предоставляет параметры eventи contextпри вызове/вызове функции. Когда вызывается конечная точка функции, она handlerполучает eventтакой объект:
{
"path": "Path parameter (original URL encoding)",
"httpMethod": "Incoming request’s method name",
"headers": {Incoming request headers},
"queryStringParameters": {Query string parameters},
"body": "A JSON string of the request payload",
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encoded"
}
С другой стороны, параметр contextвключает информацию о контексте, в котором была вызвана функция.
Внутри функции мы возвращаем объект с двумя важными свойствами:
Функция будет вызываться с нашего сайта в случае /.netlify/functions/helloуспеха, она вернет код состояния 200 и сообщение «Hello, World!».
Теперь, когда у нас есть представление о том, как работают функции Netlify, давайте посмотрим на них на практике.
Чтобы создать нашу первую функцию Netlify, мы создадим новый файл functions/hello.jsв каталоге проекта и введем следующее:
// functions/hello.js
exports.handler = async function(event, context){
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World!"})
}
}
После того, как мы создали файл функции, мы должны сделать некоторые необходимые настройки, чтобы мы могли запускать нашу функцию локально.
Мы создадим netlify.tomlфайл в корне папки нашего проекта, который сообщит Netlify, где найти наши функции:
# ./netlify.toml
[functions]
directory = "./functions"
Netlify теперь будет находить и развертывать функции в functionsпапке во время сборки.
Чтобы запускать наши функции локально без развертывания в Netlify, нам нужно установить Netlify CLI . Интерфейс командной строки позволяет нам развертывать наши проекты с некоторыми замечательными функциями Netlify локально.
Чтобы установить CLI, убедитесь, что у вас Node.js версии 10 или новее, а затем запустите:
npm install netlify-cli -g
Это устанавливает Netlify CLI глобально, поэтому мы можем запускать netlifyкоманды из любого каталога. Чтобы получить версию, использование и т. д., мы можем запустить:
netlify
Чтобы запустить наш проект локально с Netlify CLI, остановите сервер разработки (если он активен), затем запустите:
netlify dev
И вот что мы должны увидеть:
Если вы внимательно посмотрите, то увидите, что там происходит несколько вещей:
Netlify пытается внедрить переменные среды из наших .envфайлов в процесс сборки, к которым затем могут получить доступ наши функции Netlify. В этом случае у нас нет .envфайла, поэтому он загружает значения по умолчанию, определенные в process.
Во-вторых, он загружает или развертывает наши функции, расположенные в каталоге functions. Сервер функций развернут на другом случайно выбранном порту — 36647.
Наконец, он автоматически определяет, на какой платформе построено приложение, и запускает необходимые процессы сборки для развертывания приложения. В этом случае вы можете увидеть «Запуск Netlify Dev с Vue.js». Он также поддерживает React и другие популярные фреймворки.
Затем Netlify запускает наш сервер разработки на http://localhost:8888.
Теперь, когда наш сервер запущен и наши функции загружены, мы можем вызвать/вызвать его. По умолчанию мы можем получить доступ к нашим функциям, используя этот маршрут: /.netlify/functions/<function name>.
Важно отметить, что нам не нужно указывать порт, на котором работает наш сервер функций. Мы можем использовать указанный выше маршрут по умолчанию для связи с нашим сервером функций. Netlify автоматически разрешает URL-адрес и порт за кулисами.
если мы отправим GETзапрос на http://localhost:8888/.netlify/functions/hello , мы должны получить ответ {"message":"Hello, World!"}.
Большой! Наша первая бессерверная функция работает!
Теперь, когда наша функция Netlify работает, мы можем приступить к созданию предварительного API. Вот краткое изложение того, что будет делать наш API функций:
Теперь, когда у нас есть общее представление о том, что будет делать наш API функций, мы можем приступить к созданию функций. Начнем с установки и настройки Puppeteer для функций Netlify.
Puppeteer — это библиотека Node, которая предоставляет высокоуровневый API для управления безголовыми браузерами Chrome или Chromium. Его также можно настроить для использования полного (не безголового) хрома или хрома. Вы можете делать большинство вещей, которые вы можете делать вручную в браузере, используя Puppeteer. Подробнее о Puppeteer можно узнать в документации Puppeteer.
Чтобы начать работу с Puppeteer, мы установим его в нашем проекте.
Puppeteer загружает последнюю версию Chromium (~170 МБ для macOS, ~282 МБ для Linux, ~280 МБ для Windows), которая гарантированно работает с API.
Мы не можем использовать полный puppeteerпакет для производства. Это связано с тем, что максимальный размер Netlify Functions составляет 50 МБ, а пакет Chromium слишком велик.
Благодаря этой очень полезной статье Ире Адеринокуна мы по-прежнему можем работать с Puppeteer как локально, так и в продакшне. Вот что нам нужно сделать:
Установить puppeteerкак зависимость разработки * для локального развертывания:
npm i puppeteer --save-dev
Чтобы Puppeteer работал как локально, так и в продакшене, мы должны установить puppeteer-core и chrome-aws-lambdaв качестве производственных зависимостей .
Вы можете проверить разницу между puppeteerи puppeteer-core здесь . Однако основное отличие заключается в том, что puppeteer-coreChromium не загружается автоматически при установке.
Поскольку puppeteer-coreбраузер не загружается, мы установим chrome-aws-lambda , «Chromium Binary для AWS Lambda и облачных функций Google», который мы можем использовать в наших функциях Netlify. Вот пакеты, которые будут работать в продакшене:
npm i puppeteer-core chrome-aws-lambda --save-prod
Теперь, когда мы установили наши пакеты, давайте создадим нашу функцию.
Если Puppeteer устанавливает полноценный браузер для локальной работы, это может быть связано с проблемами с медленной сетью или пропускной способностью. Есть обходной путь — использовать уже установленный браузер Chrome или Chromium для Puppeteer.
Нам нужен путь к браузеру на нашей локальной машине. Мы будем использовать это как наш executablePath, который мы передадим методу puppeteer.launch(). Это сообщает Puppeteer, где найти исполняемый файл браузера.
Если вы точно не знаете, где найти путь к исполняемому файлу, откройте браузер и перейдите на страницу chrome://version/ , чтобы отобразить версию chrome.
Скопируйте путь и создайте .envфайл в корне проекта.
# ./.env
EXCECUTABLE_PATH=<path to chrome>
Чтобы получить содержимое файла .env, мы установим другой пакет — dotenv:
npm install dotenv
Теперь, когда мы успешно установили пакет, давайте создадим функцию Netlify.
Создайте новый файл, ./functions/generate-preview.js:
// ./functions/generate-preview.js
const chromium = require('chrome-aws-lambda')
const puppeteer = require('puppeteer-core')
exports.handler = async function (event, context) {
// parse body of POST request to valid object and
// use object destructuring to obtain target url
const { targetURL } = JSON.parse(event.body)
// launch browser
const browser = await puppeteer.launch({
args: chromium.args,
// get path to browser
executablePath: process.env.EXCECUTABLE_PATH || await chromium.executablePath,
headless: true
})
// open new page in browser
const page = await browser.newPage()
// set the viewport of the page
await page.setViewport({
width: 768,
height: 425,
deviceScaleFactor: 1
})
// set the prefers-color-scheme to dark
await page.emulateMediaFeatures([
{name: 'prefers-color-scheme', value:'dark'}
])
// navigate to target URL and get page details and screenshot
try{
...
}
}
В приведенном выше коде мы делаем несколько вещей. Во-первых, мы получаем targetURLиз полезной нагрузки запроса в формате event.body. Это будет отправлено с POSTзапросом.
Далее запускаем браузер с помощью chrome-aws-lambdapackage. Делаем это с помощью puppeteer.launch()метода. Этот метод принимает объект в качестве аргумента с несколькими необязательными свойствами. Важным свойством, которое мы передаем этому методу, является executablePath.
Мы назначаем executablePath, чтобы process.env.EXCECUTABLE_PATH || await chromium.executablePathпозволить пакету найти доступный безголовый браузер для запуска.
После запуска браузера мы открываем новую страницу в браузере с помощью browser.newPage()метода. Мы также устанавливаем желаемую область просмотра браузера для страницы с помощью page.setViewport()метода.
Обратите внимание, что мы используем awaitключевое слово при запуске любой функции. Это связано с тем, что Puppeteer работает асинхронно, и выполнение некоторых функций может занять некоторое время.
Мы также можем делать такие вещи, как определение мультимедийных функций страницы с помощью Puppeteer, используя метод page.emulateMediaFeatures(), который принимает массив объектов мультимедийных функций. Вот как мы устанавливаем prefers-color-schemeзначение dark.
Затем мы перейдем к целевому URL-адресу и получим наш заголовок, описание и снимок экрана:
// ./functions/generate-preview.js
...
// navigate to target URL and get page details and screenshot
try {
// navigate to the targetURL
await page.goto(targetURL)
// get the title from the newly loaded page
const title = (await page.$eval(`head > title`, el => el.textContent) || null)
// get the descriptions of the page using their CSS selectors
const descriptions = await page.evaluate(() => {
let descriptions = {}
let desc = document.querySelector(`meta[name='description']`)
let og = document.querySelector(`meta[property='og:description']`)
let twitter = document.querySelector(`meta[property='twitter:description']`)
desc ? descriptions.desc = desc.content : descriptions.desc = null
og ? descriptions.og = og.content: descriptions.og = null
twitter ? descriptions.twitter = twitter.content : descriptions.twitter = null
return descriptions
})
// screenshot the page as a jpeg with a base64 encoding
const screenshot = await page.screenshot({
type: 'jpeg',
encoding: 'base64'
})
// close the browser
await browser.close()
// send the page details
return {
statusCode: 200,
body: JSON.stringify({
title,
screenshot,
descriptions
})
}
} catch (error) {
// if any error occurs, close the browser instance
// and send an error code
await browser.close()
return {
statusCode: 400,
body: JSON.stringify({
error
})
}
}
В приведенном выше коде мы используем trycatchблок для переноса нашего кода, чтобы, если что-то пойдет не так, начиная с await page.goto(targetURL), который ведет к целевому URL-адресу, мы могли перехватить ошибку и отправить ее в наш внешний интерфейс. Ошибка может возникнуть из-за предоставления недопустимого URL-адреса.
Если URL был действительным, мы получаем заголовок с помощью метода page.$eval(), аналогичного обычному document.querySelectorметоду в JavaScript. Мы передаем селектор CSS — head > title— тега title в качестве первого аргумента. Мы также передаем функцию el => el.textContentв качестве второго аргумента, где el— параметр, который мы передаем функции, и — titleэлемент. Теперь мы можем получить значение, используя title.textContent.
Обратите внимание, что все это заключено в круглые скобки ( ()), и у нас есть || nullafter page.$eval. Это связано с тем, что titleприсваивается значение null, если page.$eval()не удается получить заголовок страницы.
Чтобы получить описания страницы, мы будем использовать метод page.evaluate(), который позволяет нам запустить некоторый клиентский JavaScript и вернуть значение назначенной переменной — descriptions.
Мы передаем функцию как и аргумент методу page.evaluate(). Внутри функции мы используем document.querySelectorдля получения различных метаописаний, таких как <meta name="description" content="<site description>" />описание по умолчанию и <meta property="og:description" content="<site description>" />описание OpenGraph.
После получения элементов мы используем тернарные операторы, чтобы получить contentи добавить их к descriptionsобъекту, если элементы существуют или nullэлемент не существует.
Получив описания, мы делаем снимок экрана с помощью метода page.screenshot()и закрываем браузер с помощью browser.close().
Наконец, мы отправляем сведения о странице в bodyсвойстве объекта JSON с statusCodeрасширением 200. Если на каком-либо из предыдущих шагов возникает ошибка, она фиксируется в catchблоке, и вместо этого мы отправляем statusCodeсообщение 400об ошибке.
Проверим нашу функцию с помощью тестера API. Вы можете установить тестер API Postman или Talend в свой браузер или использовать расширение Thunder Client , тестер API для VS Code.
Вы также можете использовать cURL:
curl -X POST -H "Content-Type: application/json" -d '{"paramName": "value"}' <URL>
Запустите функцию с помощью netlify devкоманды.
Мы можем отправить запрос, используя порт для сервера функций или :8888порт по умолчанию для сервера разработки Netlify, чтобы отправить запрос к нашим функциям. Я буду использовать http://localhost:8888/.netlify/functions/generate-previewдля отправки POSTзапроса объект, содержащий targetURLв body:
{
"targetURL" : "https://miracleio.me"
}
Когда мы отправляем запрос, вот ответ, который мы получаем.
Мы получаем объект JSON, содержащий наши данные предварительного просмотра:
{
"title": "Miracleio | PortfolioX",
"screenshot": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
"descriptions": {
"desc": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
"og": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
"twitter": null
}
}
Теперь, когда наша бессерверная функция работает, давайте посмотрим, как мы можем использовать ее в нашем внешнем интерфейсе.
Чтобы взаимодействовать с нашей generate-previewфункцией, нам нужно будет отправлять POSTзапросы, содержащие наш файл targetURL.
Мы создадим LinkPreviewкомпоненты, которые будут отображать обычные ссылки. Этим компонентам будут переданы их целевые URL-адреса в качестве реквизита. Прежде чем компонент будет смонтирован в приложении, он отправит запрос POSTнашей targetURLбессерверной функции, получит данные предварительного просмотра и отобразит их, как только мы наведем курсор на ссылку.
Во-первых, давайте создадим наш компонент предварительного просмотра ссылок src/components/LinkPreviewer.vue.
В нашем случае <script>мы получим данные предварительного просмотра ссылки, отправив запрос в нашу бессерверную функцию и сохранив данные в previewDataобъекте. Мы будем использовать это позже в нашем шаблоне для отображения данных:
// ./src/components/LinkPreviewer.vue
...
<script>
import { computed, onBeforeMount, ref } from '@vue/runtime-core'
export default {
// define targetURL as a prop
props: ['targetURL'],
setup(props) {
// create a reactive previewData object using ref
const previewData = ref({})
// function to send a POST request containing the targetURL
// to the serverless function
const generatePreview = async () => {
try {
const res = await fetch('/.netlify/functions/generate-preview', {
method: 'POST',
body: JSON.stringify({
targetURL : props.targetURL
})
})
const data = await res.json()
return data
} catch (err) {
console.log(err)
return null
}
}
// run function before component is mounted
onBeforeMount(async ()=>{
// run generatePreview() to get the preview data and assign to previewData
previewData.value = await generatePreview()
// use object destructuring to get the different descriptions
// from the preview data
const {desc, og, twitter} = previewData.value.descriptions
// assign only one valid value to the description property
// in the previewData object
previewData.value.description = computed(()=>(desc || og || twitter || ""))
})
// make the following entities available to the component
return { generatePreview, previewData}
}
}
</script>
В приведенном выше коде мы получаем свойство targetURLкак свойство, которое будет передано в наш компонент.
В setup(), мы передаем propsв качестве аргумента, чтобы мы могли получить доступ к свойствам компонентов, таким как targetURL.
Затем мы создаем реактивный peviewDataобъект, используя ref: const previewData = ref({}). В новой generatePreview()функции мы используем fetchдля отправки POSTзапроса, содержащего targetURLв нашу бессерверную функцию. Эта функция возвращает ответ или nullпри возникновении ошибки.
Далее, чтобы запустить функцию до того, как компонент будет смонтирован, мы используем onBeforeMount()хук. Мы передаем asyncфункцию в качестве аргумента. Внутри функции мы присваиваем previewData.valueфункции generatePreview(). Описания ( desc, og, twitter) затем получаются из descriptionsсвойства.
Чтобы получить описание, которое будет отображаться в предварительном просмотре, мы previewData.value.descriptionназначим (desc || og || twitter || ""). Таким образом, первое свойство со значением присваивается description.
Сделайте это, чтобы отобразить данные предварительного просмотра в нашем шаблоне:
<!-- ./src/components/LinkPreviewer.vue -->
<template>
<div class="inline relative">
<!-- display targetURL link -->
<a class="link underline text-blue-600"
:href="targetURL"
:target="previewData ? previewData.title : '_blank'">
{{targetURL}}
</a>
<!-- display preview data if object exists -->
<div v-if="previewData" class="result-preview absolute top-8 left-0 w-72 transform translate-y-4 opacity-0 invisible transition bg-white overflow-hidden rounded-md shadow-lg z-10">
<!-- display image using the base64 screenshot data -->
<img v-if="previewData.screenshot"
:src="`data:image/jpeg;base64,${previewData.screenshot}`"
:alt="previewData.description" />
<!-- display title and description -->
<div class="details p-4 text-left">
<h1 class=" font-extrabold text-xl"> {{previewData.title}} </h1>
<p> {{previewData.description}} </p>
</div>
</div>
</div>
</template>
<script> ... </script>
<style scoped>
.link:hover ~ .result-preview{
@apply visible opacity-100 translate-y-0;
}
</style>
В приведенном выше коде, чтобы отобразить наше изображение, которое по сути является base64строкой, мы должны передать строку вместе с такими данными, как тип изображения и кодировка, в атрибут src-"".
Вот и все для нашего LinkPreviewer.vueкомпонента. Давайте посмотрим на это в действии. В ./src/views/Home.vue:
<!-- ./src/views/Home.vue -->
<template>
<main class="home">
<header>
<h1>Welcome to the link previewer app!</h1>
<p>Here are some links that you can preview by hovering on them</p>
</header>
<ul class=" mb-4">
<!-- render LinkPreviewer component for each demolink -->
<li v-for="link in demoLinks" :key="link">
<link-previewer :targetURL="link" />
</li>
</ul>
<!-- input field to add new links -->
<input class=" p-2 ring ring-blue-600 rounded-lg shadow-md" type="url" @keyup.enter="addLink" required placeholder="enter valid url">
</main>
</template>
<script>
import { ref } from '@vue/reactivity'
import LinkPreviewer from '../components/LinkPreviewer.vue'
export default{
components: { LinkPreviewer },
setup(){
// demo links
const demoLinks = ref([
'http://localhost:5000',
'https://google.com',
'https://miracleio.me',
'https://miguelpiedrafita.com/'
])
// function to add new links to the demoLinks array
const addLink = ({target}) => {
demoLinks.value.push(target.value)
target.value = ""
}
return {demoLinks, addLink}
}
}
</script>
В нашем Home.vueфайле мы в основном используем demoLinksмассив ссылок для отображения списка LinkPreviewerкомпонентов, который мы передаем реквизитам targetURLкомпонента.
У нас также есть <input>элемент, который мы используем для динамического добавления дополнительных LinkPreviewerкомпонентов в список.
Вот как теперь выглядит наше простое приложение.
Сладкий! Наше приложение работает. Поскольку мы работали локально с помощью Netlify CLI, давайте посмотрим, как мы можем выполнить развертывание в Netlify с помощью CLI.
Прежде чем мы развернем наше приложение в Netlify, мы должны создать наше приложение для производства:
npm run build
Это создаст наше приложение и создаст dist/папку, которую мы можем развернуть в рабочей среде.
Далее нам нужно войти в нашу учетную запись Netlify:
netlify deploy
Это войдет в вашу учетную запись Netlify в вашем браузере.
После авторизации приложения мы можем связать наш проект с новым сайтом. Netlify задаст нам кучу вопросов:
После этого Netlify загрузит наши файлы и развернет их на нашем новом сайте.
В качестве альтернативы мы можем решить развернуть наш сайт с GitHub. Все, что вам нужно сделать, это войти в GitHub, создать новый репозиторий и скопировать URL-адрес в наш только что созданный репозиторий.
Затем мы запускаем следующую команду в папке нашего проекта:
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/miracleonyenma/link-previewer.git
git push -u origin main
Примечание. Возможно, вы не сможете отправить репозиторий со своего терминала из-за проблем с аутентификацией, и вы можете получить сообщение от Git, подобное этому: «Поддержка аутентификации по паролю была удалена 13 августа 2021 года. Пожалуйста, используйте токен личного доступа. вместо." Это означает, что вам нужно создать токен личного доступа (PAT) и использовать его для входа в систему. Для этого перейдите в настройки токена GitHub и создайте новый токен. Выберите все разрешения, которые вы хотите. Убедитесь, что у вас есть доступ к репозиториям. После создания PAT скопируйте его и сохраните где-нибудь. Затем повторите git push -u origin mainкоманду и вставьте свой PAT, когда вас попросят ввести пароль.
После того, как мы отправили проект на GitHub, перейдите в Netlify, чтобы создать новый сайт из GitHub .
Следуйте инструкциям, чтобы выбрать репозиторий и ввести параметры сборки для вашего проекта. Для нашего проекта Vue команда сборки — npm run build, а каталог развертывания — dist.
После этого нажмите «Развернуть сайт» .
Netlify развернет сайт, и мы сможем просмотреть наш сайт, щелкнув предоставленную ссылку для развертывания. Мы можем увидеть наши функции, перейдя в «Функции» в верхнем меню.
Вы можете выбрать функцию для просмотра дополнительных сведений и журналов.
Сладкий!
Вот ссылка на демонстрацию, развернутую на Netlify: https://lnkpreviewr.netlify.app
Мы смогли создать и развернуть бессерверные функции с помощью Netlify, используя функции Netlify. Мы также увидели, как мы можем взаимодействовать с функциями нашего внешнего интерфейса Vue. На этот раз мы использовали его для создания скриншотов и получения данных с других сайтов и создали с его помощью компонент предварительного просмотра ссылок, но мы можем сделать гораздо больше. С бессерверными функциями мы можем делать больше на внешнем интерфейсе, не беспокоясь о настройке внутреннего сервера.
Оригинальный источник статьи: https://www.sitepoint.com/
1685184863
在本教程中,我们将了解如何将无服务器功能与前端应用程序一起部署,并创建一个 API 来生成图像并从链接中获取元数据。
借助无服务器功能,开发人员可以在其应用程序中创建和实施现代特性和功能,而无需经历设置和管理后端服务器的痛苦。这些功能由云计算公司托管和部署。
Netlify 函数使得为托管在Netlify上的应用程序创建和部署无服务器函数变得容易。
要学习本教程,您需要熟悉 JavaScript、Vue.js、Git、GitHub 和Netlify。您还应该有一个安装了Vetur (用于 IntelliSense)的文本编辑器(例如VS Code),并且在您的计算机上安装了最新版本的 Node。您可以在此处安装 Node 。您可以通过在终端中运行命令来检查您的 Node 版本。node -v
您还应该在 Netlify 上有一个帐户。如果您还没有,可以创建一个。
为了展示我们如何使用前端应用程序轻松设置无服务器功能,我们将构建一个带有自定义链接预览器组件的应用程序。
该组件向我们的无服务器功能发送带有 URL 的请求。然后该函数使用 Puppeteer 使用 URL 从目标站点获取元数据并生成该站点的屏幕截图。
该函数将元数据和屏幕截图发送回我们前端的组件,以在应用程序中将其显示为链接预览。
这是部署在 Netlify 上的示例项目的链接。这里是GitHub Repo跟随。
我们将使用Vue CLI创建一个 Vue 3 应用程序。我们还将安装和设置Tailwind CSS,这是一个实用程序优先的 CSS 框架,它提供了我们可以用于我们的应用程序的类,而无需编写大量自定义 CSS。
为了快速构建 Vue 应用程序,我们将使用 Vue CLI。要安装 Vue CLI,请运行:
npm install -g @vue/cli
安装 CLI 后,我们可以通过运行以下命令创建项目:
vue create link-previewer
这将提示我们为我们的安装选择一个预设。我们将选择“手动选择功能”,以便我们可以选择我们需要的功能。以下是我选择的选项:
Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, PWA, Router, Vuex, Linter
? Choose a version of Vue.js that you want to start the project with: 3.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
选择这些选项后,系统会询问我们是否要将这些选项保存为预设以备后用。选择Y(是)或N(否)并继续安装。
运行cd link-previewer进入新建的项目。
要安装 Tailwind,我们将使用PostCSS 7 兼容性构建,因为 Tailwind 依赖于 PostCSS 8——在撰写本文时,Vue 3 尚不支持它。卸载任何以前的 Tailwind 安装并重新安装兼容性构建:
npm uninstall tailwindcss postcss autoprefixer
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
接下来,生成tailwind.config.js和postcss.config.js文件:
npx tailwindcss init -p
tailwind.config.js这将在项目的根目录下创建一个最小文件。
在该tailwind.config.js文件中,purge使用所有页面和组件的路径配置选项,以便 Tailwind 可以在生产构建中摇树未使用的样式:
// ./tailwind.config.js
module.exports = {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
...
}
创建./src/assets/css/main.css文件并使用@tailwind指令包含 Tailwind 的base、components和utilities样式:
/* ./src/assets/css/main.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body{
@apply bg-gray-50;
}
Tailwind 将在构建时将这些指令与它根据配置的设计系统生成的所有样式交换出来。
最后,确保在文件中导入 CSS 文件./src/main.js:
// ./src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import './assets/css/main.css'
createApp(App).use(store).use(router).mount('#app')
就是这样,我们可以运行我们的服务器:
npm run serve
现在应用程序正在运行,如果我们转到提供的 URL,我们应该会看到 Vue 的默认演示应用程序并看到 Tailwind 的预检基础样式已被应用。
为了获得更流畅的开发体验,请安装适用于 VS Code 的Tailwind CSS Intellisense 扩展。
这是我们的项目文件夹应该是什么样子的概述:
link-previewer/
├─ functions/
│ ├─ generate-preview.js
│ └─ hello.js
├─ public/
│ ├─ favicon.ico
│ ├─ img/
│ │ └─ icons/
│ ├─ index.html
│ └─ robots.txt
├─ src/
│ ├─ main.js
│ ├─ App.vue
│ ├─ registerServiceWorker.js
│ ├─ assets/
│ │ ├─ css/
│ │ │ └─ main.css
│ │ └─ logo.png
│ ├─ components/
│ │ └─ LinkPreviewer.vue
│ ├─ router/
│ │ └─ index.js
│ ├─ store/
│ │ └─ index.js
│ └─ views/
│ ├─ About.vue
│ └─ Home.vue
├─ .git
├─ .gitignore
├─ .browserslistrc
├─ .eslintrc.js
├─ babel.config.js
├─ netlify.toml
├─ package-lock.json
├─ package.json
├─ postcss.config.js
├─ README.md
└─ tailwind.config.js
Netlify Functions是一种 Netlify 产品,可简化创建和部署无服务器函数的过程。根据产品主页,它用于:
部署用作 API 端点的服务器端代码,自动运行以响应事件,或在后台处理更复杂的作业。
基本的 Netlify 函数文件使用以下语法导出处理程序方法:
exports.handler = async function(event, context){
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World!"})
}
}
Netlify在调用/调用函数时提供event和参数。context当一个函数的端点被调用时,handler接收到一个event像这样的对象:
{
"path": "Path parameter (original URL encoding)",
"httpMethod": "Incoming request’s method name",
"headers": {Incoming request headers},
"queryStringParameters": {Query string parameters},
"body": "A JSON string of the request payload",
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encoded"
}
context另一方面,参数包括有关调用函数的上下文的信息。
在函数中,我们返回一个具有两个重要属性的对象:
该函数将在成功时从我们的站点调用/.netlify/functions/hello,它将返回 200 状态代码和消息“Hello, World!”。
现在我们已经了解了 Netlify 函数的工作原理,让我们在实践中看看它们。
要创建我们的第一个 Netlify 函数,我们将functions/hello.js在项目目录中创建一个新文件并输入以下内容:
// functions/hello.js
exports.handler = async function(event, context){
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World!"})
}
}
一旦我们创建了函数文件,我们必须进行一些必要的配置,以便我们在本地运行我们的函数。
我们将netlify.toml在项目文件夹的根目录下创建一个文件,告诉 Netlify 在哪里可以找到我们的函数:
# ./netlify.toml
[functions]
directory = "./functions"
functionsNetlify 现在将在构建时定位并部署文件夹中的函数。
要在本地运行我们的功能而不必部署到 Netlify,我们需要安装Netlify CLI。CLI 允许我们在本地部署具有一些出色的 Netlify 功能的项目。
要安装 CLI,请确保您拥有Node.js版本 10 或更高版本,然后运行:
npm install netlify-cli -g
这会全局安装 Netlify CLI,因此我们可以netlify从任何目录运行命令。要获取版本、用法等信息,我们可以运行:
netlify
要使用 Netlify CLI 在本地运行我们的项目,请停止开发服务器(如果它处于活动状态),然后运行:
netlify dev
这是我们应该看到的:
如果你仔细观察,你会看到那里发生了一些事情:
Netlify 尝试将我们.env文件中的环境变量注入到构建过程中,然后我们的 Netlify 函数可以访问这些变量。在这种情况下,我们没有.env文件,因此它加载在process.
其次,它加载或部署位于函数目录中的函数。Functions 服务器部署在不同的随机端口 — 36647.
最后,它会自动检测应用程序是用什么框架构建的,并运行必要的构建过程来部署应用程序。在这种情况下,您可以看到“使用 Vue.js 启动 Netlify Dev”。它还支持 React 和其他流行的框架。
Netlify 然后在 上启动我们的开发服务器http://localhost:8888。
现在我们的服务器已经启动并且我们的函数已经加载,我们可以调用它了。默认情况下,我们可以使用此路由访问我们的函数/.netlify/functions/<function name>:
需要注意的一件重要事情是,我们不需要指定运行 Functions 服务器的端口。我们可以使用上面的默认路由与我们的 Functions 服务器通信。Netlify 在后台自动解析 URL 和端口。
如果我们GET向http://localhost:8888/.netlify/functions/hello发送请求,我们应该得到一个响应{"message":"Hello, World!"}。
伟大的!我们的第一个无服务器功能起作用了!
现在我们的 Netlify 函数可以工作了,我们可以开始构建预览 API。以下是我们的 Functions API 将要执行的操作的简要说明:
现在我们对函数 API 将要做什么有了基本的了解,我们可以开始创建函数了。让我们从为 Netlify Functions 安装和设置 Puppeteer 开始。
Puppeteer是一个 Node 库,它提供高级 API 来控制无头 Chrome 或 Chromium 浏览器。它还可以配置为使用完整的(非无头的)chrome 或 Chromium。您可以使用 Puppeteer 完成大多数您可以在浏览器中手动完成的事情。有关 Puppeteer 的更多信息,请参阅 Puppeteer 文档。
要开始使用 Puppeteer,我们将把它安装到我们的项目中。
Puppeteer 下载最新版本的 Chromium(~170MB macOS,~282MB Linux,~280MB Windows)保证与 API 一起使用。
我们不能将完整的puppeteer包用于生产。这是因为 Netlify Functions 的最大大小为 50MB,而 Chromium 包太大。
感谢Ire Aderinokun的这篇非常有用的文章,我们仍然可以在本地和生产环境中使用 Puppeteer。这是我们必须做的:
puppeteer作为本地部署的开发依赖*安装:
npm i puppeteer --save-dev
为了让 Puppeteer 在本地和生产环境中工作,我们必须安装puppeteer-core并chrome-aws-lambda作为生产依赖项。
您可以在此处查看puppeteer和之间的区别。不过,主要区别在于安装时不会自动下载 Chromium。puppeteer-core puppeteer-core
由于puppeteer-core没有下载浏览器,我们将安装chrome-aws-lambda,这是一个“适用于 AWS Lambda 和 Google Cloud Functions 的 Chromium 二进制文件”,我们可以在 Netlify 函数中使用它。这些是将在生产中使用的包:
npm i puppeteer-core chrome-aws-lambda --save-prod
现在我们已经安装了我们的包,让我们创建我们的函数。
如果 Puppeteer 安装完整的浏览器以在本地使用将成为一个问题,那可能是由于网络速度慢或带宽问题。有一个解决方法,就是使用我们已经安装的 Chrome 或 Chromium 浏览器来运行 Puppeteer。
我们需要的是本地计算机中浏览器的路径。我们将使用它作为我们的executablePath,我们将传递给puppeteer.launch()方法。这告诉 Puppeteer 在哪里可以找到浏览器可执行文件。
如果您不知道在哪里可以找到可执行文件路径,请打开浏览器并转到chrome://version/以显示 chrome 的版本。
复制路径并.env在项目的根目录中创建一个文件。
# ./.env
EXCECUTABLE_PATH=<path to chrome>
为了获取文件的内容.env,我们将安装另一个包—— dotenv:
npm install dotenv
现在我们已经成功安装了包,让我们创建 Netlify 函数。
创建一个新文件,./functions/generate-preview.js:
// ./functions/generate-preview.js
const chromium = require('chrome-aws-lambda')
const puppeteer = require('puppeteer-core')
exports.handler = async function (event, context) {
// parse body of POST request to valid object and
// use object destructuring to obtain target url
const { targetURL } = JSON.parse(event.body)
// launch browser
const browser = await puppeteer.launch({
args: chromium.args,
// get path to browser
executablePath: process.env.EXCECUTABLE_PATH || await chromium.executablePath,
headless: true
})
// open new page in browser
const page = await browser.newPage()
// set the viewport of the page
await page.setViewport({
width: 768,
height: 425,
deviceScaleFactor: 1
})
// set the prefers-color-scheme to dark
await page.emulateMediaFeatures([
{name: 'prefers-color-scheme', value:'dark'}
])
// navigate to target URL and get page details and screenshot
try{
...
}
}
在上面的代码中,我们做了很多事情。targetURL首先,我们从请求有效载荷中获取event.body。这将与请求一起发送POST。
接下来,我们使用该chrome-aws-lambda包启动浏览器。我们使用puppeteer.launch()方法来做到这一点。此方法将一个对象作为带有一些可选属性的参数。我们传递给此方法的一个重要属性是executablePath.
我们将 分配executablePath给process.env.EXCECUTABLE_PATH || await chromium.executablePath使包能够找到可用的无头浏览器来启动。
启动浏览器后,我们使用该browser.newPage()方法在浏览器中打开一个新页面。我们还使用该方法为页面设置了我们想要的浏览器视口page.setViewport()。
请注意,我们await在运行任何函数时都使用了关键字。这是因为 Puppeteer 是异步工作的,一些函数可能需要一些时间才能执行。
我们还可以做一些事情,比如使用 Puppeteer 使用该方法定义页面的媒体功能page.emulateMediaFeatures(),该方法采用媒体功能对象数组。这就是我们设置prefers-color-schemeto 的方式dark。
接下来,我们将导航到目标 URL 并获取我们的标题、描述和屏幕截图:
// ./functions/generate-preview.js
...
// navigate to target URL and get page details and screenshot
try {
// navigate to the targetURL
await page.goto(targetURL)
// get the title from the newly loaded page
const title = (await page.$eval(`head > title`, el => el.textContent) || null)
// get the descriptions of the page using their CSS selectors
const descriptions = await page.evaluate(() => {
let descriptions = {}
let desc = document.querySelector(`meta[name='description']`)
let og = document.querySelector(`meta[property='og:description']`)
let twitter = document.querySelector(`meta[property='twitter:description']`)
desc ? descriptions.desc = desc.content : descriptions.desc = null
og ? descriptions.og = og.content: descriptions.og = null
twitter ? descriptions.twitter = twitter.content : descriptions.twitter = null
return descriptions
})
// screenshot the page as a jpeg with a base64 encoding
const screenshot = await page.screenshot({
type: 'jpeg',
encoding: 'base64'
})
// close the browser
await browser.close()
// send the page details
return {
statusCode: 200,
body: JSON.stringify({
title,
screenshot,
descriptions
})
}
} catch (error) {
// if any error occurs, close the browser instance
// and send an error code
await browser.close()
return {
statusCode: 400,
body: JSON.stringify({
error
})
}
}
在上面的代码中,我们使用一个trycatch块来包装我们的代码,这样,如果出现任何问题,从await page.goto(targetURL)导航到目标 URL 开始,我们可以捕获错误并将其发送到我们的前端。提供无效的 URL 可能会发生错误。
如果 URL 有效,我们将使用 方法获取标题page.$eval(),这类似于document.querySelectorJavaScript 中的常用方法。我们传入head > title标题标签的 CSS 选择器——作为第一个参数。我们还传递一个函数el => el.textContent作为第二个参数,其中el是我们传递给函数的参数,是元素title。我们现在可以使用 获取值title.textContent。
请注意,所有这些都包含在括号 ( ()) 中,并且我们有一个|| nullafter page.$eval。这是为了在无法获取页面标题title时分配 null 。page.$eval()
要获取页面的描述,我们将使用该page.evaluate()方法,它允许我们运行一些客户端 JavaScript 并将值返回给分配的变量 — descriptions。
我们将函数作为参数传递给page.evaluate()方法。document.querySelector在我们用来获取各种元描述的函数中,例如<meta name="description" content="<site description>" />默认描述和<meta property="og:description" content="<site description>" />OpenGraph 描述。
获取到元素后,如果元素存在,则使用三元运算符获取content并添加到对象中,如果元素不存在,则将其添加到对象中。descriptionsnull
获得描述后,我们使用page.screenshot()方法截取页面的屏幕截图并使用 关闭浏览器browser.close()。
最后,我们将属性中的页面详细信息发送到一个带有ofbody的 JSON 对象。如果在前面的任何步骤中发生错误,它会在块中被捕获,我们会发送一个of和错误消息。statusCode200catchstatusCode400
让我们使用 API 测试器测试我们的功能。您可以在浏览器中安装Postman或Talend API 测试器,或使用Thunder Client 扩展,一个用于 VS Code 的 API 测试器。
您还可以使用卷曲:
curl -X POST -H "Content-Type: application/json" -d '{"paramName": "value"}' <URL>
使用命令运行函数netlify dev。
我们可以使用功能服务器的端口或 Netlify 开发服务器的默认端口发送请求:8888,以向我们的功能发送请求。我将使用包含以下对象的对象http://localhost:8888/.netlify/functions/generate-preview发送请求:POSTtargetURLbody
{
"targetURL" : "https://miracleio.me"
}
当我们发送请求时,这是我们得到的响应。
我们得到一个包含预览数据的 JSON 对象:
{
"title": "Miracleio | PortfolioX",
"screenshot": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
"descriptions": {
"desc": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
"og": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
"twitter": null
}
}
现在我们的无服务器功能可以工作了,让我们看看如何在前端使用它。
为了与我们的generate-preview功能进行交互,我们需要发送POST包含我们的targetURL.
我们将创建LinkPreview将显示正常链接的组件。这些组件将作为 props 传递它们的目标 URL。在组件安装到应用程序之前,它会向我们的无服务器功能发送一个POST请求targetURL,获取预览数据,并在我们将鼠标悬停在链接上时显示它。
首先,让我们创建我们的链接预览组件src/components/LinkPreviewer.vue。
在我们的 中,我们将通过向我们的无服务器函数发送请求并将数据保存在对象<script>中来获取链接预览数据。previewData稍后我们将在模板中使用它来显示数据:
// ./src/components/LinkPreviewer.vue
...
<script>
import { computed, onBeforeMount, ref } from '@vue/runtime-core'
export default {
// define targetURL as a prop
props: ['targetURL'],
setup(props) {
// create a reactive previewData object using ref
const previewData = ref({})
// function to send a POST request containing the targetURL
// to the serverless function
const generatePreview = async () => {
try {
const res = await fetch('/.netlify/functions/generate-preview', {
method: 'POST',
body: JSON.stringify({
targetURL : props.targetURL
})
})
const data = await res.json()
return data
} catch (err) {
console.log(err)
return null
}
}
// run function before component is mounted
onBeforeMount(async ()=>{
// run generatePreview() to get the preview data and assign to previewData
previewData.value = await generatePreview()
// use object destructuring to get the different descriptions
// from the preview data
const {desc, og, twitter} = previewData.value.descriptions
// assign only one valid value to the description property
// in the previewData object
previewData.value.description = computed(()=>(desc || og || twitter || ""))
})
// make the following entities available to the component
return { generatePreview, previewData}
}
}
</script>
在上面的代码中,我们得到了targetURL作为传递给我们组件的道具。
在 中setup(),我们props作为参数传递,以便我们访问组件道具,如targetURL.
然后,我们使用:创建一个反应peviewData对象。在一个新函数中,我们使用将包含 的请求发送到我们的无服务器函数。此函数返回响应或是否发生错误。refconst previewData = ref({})generatePreview()fetchPOSTtargetURLnull
接下来,要在挂载组件之前运行函数,我们使用钩子onBeforeMount()。我们传递一个async函数作为参数。在函数内,我们分配previewData.value给generatePreview()函数。desc, og, twitter然后从属性中获取描述 ( ) descriptions。
为了获得将在预览中显示的描述,我们将分配previewData.value.description给(desc || og || twitter || ""). 这样,第一个有值的属性被分配给description.
执行此操作以在我们的模板中显示预览数据:
<!-- ./src/components/LinkPreviewer.vue -->
<template>
<div class="inline relative">
<!-- display targetURL link -->
<a class="link underline text-blue-600"
:href="targetURL"
:target="previewData ? previewData.title : '_blank'">
{{targetURL}}
</a>
<!-- display preview data if object exists -->
<div v-if="previewData" class="result-preview absolute top-8 left-0 w-72 transform translate-y-4 opacity-0 invisible transition bg-white overflow-hidden rounded-md shadow-lg z-10">
<!-- display image using the base64 screenshot data -->
<img v-if="previewData.screenshot"
:src="`data:image/jpeg;base64,${previewData.screenshot}`"
:alt="previewData.description" />
<!-- display title and description -->
<div class="details p-4 text-left">
<h1 class=" font-extrabold text-xl"> {{previewData.title}} </h1>
<p> {{previewData.description}} </p>
</div>
</div>
</div>
</template>
<script> ... </script>
<style scoped>
.link:hover ~ .result-preview{
@apply visible opacity-100 translate-y-0;
}
</style>
在上面的代码中,为了显示我们的图像——它本质上是一个base64字符串——我们必须将字符串与图像类型和编码等数据一起传递到属性中src-""。
这就是我们的LinkPreviewer.vue组件。让我们看看它的实际效果。在./src/views/Home.vue:
<!-- ./src/views/Home.vue -->
<template>
<main class="home">
<header>
<h1>Welcome to the link previewer app!</h1>
<p>Here are some links that you can preview by hovering on them</p>
</header>
<ul class=" mb-4">
<!-- render LinkPreviewer component for each demolink -->
<li v-for="link in demoLinks" :key="link">
<link-previewer :targetURL="link" />
</li>
</ul>
<!-- input field to add new links -->
<input class=" p-2 ring ring-blue-600 rounded-lg shadow-md" type="url" @keyup.enter="addLink" required placeholder="enter valid url">
</main>
</template>
<script>
import { ref } from '@vue/reactivity'
import LinkPreviewer from '../components/LinkPreviewer.vue'
export default{
components: { LinkPreviewer },
setup(){
// demo links
const demoLinks = ref([
'http://localhost:5000',
'https://google.com',
'https://miracleio.me',
'https://miguelpiedrafita.com/'
])
// function to add new links to the demoLinks array
const addLink = ({target}) => {
demoLinks.value.push(target.value)
target.value = ""
}
return {demoLinks, addLink}
}
}
</script>
在我们的Home.vue文件中,我们基本上使用一个demoLinks链接数组来呈现一个LinkPreviewer组件列表,我们将其传递给targetURL组件的 props。
我们还有一个<input>元素,我们用它来动态地将更多LinkPreviewer组件添加到列表中。
这是我们的简单应用程序现在的样子。
甜的!我们的应用程序有效。由于我们一直在使用 Netlify CLI 在本地运行,让我们看看如何使用 CLI 部署到 Netlify。
在我们将我们的应用程序部署到 Netlify 之前,我们必须构建我们的生产应用程序:
npm run build
这将构建我们的应用程序并创建一个dist/我们可以部署到生产环境的文件夹。
接下来,我们需要登录到我们的 Netlify 帐户:
netlify deploy
这将使您在浏览器中登录到您的 Netlify 帐户。
授权应用程序后,我们可以将我们的项目链接到新站点。Netlify 会问我们一堆问题:
在此之后,Netlify 将上传我们的文件并将它们部署到我们的新站点。
或者,我们可以决定从 GitHub 部署我们的站点。您所要做的就是登录 GitHub,创建一个新的存储库,并将 URL 复制到我们新创建的存储库。
然后我们在项目文件夹中运行以下命令:
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/miracleonyenma/link-previewer.git
git push -u origin main
注意:由于身份验证问题,您可能无法从终端推送到您的存储库,并且您可能会从 Git 收到这样的消息:“对密码身份验证的支持已于 2021 年 8 月 13 日删除。请使用个人访问令牌反而。” 这意味着您必须创建一个个人访问令牌 (PAT) 并使用它来登录。为此,请转到GitHub 令牌设置并生成一个新令牌。选择您想要的所有权限。确保您能够访问回购协议。生成 PAT 后,将其复制并保存在某处。然后再次尝试该git push -u origin main命令,并在询问您的密码时粘贴您的 PAT。
将项目推送到 GitHub 后,前往 Netlify从 GitHub 创建一个新站点。
按照步骤选择存储库并输入项目的构建设置。对于我们的 Vue 项目,构建命令是npm run build,部署目录是dist.
之后,单击部署站点。
Netlify 将部署该站点,我们可以通过单击提供的部署链接来预览我们的站点。我们可以通过从顶部菜单转到功能来查看我们的功能。
您可以选择一个功能来查看更多详细信息和日志。
甜的!
这是部署在 Netlify 上的演示链接:https://lnkpreviewr.netlify.app
我们已经能够使用 Netlify 函数通过 Netlify 创建和部署无服务器函数。我们还看到了如何与 Vue 前端的功能进行交互。这一次,我们习惯于使用它来截屏并从其他站点获取数据,并用它构建一个链接预览器组件,但我们可以做的更多。使用无服务器功能,我们可以在前端做更多的事情,而不必费心去设置后端服务器。
文章原文出处:https: //www.sitepoint.com/
1685181060
In this tutorial, we’re going to see how we can deploy serverless functions alongside our front-end application and create an API that generates images and grabs metadata from links.
With serverless functions, developers can create and implement modern features and functionalities in their applications without going through the pain of setting up and managing back-end servers. The functions are hosted and deployed by cloud computing companies.
Netlify functions make creating and deploying serverless functions easy for applications hosted on Netlify.
To follow along with this tutorial, you’ll need to be familiar with JavaScript, Vue.js, Git, GitHub, and Netlify. You should also have a text editor — such as VS Code) with Vetur installed (for IntelliSense) — and a recent version of Node installed on your machine. You can install Node here. You can check your version of Node by running the command node -v
in your terminal.
You should also have an account on Netlify. You can create one if you haven’t already.
To show how we can easily set up serverless functions with our front-end application, we’ll be building an app with a custom link previewer component.
This component sends a request with a URL to our serverless function. The function then uses Puppeteer to get metadata from the target site using the URL and to generate a screenshot of the site.
The function sends the metadata and screenshots back to the component on our front-end to display it as a link preview in the application.
Here’s the link to the example project deployed on Netlify. And here’s the GitHub Repo to follow along.
We’re going to create a Vue 3 application using Vue CLI. We’ll also install and set up Tailwind CSS, a utility-first CSS framework that provides classes we can use for our app without having to write a lot of custom CSS.
To quickly scaffold a Vue application, we’ll use Vue CLI. To install Vue CLI, run:
npm install -g @vue/cli
Once the CLI has been installed, we can create a project by running:
vue create link-previewer
This will prompt us to pick a preset for our installation. We’ll select “Manually select features” so that we can pick the features we need. Here are the options I selected:
Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, PWA, Router, Vuex, Linter
? Choose a version of Vue.js that you want to start the project with: 3.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
After selecting these options, we’ll be asked if we want to save the options as a preset for later use. Select Y
(yes) or N
(no) and continue with the installation.
Run cd link-previewer
to enter the newly created project.
To install Tailwind, we’ll use the PostCSS 7 compatibility build, since Tailwind depends on PostCSS 8 — which at the time of writing is not yet supported by Vue 3. Uninstall any previous Tailwind installation and re-install the compatibility build:
npm uninstall tailwindcss postcss autoprefixer
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
Next, generate tailwind.config.js
and postcss.config.js
files:
npx tailwindcss init -p
This will create a minimal tailwind.config.js
file at the root of the project.
In the tailwind.config.js
file, configure the purge
option with the paths to all of the pages and components so Tailwind can tree-shake unused styles in production builds:
// ./tailwind.config.js
module.exports = {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
...
}
Create the ./src/assets/css/main.css
file and use the @tailwind
directive to include Tailwind’s base
, components
, and utilities
styles:
/* ./src/assets/css/main.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body{
@apply bg-gray-50;
}
Tailwind will swap these directives out at build time with all of the styles it generates based on the configured design system.
Finally, ensure the CSS file is being imported in the ./src/main.js
file:
// ./src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import './assets/css/main.css'
createApp(App).use(store).use(router).mount('#app')
And that’s it, we can run our server:
npm run serve
Now that the app is running, if we go to the URL provided, we should see the default demo app for Vue and see that Tailwind’s preflight base styles have been applied.
For a smoother development experience, install the Tailwind CSS Intellisense extension for VS Code.
Here’s an overview of what our project folder should look like:
link-previewer/
├─ functions/
│ ├─ generate-preview.js
│ └─ hello.js
├─ public/
│ ├─ favicon.ico
│ ├─ img/
│ │ └─ icons/
│ ├─ index.html
│ └─ robots.txt
├─ src/
│ ├─ main.js
│ ├─ App.vue
│ ├─ registerServiceWorker.js
│ ├─ assets/
│ │ ├─ css/
│ │ │ └─ main.css
│ │ └─ logo.png
│ ├─ components/
│ │ └─ LinkPreviewer.vue
│ ├─ router/
│ │ └─ index.js
│ ├─ store/
│ │ └─ index.js
│ └─ views/
│ ├─ About.vue
│ └─ Home.vue
├─ .git
├─ .gitignore
├─ .browserslistrc
├─ .eslintrc.js
├─ babel.config.js
├─ netlify.toml
├─ package-lock.json
├─ package.json
├─ postcss.config.js
├─ README.md
└─ tailwind.config.js
Netlify Functions is a Netlify product that simplifies the process of creating and deploying serverless functions. According to the product’s home page, it’s used to:
Deploy server-side code that works as API endpoints, runs automatically in response to events, or processes more complex jobs in the background.
A basic Netlify Function file exports a handler method with the following syntax:
exports.handler = async function(event, context){
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World!"})
}
}
Netlify provides the event
and context
parameters when the function is called/invoked. When a function’s endpoint is called, the handler
receives an event
object like this:
{
"path": "Path parameter (original URL encoding)",
"httpMethod": "Incoming request’s method name",
"headers": {Incoming request headers},
"queryStringParameters": {Query string parameters},
"body": "A JSON string of the request payload",
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encoded"
}
The context
parameter, on the other hand, includes information about the context in which the function was called.
Within the function, we’re returning an object with two important properties:
statusCode
, which is 200
in this casebody
, which is a stringified object.The function will be called from our site at /.netlify/functions/hello
and on success, it would return the 200 status code and the message, “Hello, World!”.
Now that we have an idea of how Netlify functions work, let’s see them in practice.
To create our first Netlify function, we’ll create a new file functions/hello.js
in the project directory and enter the following:
// functions/hello.js
exports.handler = async function(event, context){
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World!"})
}
}
Once we’ve created the function file, we have to make some necessary configurations in order for us to run our function locally.
We’ll create a netlify.toml
file at the root of our project folder that will tell Netlify where to find our functions:
# ./netlify.toml
[functions]
directory = "./functions"
Netlify will now locate and deploy the functions in the functions
folder at build time.
To run our functions locally without having to deploy to Netlify, we need to install Netlify CLI. The CLI allows us to deploy our projects with some great Netlify features locally.
To install the CLI, make sure you have Node.js version 10 or later, then run:
npm install netlify-cli -g
This installs Netlify CLI globally, so we can run netlify
commands from any directory. To get the version, usage, and so on, we can run:
netlify
To run our project locally with Netlify CLI, stop the dev server (if it’s active), then run:
netlify dev
And here’s what we should see:
If you look closely, you’ll see a few things going on there:
Netlify tries to inject environment variables from our .env
files into the build process, which can then be accessed by our Netlify Functions. In this case, we have no .env
file, so it loads the defaults defined in process
.
Secondly, it loads or deploys our functions located in the functions directory. The Functions server is deployed on a different and random port — 36647
.
Lastly, it automatically detects what framework the application is built with and runs the necessary build processes to deploy the application. In this case, you can see “Starting Netlify Dev with Vue.js”. It also supports React and other popular frameworks.
Netlify then starts our development server on http://localhost:8888
.
Now that our server has started and our functions arevloaded, we can call/invoke it. By default, we can access our functions using this route: /.netlify/functions/<function name>
.
One important thing to note is that we don’t need to specify the port where our Functions server is running. We can use the default route above to communicate with our Functions server. Netlify automatically resolves the URL and port behind the scenes.
if we send a GET
request to http://localhost:8888/.netlify/functions/hello, we should get a response of {"message":"Hello, World!"}
.
Great! Our first serverless function works!
Now that our Netlify function works, we can begin building the preview API. Here’s a quick rundown of what our Functions API is going to do:
<title>
and <meta>
tags for the description of the target pageNow that we have a basic idea of what our Functions API is going to do, we can start creating Functions. Let’s start by installing and setting up Puppeteer for Netlify Functions.
Puppeteer is a Node library that provides a high-level API to control headless Chrome or Chromium browsers. It can also be configured to use the full (non-headless) chrome or Chromium. You can do most things that you can do manually in the browser using Puppeteer. More about Puppeteer can be found in the Puppeteer documentation.
To get started with Puppeteer, we’ll install it in our project.
Puppeteer downloads a recent version of Chromium (~170MB macOS, ~282MB Linux, ~280MB Windows) that’s guaranteed to work with the API.
We can’t use the full puppeteer
package for production. This is because Netlify Functions has a maximum size of 50MB, and the Chromium package is too large.
Thanks to this very useful article by Ire Aderinokun, we can still work with Puppeteer both locally and in production. Here’s what we have to do:
Install puppeteer
as a development dependency* for local deployment:
npm i puppeteer --save-dev
For Puppeteer to work both locally and in production, we have to install puppeteer-core and chrome-aws-lambda
as production dependencies.
You can check out the difference between puppeteer
and puppeteer-core
here. The main difference, though, is that puppeteer-core
doesn’t automatically download Chromium when installed.
Since puppeteer-core
doesn’t download a browser, we’ll install chrome-aws-lambda, a “Chromium Binary for AWS Lambda and Google Cloud Functions” which we can use in our Netlify Functions. These are the packages that will work in production:
npm i puppeteer-core chrome-aws-lambda --save-prod
Now that we’ve installed our packages, let’s create our function.
If Puppeteer installing a full browser to work with locally is going to be an issue, that may be due to slow network or bandwidth concerns. There’s a workaround, which is to use our already installed Chrome or Chromium browser for Puppeteer.
What we need is the path to the browser in our local machine. We’ll use this as our executablePath
, which we’ll pass to the puppeteer.launch()
method. This tells Puppeteer where to find the browser executable file.
If you don’t know exactly where to find the executable path, open up your browser and go to chrome://version/ to display the version of chrome.
Copy the path and create a .env
file in the root of the project.
# ./.env
EXCECUTABLE_PATH=<path to chrome>
To get the content of the .env
file, we’ll install another package — dotenv
:
npm install dotenv
Now that we’ve successfully installed the package, let’s create the Netlify function.
Create a new file, ./functions/generate-preview.js
:
// ./functions/generate-preview.js
const chromium = require('chrome-aws-lambda')
const puppeteer = require('puppeteer-core')
exports.handler = async function (event, context) {
// parse body of POST request to valid object and
// use object destructuring to obtain target url
const { targetURL } = JSON.parse(event.body)
// launch browser
const browser = await puppeteer.launch({
args: chromium.args,
// get path to browser
executablePath: process.env.EXCECUTABLE_PATH || await chromium.executablePath,
headless: true
})
// open new page in browser
const page = await browser.newPage()
// set the viewport of the page
await page.setViewport({
width: 768,
height: 425,
deviceScaleFactor: 1
})
// set the prefers-color-scheme to dark
await page.emulateMediaFeatures([
{name: 'prefers-color-scheme', value:'dark'}
])
// navigate to target URL and get page details and screenshot
try{
...
}
}
In the code above, we’re doing a number of things. First, we obtain the targetURL
from the request payload in event.body
. This would be sent with a POST
request.
Next, we launch the browser using the chrome-aws-lambda
package. We do this using the puppeteer.launch()
method. This method takes in an object as an argument with a few optional properties. An important property we pass to this method is the executablePath
.
We assign the executablePath
to process.env.EXCECUTABLE_PATH || await chromium.executablePath
enabling the package to locate the available headless browser to launch.
Once the browser is launched, we open a new page in the browser using the browser.newPage()
method. We also set our desired browser viewport for the page using the page.setViewport()
method.
Notice that we’re using the await
keyword when running any function. This is because Puppeteer works asynchronously and some functions might take some time before they execute.
We can also do things like define the media features of the page with Puppeteer using the page.emulateMediaFeatures()
method, which takes an array of media feature objects. That’s how we set the prefers-color-scheme
to dark
.
Next, we’ll navigate to the target URL and get our title, description and screenshot:
// ./functions/generate-preview.js
...
// navigate to target URL and get page details and screenshot
try {
// navigate to the targetURL
await page.goto(targetURL)
// get the title from the newly loaded page
const title = (await page.$eval(`head > title`, el => el.textContent) || null)
// get the descriptions of the page using their CSS selectors
const descriptions = await page.evaluate(() => {
let descriptions = {}
let desc = document.querySelector(`meta[name='description']`)
let og = document.querySelector(`meta[property='og:description']`)
let twitter = document.querySelector(`meta[property='twitter:description']`)
desc ? descriptions.desc = desc.content : descriptions.desc = null
og ? descriptions.og = og.content: descriptions.og = null
twitter ? descriptions.twitter = twitter.content : descriptions.twitter = null
return descriptions
})
// screenshot the page as a jpeg with a base64 encoding
const screenshot = await page.screenshot({
type: 'jpeg',
encoding: 'base64'
})
// close the browser
await browser.close()
// send the page details
return {
statusCode: 200,
body: JSON.stringify({
title,
screenshot,
descriptions
})
}
} catch (error) {
// if any error occurs, close the browser instance
// and send an error code
await browser.close()
return {
statusCode: 400,
body: JSON.stringify({
error
})
}
}
In the code above, we’re using a trycatch
block to wrap our code so that, if anything goes wrong, starting from await page.goto(targetURL)
, which navigates to the target URL, we can catch the error and send it to our front end. An error might occur through providing an invalid URL.
If the URL was valid, we get the title using the page.$eval()
method, which is similar to the usual document.querySelector
method in JavaScript. We pass in the CSS selector — head > title
— of the title tag as the first argument. We also pass a function el => el.textContent
as the second argument, where el
is a parameter we pass to the function and is the title
element. We can now get the value using title.textContent
.
Notice that all this is wrapped in a parentheses (()
) and we have a || null
after page.$eval
. This is so that title
is assigned null if page.$eval()
fails to get the title of the page.
To get the descriptions of the page, we’ll use the page.evaluate()
method, which allows us to run some client-side JavaScript and return a value to the assigned variable — descriptions
.
We pass a function as and argument to the page.evaluate()
method. Within the function we use document.querySelector
to get the various meta descriptions, such as <meta name="description" content="<site description>" />
for the default description, and <meta property="og:description" content="<site description>" />
for the OpenGraph description.
After getting the elements, we use ternary operators to get the content
and add it to the descriptions
object if the elements exist, or null
if the element doesn’t exist.
Once we’ve gotten the descriptions, we take a screenshot of the page using the page.screenshot()
method and close the browser with browser.close()
.
Finally, we’re sending the page details in the body
property a JSON object with a statusCode
of 200
. If an error occurs in any of the previous steps, it’s caught in the catch
block and we send a statusCode
of 400
and the error message instead.
Let’s test our function using an API tester. You can install Postman or Talend API tester in your browser or use the Thunder Client extension, an API tester for VS Code.
You can also use cURL:
curl -X POST -H "Content-Type: application/json" -d '{"paramName": "value"}' <URL>
Run the function using the netlify dev
command.
We can send a request using the port for the functions server or the default :8888
port for the Netlify dev server to send a request to our functions. I’ll be using http://localhost:8888/.netlify/functions/generate-preview
to send a POST
request with an object containing the targetURL
in the body
:
{
"targetURL" : "https://miracleio.me"
}
When we send the request, here’s the response we get.
We get a JSON object containing our preview data:
{
"title": "Miracleio | PortfolioX",
"screenshot": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
"descriptions": {
"desc": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
"og": "Designer & Frontend Developer portfolio site. Built by Miracleio with love ❤",
"twitter": null
}
}
Now that our serverless function works, let’s see how we can use it in our front end.
In order to interact with our generate-preview
function, we’ll need to send POST
requests containing our targetURL
.
We’ll create LinkPreview
components that will display normal links. These components will be passed their target URLs as props. Before the component is mounted in the application, it’ll send a POST
request with the targetURL
to our serverless function, get the preview data, and display it once we hover on the link.
First, let’s create our link preview component src/components/LinkPreviewer.vue
.
In our <script>
, we’ll get the link preview data by sending a request to our serverless function and save the data in previewData
object. We’ll use this later in our template to display the data:
// ./src/components/LinkPreviewer.vue
...
<script>
import { computed, onBeforeMount, ref } from '@vue/runtime-core'
export default {
// define targetURL as a prop
props: ['targetURL'],
setup(props) {
// create a reactive previewData object using ref
const previewData = ref({})
// function to send a POST request containing the targetURL
// to the serverless function
const generatePreview = async () => {
try {
const res = await fetch('/.netlify/functions/generate-preview', {
method: 'POST',
body: JSON.stringify({
targetURL : props.targetURL
})
})
const data = await res.json()
return data
} catch (err) {
console.log(err)
return null
}
}
// run function before component is mounted
onBeforeMount(async ()=>{
// run generatePreview() to get the preview data and assign to previewData
previewData.value = await generatePreview()
// use object destructuring to get the different descriptions
// from the preview data
const {desc, og, twitter} = previewData.value.descriptions
// assign only one valid value to the description property
// in the previewData object
previewData.value.description = computed(()=>(desc || og || twitter || ""))
})
// make the following entities available to the component
return { generatePreview, previewData}
}
}
</script>
In the code above, we get the targetURL
as a prop that will be passed into our component.
In the setup()
, we pass props
as an argument in order for us to access component props like targetURL
.
Then, we create a reactive peviewData
object using ref
: const previewData = ref({})
. In a new generatePreview()
function, we’re using fetch
to send a POST
request containing the targetURL
to our serverless function. This function returns the response or null
if an error occurs.
Next, to run the function before the component is mounted, we use the onBeforeMount()
hook. We pass an async
function as an argument. Within the function, we assign previewData.value
to the generatePreview()
function. The descriptions (desc, og, twitter
) are then gotten from the descriptions
property.
To get the description that will be displayed in the preview, we’ll assign previewData.value.description
to (desc || og || twitter || "")
. This way, the first property with a value gets assigned to the description
.
Do this to display the preview data in our template:
<!-- ./src/components/LinkPreviewer.vue -->
<template>
<div class="inline relative">
<!-- display targetURL link -->
<a class="link underline text-blue-600"
:href="targetURL"
:target="previewData ? previewData.title : '_blank'">
{{targetURL}}
</a>
<!-- display preview data if object exists -->
<div v-if="previewData" class="result-preview absolute top-8 left-0 w-72 transform translate-y-4 opacity-0 invisible transition bg-white overflow-hidden rounded-md shadow-lg z-10">
<!-- display image using the base64 screenshot data -->
<img v-if="previewData.screenshot"
:src="`data:image/jpeg;base64,${previewData.screenshot}`"
:alt="previewData.description" />
<!-- display title and description -->
<div class="details p-4 text-left">
<h1 class=" font-extrabold text-xl"> {{previewData.title}} </h1>
<p> {{previewData.description}} </p>
</div>
</div>
</div>
</template>
<script> ... </script>
<style scoped>
.link:hover ~ .result-preview{
@apply visible opacity-100 translate-y-0;
}
</style>
In the above code, in order to display our image — which is essentially a base64
string — we have to pass the string along with data like the image type and encoding into the src-""
attribute.
That’s about it for our LinkPreviewer.vue
component. Let’s see it in action. In ./src/views/Home.vue
:
<!-- ./src/views/Home.vue -->
<template>
<main class="home">
<header>
<h1>Welcome to the link previewer app!</h1>
<p>Here are some links that you can preview by hovering on them</p>
</header>
<ul class=" mb-4">
<!-- render LinkPreviewer component for each demolink -->
<li v-for="link in demoLinks" :key="link">
<link-previewer :targetURL="link" />
</li>
</ul>
<!-- input field to add new links -->
<input class=" p-2 ring ring-blue-600 rounded-lg shadow-md" type="url" @keyup.enter="addLink" required placeholder="enter valid url">
</main>
</template>
<script>
import { ref } from '@vue/reactivity'
import LinkPreviewer from '../components/LinkPreviewer.vue'
export default{
components: { LinkPreviewer },
setup(){
// demo links
const demoLinks = ref([
'http://localhost:5000',
'https://google.com',
'https://miracleio.me',
'https://miguelpiedrafita.com/'
])
// function to add new links to the demoLinks array
const addLink = ({target}) => {
demoLinks.value.push(target.value)
target.value = ""
}
return {demoLinks, addLink}
}
}
</script>
In our Home.vue
file, we’re basically using a demoLinks
array of links to render a list of LinkPreviewer
components, which we pass to the targetURL
props of the component.
We also have an <input>
element, which we use to dynamically add more LinkPreviewer
components to the list.
Here’s what our simple app looks like now.
Sweet! Our app works. Since we’ve been running locally using Netlify CLI, let’s see how we can deploy to Netlify using the CLI.
Before we deploy our app to Netlify, we have to build our app for production:
npm run build
This will build our app and create a dist/
folder we can deploy to production.
Next, we need to log in to our Netlify account:
netlify deploy
This will log you into your Netlify account in your browser.
After authorizing the application, we can link our project to a new site. Netlify will ask us a bunch of questions:
<your team>
.<site name>
.dist
.After this, Netlify will upload our files and deploy them to our new site.
Alternatively, we can decide to deploy our site from GitHub. All you have to do is to log in to GitHub, create a new repository, and copy the URL to our newly created repo.
We then run the following command in our project folder:
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/miracleonyenma/link-previewer.git
git push -u origin main
Note: you may not be able to push to your repo from your terminal because of authentication issues, and you might get a message from Git like this: “Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.” This means that you have to create a personal access token (PAT) and use it to log in. To do that, go to GitHub token settings and generate a new token. Select all the permissions you want. Make sure you’re able to access repos. After generating your PAT, copy it and save it somewhere. Then try the git push -u origin main
command again and paste in your PAT when asked for your password.
Once we’ve pushed the project to GitHub, head over to Netlify to create a new site from GitHub.
Follow the steps to choose a repository and enter the build settings for your project. For our Vue project, the build command is npm run build
, and the deploy directory is dist
.
After that, click on Deploy site.
Netlify will deploy the site, and we can preview our site by clicking on the deploy link provided. We can see our functions by going over to Functions from the top menu.
You can select a function to view more details and logs.
Sweet!
Here’s the link to the demo deployed on Netlify: https://lnkpreviewr.netlify.app
We’ve been able to create and deploy serverless functions with Netlify using Netlify functions. We’ve also seen how we can interact with the functions from our Vue front end. This time, we used to it screenshot and get data from other sites and built a link previewer component with it, but we can do so much more. With serverless functions, we can do more on the front end without having to bother on setting up a back-end server.
Original article source at: https://www.sitepoint.com/
1675669889
Learn the basics of web scraping in JavaScript and Node.js using Puppeteer in this tutorial. JavaScript and Node.js offers various libraries that make web scraping easier. For simple data extraction, you can use Axios to fetch an API responses or a website HTML.
Welcome to the world of web scraping! Have you ever needed data from a website but found it hard to access it in a structured format? This is where web scraping comes in.
Using scripts, we can extract the data we need from a website for various purposes, such as creating databases, doing some analytics, and even more.
Disclaimer: Be careful when doing web scraping. Always make sure you're scraping sites that allow it, and performing this activity within ethical and legal limits.
JavaScript and Node.js offers various libraries that make web scraping easier. For simple data extraction, you can use Axios to fetch an API responses or a website HTML.
But if you're looking to do more advanced tasks including automations, you'll need libraries such as Puppeteer, Cheerio, or Nightmare (don't worry the name is nightmare, but it's not that bad to use 😆).
I'll introduce the basics of web scraping in JavaScript and Node.js using Puppeteer in this article. I structured the writing to show you some basics of fetching information on a website and clicking a button (for example, moving to the next page).
At the end of this introduction, I'll recommend ways to practice and learn more by improving the project we just created.
Before diving in and scraping our first page together using JavaScript, Node.js, and the HTML DOM, I'd recommend having a basic understanding of these technologies. It'll improve your learning and understanding of the topic.
Let's dive in! 🤿
New project...new folder! First, create the first-puppeteer-scraper-example
folder on your computer. It'll contain the code of our future scraper.
mkdir first-puppeteer-scraper-example
Create a new project folder using mkdir
Now, it's time to initialize your Node.js repository with a package.json file. It's helpful to add information to the repository and NPM packages, such as the Puppeteer library.
npm init -y
Initialize the package.json
file using the npm init
command
After typing this command, you should find this package.json
file in your repository tree.
{
"name": "first-puppeteer-scraper-example",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"puppeteer": "^19.6.2"
},
"type": "module",
"devDependencies": {},
"description": ""
}
package.json
initialized with the npm init -y
command
Before proceeding, we must ensure the project is configured to handle ES6 features. To do so, you can add the "types": "module"
instruction at the end of the configuration.
{
"name": "first-puppeteer-scraper-example",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"puppeteer": "^19.6.2"
},
"type": "module",
"description": "",
"types": "module"
}
package.json
file after enabling the ES6 features
The last step of our scraper initialization is to install the Puppeteer library. Here's how:
npm install puppeteer
Install Puppeteer with the npm install
command
Wow! We're there – we're ready to scrape our first website together. 🤩
In this article, we'll use the ToScrape website as our learning platform. This online sandbox provides two projects specifically designed for web scraping, making it a great starting point to learn the basics such as data extraction and page navigation.
For this beginner's introduction, we'll specifically focus on the Quotes to Scrape website.
In the project repository root, you can create an index.js
file. This will be our application entry point.
To keep it simple, our script consists of one function in charge of getting the website's quotes (getQuotes
).
In the function's body, we will need to follow different steps:
puppeteer.launch
(it'll instantiate a browser
variable that we'll use for manipulating the browser)browser.newPage
(it'll instantiate a page
variable that we'll use for manipulating the page)http://quotes.toscrape.com/
with page.goto
Here's the commented version of the initial script:
import puppeteer from "puppeteer";
const getQuotes = async () => {
// Start a Puppeteer session with:
// - a visible browser (`headless: false` - easier to debug because you'll see the browser in action)
// - no default viewport (`defaultViewport: null` - website page will in full width and height)
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
});
// Open a new page
const page = await browser.newPage();
// On this new page:
// - open the "http://quotes.toscrape.com/" website
// - wait until the dom content is loaded (HTML is ready)
await page.goto("http://quotes.toscrape.com/", {
waitUntil: "domcontentloaded",
});
};
// Start the scraping
getQuotes();
What do you think of running our scraper and seeing the output? Let's do it with the command below:
node index.js
Start our Node.js application with the node index.js
command
After doing this, you should have a brand new browser application started with a new page and the website Quotes to Scrape loaded onto it. Magic, isn't it? 🪄
Quotes to Scrape homepage loaded by our initial script
Note: For this first iteration, we're not closing the browser. This means you will need to close the browser to stop the running application.
Whenever you want to scrape a website, you'll have to play with the HTML DOM. What I recommend is to inspect the page and start navigating the different elements to find what you need.
In our case, we'll follow the baby step principle and start fetching the first quote, author, and text.
After browsing the page HTML, we can notice a quote is encapsulated in a <div>
element with a class name quote
(class="quote"
). This is important information because the scraping works with CSS selectors (for example, .quote).
Browser inspector with the first quote <div>
selectedAn example of how each quote is rendered in the HTML
Now that we have this knowledge, we can return to our getQuotes
function and improve our code to select the first quote and extract its data.
We will need to add the following after the page.goto
instruction:
page.evaluate
(it'll execute the function passed as a parameter in the page context and returns the result)document.querySelector
(it'll fetch the first <div>
with the classname quote
and returns it)quote.querySelector
(it'll extract the elements with the classname text
and author
under <div class="quote">
and returns them)Here's the updated version with detailed comments:
import puppeteer from "puppeteer";
const getQuotes = async () => {
// Start a Puppeteer session with:
// - a visible browser (`headless: false` - easier to debug because you'll see the browser in action)
// - no default viewport (`defaultViewport: null` - website page will in full width and height)
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
});
// Open a new page
const page = await browser.newPage();
// On this new page:
// - open the "http://quotes.toscrape.com/" website
// - wait until the dom content is loaded (HTML is ready)
await page.goto("http://quotes.toscrape.com/", {
waitUntil: "domcontentloaded",
});
// Get page data
const quotes = await page.evaluate(() => {
// Fetch the first element with class "quote"
const quote = document.querySelector(".quote");
// Fetch the sub-elements from the previously fetched quote element
// Get the displayed text and return it (`.innerText`)
const text = quote.querySelector(".text").innerText;
const author = quote.querySelector(".author").innerText;
return { text, author };
});
// Display the quotes
console.log(quotes);
// Close the browser
await browser.close();
};
// Start the scraping
getQuotes();
Something interesting to point out is that the function name for selecting an element is the same as in the browser inspect. Here's an example:
After running the document.querySelector
instruction in the browser inspector, we have the first quote as an output (like on Puppeteer)
Let's run our script one more time and see what we have as an output:
{
text: '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
author: 'Albert Einstein'
}
Output of our script after running node index.js
We did it! Our first scraped element is here, right in the terminal. Now, let's expand it and fetch all the current page quotes. 🔥
Now that we know how to fetch one quote, let's trick our code a bit to get all the quotes and extract their data one by one.
Previously we used document.getQuerySelector
to select the first matching element (the first quote). To be able to fetch all quotes, we will need the document.querySelectorAll
function instead.
We'll need to follow these steps to make it work:
document.getQuerySelector
with document.querySelectorAll
(it'll fetch all <div>
elements with the classname quote
and return them)Array.from(quoteList)
(it'll ensure the list of quotes is iterable)text
and author
under <div class="quote">
for each quote)Here's the code update:
import puppeteer from "puppeteer";
const getQuotes = async () => {
// Start a Puppeteer session with:
// - a visible browser (`headless: false` - easier to debug because you'll see the browser in action)
// - no default viewport (`defaultViewport: null` - website page will be in full width and height)
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
});
// Open a new page
const page = await browser.newPage();
// On this new page:
// - open the "http://quotes.toscrape.com/" website
// - wait until the dom content is loaded (HTML is ready)
await page.goto("http://quotes.toscrape.com/", {
waitUntil: "domcontentloaded",
});
// Get page data
const quotes = await page.evaluate(() => {
// Fetch the first element with class "quote"
// Get the displayed text and returns it
const quoteList = document.querySelectorAll(".quote");
// Convert the quoteList to an iterable array
// For each quote fetch the text and author
return Array.from(quoteList).map((quote) => {
// Fetch the sub-elements from the previously fetched quote element
// Get the displayed text and return it (`.innerText`)
const text = quote.querySelector(".text").innerText;
const author = quote.querySelector(".author").innerText;
return { text, author };
});
});
// Display the quotes
console.log(quotes);
// Close the browser
await browser.close();
};
// Start the scraping
getQuotes();
As an end result, if we run our script one more time, we should have a list of quotes as an output. Each element of this list should have a text and an author property.
[
{
text: '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
author: 'Albert Einstein'
},
{
text: '“It is our choices, Harry, that show what we truly are, far more than our abilities.”',
author: 'J.K. Rowling'
},
{
text: '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”',
author: 'Albert Einstein'
},
{
text: '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”',
author: 'Jane Austen'
},
{
text: "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”",
author: 'Marilyn Monroe'
},
{
text: '“Try not to become a man of success. Rather become a man of value.”',
author: 'Albert Einstein'
},
{
text: '“It is better to be hated for what you are than to be loved for what you are not.”',
author: 'André Gide'
},
{
text: "“I have not failed. I've just found 10,000 ways that won't work.”",
author: 'Thomas A. Edison'
},
{
text: "“A woman is like a tea bag; you never know how strong it is until it's in hot water.”",
author: 'Eleanor Roosevelt'
},
{
text: '“A day without sunshine is like, you know, night.”',
author: 'Steve Martin'
}
]
Output of our script after running node index.js
Good job! All the quotes from the first page are now scraped by our script. 👏
Our script is now able to fetch all the quotes for one page. What would be interesting is clicking on the "Next page" at the page bottom and doing the same on the second page.
"Next" button at the Quotes to Scrape page bottom
Back to our browser inspect, and let's find how we can target this element using CSS selectors.
As we can notice, the next button is placed under an unordered list <ul>
with a pager
classname (<ul class="pager">
). This list has an element <li>
with a next
classname (<li class="next">
). Finally, there is a link anchor <a>
that links to the second page (<a href="/page/2/">
).
In CSS, if we want to target this specific link there are different ways to do that. We can do:
.next > a
: but, it's risky because if there is an other element with .next
as a parent element containing a link, it'll click on it..pager > .next > a
: safer, because we make sure the link should be inside the .pager
parent element under the .next
element. There is a low risk of having this hierarchy more than once.An example of how the "Next" button is rendered in the HTML
To click this button, at the end of our script after the console.log(quotes);
, you can add the following: await page.click(".pager > .next > a");
.
Since we're now closing the browser page with await browser.close();
after all instructions are done, you need to comment on this instruction to see the second page opened in the scraper browser.
It's temporary and for testing purposes, but the end of our getQuotes
function should look like this:
// Display the quotes
console.log(quotes);
// Click on the "Next page" button
await page.click(".pager > .next > a");
// Close the browser
// await browser.close();
After this, if you run our scraper again, after processing all instructions, your browser should stop on the second page:
Quotes to Scrape second page loaded after clicking the "Next" button
Congrats on reaching the end of this introduction to scraping with Puppeteer! 👏
Now it's your turn to improve the scraper and make it get more data from the Quotes to Scrape website. Here's a list of potential improvements you can make:
Feel free to be creative and do any other things you see fit 🚀
Check out the latest version of our scraper on GitHub! You're free to save, fork, or utilize it as you see fit.
=> First Puppeteer Scraper (example)
I hope this article gave you a valuable introduction to web scraping using JavaScript and Puppeteer. Writing this was a pleasure, and I hope you found it informative and enjoyable.
Join me on Twitter for more content like this. I regularly share content to help you grow your web development skills and would love to have you join the conversation. Let's learn, grow, and inspire each other along the way!
Original article source at https://www.freecodecamp.org
#javascript #puppeteer #webscraping #node
1673600160
Chromda is an AWS Lambda function for serverless capturing screenshots of websites.
Provided you already have AWS credentials for Serverless, do:
git clone https://github.com/luisfarzati/chromda
cd chromda
git submodule update --init
npm install
Edit the serverless.yml
file and change the example bucket name with one of your own:
# serverless.yml
custom:
s3Bucket: <your bucket name>
Deploy the function into your AWS account:
npm run deploy
Open the AWS Lambda Console and create the following test event:
{
"source": "aws.events",
"time": "1970-01-01T00:00:00Z",
"detail": {
"url": "https://www.nytimes.com"
}
}
Click Test, wait a few seconds (it might take around 8-10 secs), then you should see a response like:
{
"url": "https://<your bucket name>.s3.amazonaws.com/<uuid>.png"
}
The function accepts different kind of events, extracting the data from the proper body attribute as follows:
Event | Body is extracted from |
---|---|
SNS Message Event | .Records[0].Sns.Message |
SQS Message Event | .Records[0].body |
API Gateway Message Event | .body |
CloudWatch Events Message Event | .detail |
{
// required
"url": "https://google.com",
// optional - valid options: page, viewport, element
// default: viewport
"capture": "page",
// selector of element to capture
// required if capture: element
"selector": ".container",
// optional - S3 key for the image file
// default: uuid()
"s3key": "test.png",
// optional - selectors of elements to exclude
"exclude": [".ad", "video"],
// optional - styles to override
// see Puppeteer.addStyleTag
"styles": [
{
"content": "body { color: #f00; }"
}
],
// optional - puppeteer options
"puppeteer": {
// see Puppeteer.goto options
"navigation": {
"timeout": 30000,
"waitUntil": ["domcontentloaded", "networkidle2"]
},
// see Puppeteer.screenshot options
"screenshot": {
"type": "jpeg",
"quality": 50,
"omitBackground": false
},
// viewport size, overrides env defaults
"viewport": {
"width": 1200,
"height": 2000
}
}
}
Name | Default |
---|---|
S3_BUCKET* | |
S3_REGION* | |
S3_ACL | "public-read" |
CHROMIUM_ARGS | "[]" |
TIMEOUT | "30000" |
IGNORE_HTTPS_ERRORS | "false" |
VIEWPORT_WIDTH | "1920" |
VIEWPORT_HEIGHT | "1200" |
DEVICE_SCALE_FACTOR | "1" |
IS_MOBILE | "false" |
IS_LANDSCAPE | "false" |
# serverless.yml
# ...
custom:
s3Bucket: <your bucket name>
provider:
# ...
layers:
# Replace <version> with the latest version of chrome-aws-lambda-layer
# It depends on the region you are deploying.
# https://github.com/shelfio/chrome-aws-lambda-layer#available-regions
- arn:aws:lambda:${self:provider.region}:764866452798:layer:chrome-aws-lambda:<version>
functions:
captureScreenshot:
# ...
environment:
# configure the environment variables
VIEWPORT_WIDTH: "1920"
VIEWPORT_HEIGHT: "1200"
# ...
events:
# add any of the supported event source(s) you want to use
# the provided example uses SNS
- sns:
arn: !Ref chromdaTopic
topicName: ${self:custom.snsTopic}
resources:
# following the example, we provision an SNS topic
chromdaTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: ${self:custom.snsTopic}
AWS X-Ray support is provided and there are segments for Puppeteer navigation and screenshot:
Author: luisfarzati
Source Code: https://github.com/luisfarzati/chromda
License: MIT license
1670869680
Have you ever needed to generate an image from your HTML? Whether it be for design purposes or for marketing purposes with social media, knowing how to get a screenshot of your HTML design without manually taking the screenshot can be a great thing.
A use-case I was interested in was around feature graphics for each of my blog posts. Sure I could open a graphic design tool like Affinity Photo, or use the same feature graphic for every tutorial, but what if I wanted to automatically generate them based on certain criteria?
In this tutorial we’re going to see how to use Puppeteer to take screenshots of our HTML through a headless Gulp task.
To get an idea of what we want to accomplish, take the following as an example:
The above image was created entirely with HTML and could satisfy my use case of being a feature image for a tutorial on the blog. In fact, I got the idea after seeing similar feature graphics on DEV.to articles. While I don’t know what their strategy is, I thought I’d invent my own.
Before we dive into the process of taking screenshots, we should come up with some HTML to capture. To be clear, any HTML elements would work, but let’s focus on being creative, matching the example I presented earlier.
Create a project with an index.html file, a style.css file, and a few image files. For this particular example, we’ll being using a lot of Flexbox.
Open the project’s style.css file and include the following code:
html, body {
height: 100vh;
margin: 0;
display: flex;
width: 100vw;
}
#container {
flex: 1;
display: flex;
flex-direction: column;
border: 10px solid black;
background-color: #931c22;
padding: 25px;
color: #FFFFFF;
}
#body {
flex-grow: 1;
font-size: 4rem;
display: flex;
align-items: center;
text-transform: uppercase;
font-weight: bold;
line-height: 6rem;
}
#footer {
display: flex;
flex-grow: 0;
flex-direction: row;
align-items: center;
}
#author {
flex-grow: 1;
display: flex;
flex-direction: row;
align-items: center;
}
#author-img {
width: 75px;
height: 75px;
margin-right: 25px;
border: 2px solid #FFFFFF;
}
#author-name {
font-size: 2.5rem;
margin-right: 10px;
text-transform: uppercase;
}
#brand-icon {
width: 75px;
height: 75px;
border: 2px solid #FFFFFF;
}
I am by no means a CSS or design expert, so the CSS above may be able to be optimized. However, everything worked as expected when combined with my HTML.
With the CSS in place, open the project’s index.html file and include the following markup:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="container">
<div id="body">
Execute HTTP Requests in JavaScript Applications
</div>
<div id="footer">
<div id="author">
<img id="author-img" src="nraboy.jpg">
<div id="author-name">
Nic Raboy
</div>
</div>
<img id="brand-icon" src="icon.png">
</div>
</div>
</body>
</html>
In my example, I’ve already downloaded an nraboy.jpg image and an icon.png image. However, feel free to change the CSS, HTML, and images to meet your needs.
Now that we have some HTML in place, we can focus on generating a screenshot of it. To do this we’ll be using Puppeteer, which is a headless Chrome Node.js API.
Within the project, we’ll need to install a few dependencies, which will require Node.js. From the command line, execute the following:
npm install puppeteer gulp gulp-tap --save-dev
While Puppeteer will be doing most of the work, we’ll be using Gulp to manage the task. Since we’ll be using a Gulp file pipeline, we need to be able to tap into the pipeline and gather various information about the files being processed, hence the extra package.
Now that the dependencies are in place, create a gulpfile.js file with the following JavaScript code:
const gulp = require("gulp");
const puppeteer = require("puppeteer");
const tap = require("gulp-tap");
const path = require("path");
gulp.task("build", function () {
return gulp.src(["**/*.html", "!node_modules/**/*"])
.pipe(tap(async (file) => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({
width: 1200,
height: 600,
deviceScaleFactor: 1,
});
await page.goto("file://" + file.path);
await page.screenshot({ path: path.basename(file.basename, ".html") + ".png" });
await browser.close();
}));
});
This particular Gulp configuration has a single task for building the images. The task looks for all HTML files in the directory and excludes anything in the node_modules directory.
For every file in the pipeline, a headless browser is created with the specified viewport configuration. The filename and path is extracted and used for rendering the HTML, then saving the render to an image.
You just saw how to use Puppeteer to capture a screenshot of an HTML render without using browser based JavaScript, meaning completely headless.
There are plenty of use-cases for this, but for me, using HTML as a template for marketing images is great. These images can be generated automatically without having to open a common multimedia editing application.
Original article source at: https://www.thepolyglotdeveloper.com/
1659112380
最近、プロジェクトに「PDFとしてダウンロード」機能という新しい機能を提供する必要がありました。私が最初に疑問に思ったのは、なぜこの機能を提供する必要があるのかということでした。マウスの右クリック/印刷/「PDFとして保存」オプションを備えたほとんどすべてのWebブラウザにネイティブに存在していませんか?さて、私は自分のWebページを試してみましたが、結果は本当に残念でした。
Boursoramaランダムページの例。次のようになります。
レンダリング:
その時、「この機能は価値があるかもしれない」と言ったのですが、どうすればいいですか?PDFを生成できる多くのオープンソースライブラリがあります。しかし、私の選択は、Googleが開発した有名なライブラリであるPuppeteerに自然に行き着きました。私によると、これはシングルページアプリケーションのPDF生成を処理する最も簡単な方法です。javascriptバンドルを処理せず、プレーンHTML / CSSを処理する場合は、そうではない可能性があります。実際、このユースケースには、たとえばwkhtmltopdf、html-pdf-node、jspdfなどのより簡単なソリューションがあります。
この記事では、Puppeteerを使用してSPAの美しいPDFを生成するためのヒントをいくつか紹介します。まず、ReactとPuppeteerを使用してページの印刷可能なバージョンを作成する方法を説明します。次に、Puppeteerを使用して新しい印刷可能なページを生成する方法を説明します。
この部分では、実際にPuppeteerやその他のプリンタサービスを設定する必要はありません。通常どおりコードに変更を加えてから、ページでCtrl + Pキーを押して、どのように表示されるかを確認できます。
ただし、フィードバックループは通常ほど速くはありません。
コードを印刷用に適合させるには、WebページとPDFの2つの主な違いを回避する必要があります。
Reactを使用してSPAの「印刷可能なバージョン」を作成します。ページの印刷可能なバージョンを作成するには、ページを構成するさまざまなコンポーネントを追加/削除/変更する必要があります。
この部分には基本的に2つの解決策があります。
2番目のソリューション(より安価)を選択した場合は、既存のコンポーネントを適応させる必要があります。たとえば、3つのタブがあるテーブルがある場合、すべてのタブのコンテンツを表示したい場合があります。タブを次々に表示するようなものでうまくいくかもしれません:
動的のみ:
<Table selectedTabIndex="tab1" />
動的および静的:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
この場合、isPrintable
小道具は3つのタブを表示するか、最初のタブだけを表示するかを決定します。この小道具は、印刷用に調整する必要があるページのすべての動的コンポーネントに渡すことができます。
Boursoramaの例でわかるように、ページを印刷しようとすると、コンポーネントが2ページの間で途切れる場合があります。あなたが彼に言わなければあなたのウェブブラウザがどこでページを壊すかを知らないのでそれは起こります。これは、break-inside
CSSプロパティがステップインする場所です。明らかに、以前のタブのセットが途中で途切れることは望ましくありません。グラフもページ上のほとんどすべてのコンポーネントもありません。次に、このCSSプロパティを追加するために、前のコードを適応させる必要があります。style={{ breakInside: 'avoid' }}
inline-cssで機能しますが、jsx/tsxファイルのどこにでも追加したくない場合があります。
むしろスタイルシートを使用したいと思います。また、既存のすべてのCSSクラスにこのプロパティを追加する代わりに、このmedia @print
オプションを使用することをお勧めします。これにより、印刷専用にWebページをカスタマイズできます。たとえば、美的理由や利便性のために、テキストを少し大きくしたり、印刷可能なバージョンで滑らかな灰色にしたりすることができます。
これをcssファイルに追加し@media object
ます。
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
これらのいくつかのCSSのヒントは、Webページのレンダリングを大幅に改善するのに役立ちます。
これで、ページを印刷する準備が整いました。isPrintable
小道具をページに渡し、ブラウザで右クリックして印刷すると、それがわかります。表示されている内容に非常に満足しています。これが印刷の一部です。これで、印刷可能なWebページができましたが、ユーザーはそれを認識できず、WebサイトでCtrl + Pを押しても、Webページの「動的」バージョンが表示されます。彼らにPDFバージョンを生成させ、最新の生成を自動化させるには、PDFサーバー側を直接生成するボタンを追加し、さらにカスタマイズを追加することもできます。これは、とりわけ、Puppeteerが使用されるものです。
Puppeteerは、Chromeを制御するための一般的で自然な方法です。ブラウザ機能へのフルアクセスを提供し、最も重要なこととして、リモートサーバー上で完全にヘッドレスモードでChromeを実行できます[...]
—Dima Bekerman、https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Puppeteerがサーバー側でどのように機能するかのスキーマ |
Reactアプリの生成は、Webブラウザーによって行われます。DOMをレンダリングするためにJavaScriptを実行できる最小限の環境が必要です。Puppeteerは、ヘッドレスクロムを発売することでそれを実現します。今後、生成はサーバー上で行われるため、Webブラウザーにグラフィカルユーザーインターフェイス(GUI)を設定する必要はありません。Chromium withは、印刷可能なバージョンを生成します。ユーザーがWebブラウザーに表示するのと同じページですが、isPrintable
小道具がアクティブになっています。次に、Puppeteerはpdf
、ページの印刷をトリガーするいくつかのカスタムオプションを使用して、ページで関数を実行します。
プリンタサービスを呼び出すURLのボタンを追加するだけです。
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
これdownloadUrl
は実際にはサーバーでのGETリクエストであり、サーバー上でPuppeteerを実行し、コンテンツタイプのコンテンツを返します。application/pdf
では、このPuppeteerコードはどのように見えますか?
実際にPDFをダウンロードできるようにするには、数行のコード行が必要です。
最小のコードは次のようになります。
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
これらは、PDFを生成するために必要な一般的な手順です。バックエンドによっては、PDFをサーバーにダウンロードするのではなく、応答オブジェクトにレンダリングしてクライアント(ユーザーのWebブラウザー)に送り返すことをお勧めします。page.pdf()
次に、メソッドを適応させ、ユーザーがブラウザーで開いconst buffer = await page.pdf({ format: 'a4'});
たページでこのバッファーを返し_blank
、応答を待つ必要があります。
もちろん、公式ドキュメントの助けを借りて、用紙サイズ、スケール、マージンなど、ブラウザに自然にあるオプションを適応させることができます:https ://github.com/puppeteer/puppeteer/blob/v10 .4.0 / docs / api.md#pagepdfoptions。
私がお勧めするクールなオプションの1つは、主にGoogle Chromeによって提供されるデフォルトのオプションが本当に醜いため、ヘッダーまたはフッターのテンプレートです。HTMLファイルテンプレートを読んで、現在の日付、各ページのページ番号、さらには画像/ロゴなど、表示するデータに渡すだけです。
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
HTMLテンプレートを使用する
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
これで、完全にカスタマイズされたフッターがPDFに提供されました。
PDF生成に関しては他にも多くのオプションがありますが、ブラウザを起動し、新しいページを開き、URLに移動する前の手順でも、サーバー上でPDF生成を完全にカスタマイズできます。
最後に、React / CSSコードを適応させ、Puppeteerを使用することで、ページの完全なカスタムPDFを簡単に提供できます。さらに、Puppeteerはすべてのものをサーバー側で実行しています。これにより、この機能は完全に透過的になり、エンドユーザーにとっては非常に高速になり、どのブラウザのすべてのユーザーに対しても同じ結果が得られます。Puppeteerは非常に強力で、開発者がPDFを非常に簡単に生成できるようにする多くのオプションがあり、ユーザーのブラウザのデフォルトよりもはるかにカスタムで美しいレンダリングが可能です。
出典:https ://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659105180
Recentemente, tive que fornecer uma nova funcionalidade no meu projeto: a de "baixar como PDF". A primeira coisa que me perguntei foi por que deveríamos fornecer essa funcionalidade? Já não existe nativamente com praticamente todos os navegadores da Web com a opção de clicar com o botão direito do mouse / imprimir / "salvar como pdf"? Bem, eu tentei na minha página da web e o resultado foi realmente decepcionante:
Exemplo em uma página aleatória do Boursorama , que se parece com:
Renderização:
Foi aí que eu disse "ok esse recurso pode valer a pena", mas como devo fazer? Existem muitas bibliotecas de código aberto que podem gerar PDFs. Mas minha escolha foi naturalmente para a conhecida biblioteca desenvolvida pelo Google: Puppeteer. Para mim, é a maneira mais fácil de lidar com a geração de PDF de aplicativos de página única. Pode não ser assim se você não lida com pacotes de javascript, mas com HTML/CSS simples. De fato, existem soluções mais fáceis para este caso de uso, como wkhtmltopdf, html-pdf-node ou jspdf, por exemplo.
Neste artigo, quero dar algumas dicas para gerar belos PDFs de SPAs com o Puppeteer. Em primeiro lugar, vou explicar como você pode criar uma versão para impressão da sua página com React e Puppeteer. Em seguida, mostrarei como usar o Puppeteer para a geração de sua nova página imprimível.
Para esta parte, você não precisa ter o Puppeteer ou qualquer outro serviço de impressora configurado. Você pode fazer suas alterações em seu código como de costume e, em seguida, ctrl+P em sua página para ver como fica:
No entanto, o ciclo de feedback não é tão rápido quanto de costume.
Para adaptar seu código para impressão, você deve ignorar as 2 principais diferenças entre uma página da Web e um PDF:
Crie a "versão para impressão" do seu SPA com React. Para criar a versão imprimível de nossa página, você terá que adicionar/remover/modificar os diferentes componentes que compõem a página.
Você basicamente tem 2 soluções para esta parte:
Se optar pela segunda solução (que é menos dispendiosa), terá de adaptar os seus componentes existentes. Por exemplo, se você tiver uma tabela com 3 guias, provavelmente desejará exibir o conteúdo de todas as guias. Algo como exibir as guias uma após a outra pode resolver o problema:
Apenas dinâmico:
<Table selectedTabIndex="tab1" />
Dinâmico e Estático:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
Nesse caso, os isPrintable
adereços determinarão se serão exibidas as 3 abas ou apenas a primeira. Você pode passar esses adereços para todos os componentes dinâmicos da sua página, que precisam ser adaptados para impressão.
Como você pode ver com o exemplo do Boursorama, seus componentes podem ser cortados entre 2 páginas ao tentar imprimir sua página. Isso acontece porque seu navegador da web não tem ideia de onde quebrar a página se você não contar a ele. É aqui que break-inside
entra a propriedade CSS. Você obviamente não quer que seu conjunto anterior de abas seja cortado no meio. Nem seus gráficos ou quase qualquer componente em sua página. Então você teria que adaptar o código anterior para adicionar essa propriedade CSS. Funcionaria com inline-css, mas você provavelmente não deseja adicionar todos os style={{ breakInside: 'avoid' }}
lugares em seus arquivos jsx/tsx.
Você prefere usar folhas de estilo. E ao invés de adicionar esta propriedade em cada classe CSS já existente, você vai querer usar a media @print
opção. Isso permitirá que você personalize sua página da Web apenas para impressão! Por exemplo, você pode querer que seu texto seja um pouco maior ou tenha uma cor cinza suave na versão para impressão, por qualquer razão estética ou conveniência.
Vamos apenas adicionar isso no @media object
seu arquivo css:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Essas poucas dicas de CSS devem te ajudar a melhorar muito a renderização da sua página.
Agora, sua página está pronta para impressão. Você sabe quando passa os isPrintable
adereços para sua página, clica com o botão direito + imprimir no seu navegador, e fica bastante confortável com o que vê. Aí vem a parte da impressão. Agora você tem uma versão para impressão da sua página da web, mas os usuários não têm ideia disso e, mesmo que o ctrl + P no site, eles vejam a versão "dinâmica" da página da web. Para permitir que eles gerem a versão PDF e automatizem a geração da mais recente, você provavelmente deseja adicionar um botão que gere diretamente o lado do servidor PDF e até adicione alguma personalização. É para isso que, entre outras coisas, o Puppeteer é usado.
O Puppeteer é uma maneira comum e natural de controlar o Chrome. Ele fornece acesso total aos recursos do navegador e, mais importante, pode executar o Chrome no modo totalmente sem periféricos em um servidor remoto [...]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Esquema de como o Puppeteer funciona no lado do servidor |
A geração do aplicativo React é feita por um navegador da web. Precisamos do ambiente mínimo capaz de executar javascript para renderizar um DOM. O Puppeteer fará isso lançando um cromo sem cabeça. A partir de agora, e uma vez que a geração é feita no servidor, o navegador web não necessita de ter uma interface gráfica de utilizador (GUI). O Chromium gera a versão para impressão: a mesma página que o usuário vê em seu navegador, mas com os isPrintable
adereços ativados. Em seguida, o Puppeteer executará a pdf
função na página com algumas opções personalizadas que acionarão a impressão da página.
Basta adicionar o botão com a URL que chama o serviço de impressora:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
Na downloadUrl
verdade, é uma solicitação GET em seu servidor que executará o Puppeteer no servidor e retornará conteúdo com tipo de conteúdoapplication/pdf
Então, como é esse código do Puppeteer?
Para poder realmente baixar o PDF, você só precisa de algumas linhas de código.
O código mínimo ficaria assim:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Estas são as etapas comuns necessárias para gerar o PDF. Dependendo do seu backend, você provavelmente não quer baixar o PDF no servidor, mas renderizá-lo em um objeto de resposta, para enviá-lo de volta ao cliente (o navegador da web do usuário). Você deve então adaptar o page.pdf()
método const buffer = await page.pdf({ format: 'a4'});
e retornar esse buffer na _blank
página que o usuário abriu em seu navegador, aguardando uma resposta.
Claro que você pode adaptar as opções que você naturalmente tem no seu navegador, como o tamanho do papel, a escala, as margens, etc. com a ajuda da documentação oficial: https://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions .
Uma opção legal que eu recomendo, principalmente porque o padrão fornecido pelo Google Chrome é muito feio, é o modelo de cabeçalho ou rodapé. Basta ler um modelo de arquivo HTML e passá-lo pelos dados que deseja exibir, como a data atual, o número da página de cada página ou até mesmo uma imagem/logotipo:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
usando um modelo html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Agora você forneceu ao seu PDF um rodapé totalmente personalizado.
Existem muitas outras opções em relação à geração de PDF, mas também para as etapas anteriores de iniciar o navegador, abrir uma nova página, acessar a URL, que permitirá personalizar totalmente sua geração de PDF no servidor.
Finalmente, adaptando seu código React/CSS e usando o Puppeteer, você pode facilmente fornecer um PDF totalmente personalizado de sua página. Além disso, o Puppeteer está fazendo tudo do lado do servidor. O que torna esse recurso totalmente transparente, bastante rápido para o usuário final e com o mesmo resultado para todos os usuários em qualquer navegador! O Puppeteer é realmente poderoso e possui muitas opções que tornam a geração de PDF bastante fácil para os desenvolvedores, e com uma renderização muito mais personalizada e bonita do que a padrão nos navegadores dos usuários.
Fonte: https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659073080
I recently had to provide a new functionality on my project: the "download as PDF" one. The first thing I wondered was why should we provide this functionality? Doesn't it already exist natively with pretty much all web browsers with a right mouse click / print / "save as pdf" option? Well I tried on my webpage and the result was really disappointing:
See more at: https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659026160
我最近不得不在我的项目中提供一项新功能:“下载为 PDF”功能。我想的第一件事是我们为什么要提供这个功能?它不是已经原生存在于几乎所有具有鼠标右键单击/打印/“另存为 pdf”选项的 Web 浏览器中吗?好吧,我在我的网页上试过,结果真的很令人失望:
Boursorama 随机页面上的示例,如下所示:
渲染:
那时我说“好吧,这个功能可能值得”,但我该怎么做呢?有许多可以生成 PDF 的开源库。但我自然而然地选择了著名的 google 开发的库:Puppeteer。在我看来,这是处理单页应用程序的 PDF 生成最简单的方法。如果您不处理 javascript 包而是使用纯 HTML/CSS,则可能并非如此。实际上,对于这种用例,有更简单的解决方案,例如 wkhtmltopdf、html-pdf-node 或 jspdf。
在本文中,我想为您提供一些使用 Puppeteer 生成漂亮的 SPA PDF 的技巧。首先,我将向您解释如何使用 React 和 Puppeteer 创建页面的可打印版本。然后,我将向您展示如何使用 Puppeteer 生成新的可打印页面。
对于这部分,您实际上不需要设置 Puppeteer 或任何其他打印机服务。您可以像往常一样对代码进行更改,然后在您的页面上按 ctrl+P 来查看它的样子:
然而,反馈循环并不像往常那样快。
要使您的代码适应打印,您必须绕过网页和 PDF 之间的 2 个主要区别:
使用React创建 SPA 的“可打印版本” 。要创建我们页面的可打印版本,您必须添加/删除/修改构成页面的不同组件。
对于这部分,您基本上有 2 个解决方案:
如果您选择第二种解决方案(成本更低),您将不得不调整现有组件。例如,如果您有一个包含 3 个选项卡的表格,您可能希望显示所有选项卡的内容。诸如一个接一个地显示选项卡之类的东西可能会起作用:
只有动态:
<Table selectedTabIndex="tab1" />
动态和静态:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
在这种情况下,isPrintable
道具将决定是显示 3 个选项卡,还是只显示第一个。您可以将此道具传递给页面的每个动态组件,这些组件需要适应打印。
正如您在 Boursorama 示例中看到的那样,在尝试打印页面时,您的组件可能会在 2 页之间被切断。发生这种情况是因为如果您不告诉他,您的网络浏览器不知道在哪里中断页面。这就是break-inside
CSS 属性介入的地方。您显然不希望之前的一组选项卡在中间被切断。您的图表或页面上的几乎任何组件都不是。然后,您必须修改前面的代码来添加这个 CSS 属性。它可以与 inline-css 一起使用,但您可能不想style={{ breakInside: 'avoid' }}
在 jsx/tsx 文件中添加到处。
您宁愿使用样式表。而不是在每个已经存在的 CSS 类上添加这个属性,你会想要使用这个media @print
选项。这将让您自定义您的网页仅用于打印!例如,出于任何美学原因或方便,您可能希望文本更大一点或在可打印版本上具有平滑的灰色。
我们只需将其添加到@media object
您的 css 文件中:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
这几个 CSS 技巧应该可以帮助您改善网页的渲染。
现在,您的页面已准备好打印。当您将isPrintable
道具传递到您的页面时,您就知道了,在浏览器上右键单击 + 打印,并且您对所看到的内容感到非常满意。这里是打印的部分。您现在有了网页的可打印版本,但用户不知道它,即使在网站上按 ctrl + P,他们也会看到网页的“动态”版本。为了让他们生成 PDF 版本并自动生成最新版本,您可能想要添加一个按钮,直接生成 PDF 服务器端,甚至添加一些自定义。这就是 Puppeteer 的用途。
Puppeteer 是控制 Chrome 的一种常见且自然的方式。它提供对浏览器功能的完全访问,最重要的是,可以在远程服务器上以完全无头模式运行 Chrome [...]
——Dima Bekerman,https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Puppeteer 如何在服务器端工作的架构 |
React 应用程序的生成由 Web 浏览器完成。我们需要能够执行 javascript 来呈现 DOM 的最小环境。Puppeteer 将通过发射无头铬来做到这一点。从现在开始,由于生成是在服务器上完成的,Web 浏览器不需要图形用户界面 (GUI)。Chromium 生成可打印版本:用户在其网络浏览器上看到的相同页面,但isPrintable
激活了道具。然后 Puppeteer 将pdf
使用一些自定义选项在页面上执行该功能,这些选项将触发页面的打印。
只需添加带有调用打印机服务的 URL 的按钮:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
这downloadUrl
实际上是您服务器上的 GET 请求,它将在服务器上执行 Puppeteer 并返回具有 content-type 的内容application/pdf
那么这个 Puppeteer 代码是什么样的呢?
为了能够实际下载 PDF,您只需要几行代码。
最小的代码将如下所示:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
这些是生成 PDF 所需的常见步骤。根据您的后端,您可能不想在服务器上下载 PDF,而是在响应对象上呈现它,然后将其发送回客户端(用户的 Web 浏览器)。然后,您应该调整该page.pdf()
方法const buffer = await page.pdf({ format: 'a4'});
并在用户在其浏览器上打开的页面上返回此缓冲区_blank
,等待响应。
您当然可以在官方文档的帮助下调整您在浏览器上自然拥有的选项,例如纸张大小、比例、边距等:https ://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions。
我推荐的一个很酷的选项是页眉或页脚模板,主要是因为谷歌浏览器提供的默认选项真的很难看。只需读取 HTML 文件模板并将其传递给您要显示的数据,例如当前日期、每页的页码,甚至是图像/徽标:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
使用 html 模板
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
您现在已经为您的 PDF 提供了一个完全自定义的页脚。
关于 PDF 生成,还有很多其他选项,还有启动浏览器、打开新页面、转到 URL 的前面步骤,这将让您完全自定义服务器上的 PDF 生成。
最后,通过调整您的 React/CSS 代码并使用 Puppeteer,您可以轻松地为您的页面提供完全自定义的 PDF。此外,Puppeteer 正在做服务器端的所有工作。这使得此功能完全透明,对最终用户来说非常快,并且对于任何浏览器上的每个用户都具有相同的结果!Puppeteer 非常强大,并且有很多选项可以让开发人员轻松生成 PDF,并且渲染比用户浏览器上的默认渲染更加自定义和美观。
来源:https ://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659022266
J'ai récemment dû fournir une nouvelle fonctionnalité sur mon projet : celle du "téléchargement en PDF". La première chose que je me suis demandé était pourquoi devrions-nous fournir cette fonctionnalité ? N'existe-t-il pas déjà nativement avec à peu près tous les navigateurs Web avec une option clic droit / imprimer / "enregistrer en pdf" ? Eh bien, j'ai essayé sur ma page Web et le résultat était vraiment décevant:
Exemple sur une page aléatoire Boursorama , qui ressemble à :
le rendu:
C'est alors que j'ai dit "ok cette fonctionnalité en vaut peut-être la peine", mais comment dois-je faire ? Il existe de nombreuses bibliothèques open source capables de générer des PDF. Mais mon choix s'est porté naturellement sur la bibliothèque bien connue développée par google : Puppeteer. Selon moi, c'est le moyen le plus simple de gérer la génération PDF d'applications à page unique. Ce n'est peut-être pas le cas si vous ne traitez pas avec des bundles javascript mais avec du HTML/CSS brut. En effet, il existe des solutions plus simples pour ce cas d'utilisation comme wkhtmltopdf, html-pdf-node ou jspdf par exemple.
Dans cet article, je souhaite vous donner quelques astuces pour générer de beaux PDF de SPA avec Puppeteer. Dans un premier temps, je vais vous expliquer comment vous pouvez créer une version imprimable de votre page avec React et Puppeteer. Ensuite, je vous montrerai comment utiliser Puppeteer pour la génération de votre nouvelle page imprimable.
Pour cette partie, vous n'avez pas réellement besoin d'avoir Puppeteer ou tout autre service d'impression configuré. Vous pouvez apporter vos modifications à votre code comme d'habitude, puis ctrl+P sur votre page pour voir à quoi il ressemble :
Cependant, la boucle de rétroaction n'est pas aussi rapide que d'habitude.
Pour adapter votre code à l'impression, vous devez contourner les 2 principales différences entre une page web et un PDF :
Créez la "version imprimable" de votre SPA avec React. Pour créer la version imprimable de notre page, vous devrez ajouter/supprimer/modifier les différents éléments qui composent la page.
Vous avez essentiellement 2 solutions pour cette partie :
Si vous optez pour la deuxième solution (moins coûteuse), vous devrez adapter vos composants existants. Par exemple, si vous avez un tableau avec 3 onglets, vous souhaiterez probablement afficher le contenu de tous les onglets. Quelque chose comme afficher les onglets les uns après les autres peut faire l'affaire :
Uniquement dynamique :
<Table selectedTabIndex="tab1" />
Dynamique et Statique :
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
Dans ce cas, les isPrintable
props détermineront s'il faut afficher les 3 onglets, ou juste le premier. Vous pouvez transmettre ces accessoires à chaque composant dynamique de votre page, qui doit être adapté pour l'impression.
Comme vous pouvez le voir avec l'exemple Boursorama, vos composants peuvent être coupés entre 2 pages lorsque vous essayez d'imprimer votre page. Cela se produit parce que votre navigateur Web n'a aucune idée de l'endroit où couper la page si vous ne le lui dites pas. C'est là qu'intervient la break-inside
propriété CSS. Vous ne voulez évidemment pas que votre ensemble d'onglets précédent soit coupé au milieu. Ni vos graphiques ni presque aucun composant de votre page. Ensuite, vous devrez adapter le code précédent pour ajouter cette propriété CSS. Cela fonctionnerait avec inline-css mais vous ne voulez probablement pas ajouter le style={{ breakInside: 'avoid' }}
partout dans vos fichiers jsx/tsx.
Vous préférez utiliser des feuilles de style. Et au lieu d'ajouter cette propriété sur chaque classe CSS déjà existante, vous voudrez utiliser l' media @print
option. Cela vous permettra de personnaliser votre page Web pour l'impression uniquement ! Par exemple, vous voudrez peut-être que votre texte soit un peu plus grand ou qu'il ait une couleur grise lisse sur la version imprimable, pour une raison esthétique ou de commodité.
Nous allons simplement ajouter ceci @media object
dans votre fichier CSS :
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Ces quelques astuces CSS devraient vous aider à améliorer considérablement le rendu de votre page web.
Maintenant, votre page est prête à être imprimée. Vous le savez lorsque vous passez les isPrintable
accessoires à votre page, faites un clic droit + imprimer sur votre navigateur, et vous êtes assez à l'aise avec ce que vous voyez. Voici la partie de l'impression. Vous avez maintenant une version imprimable de votre page Web, mais les utilisateurs n'en ont aucune idée, et même si le ctrl + P sur le site Web, ils verront la version "dynamique" de la page Web. Pour leur permettre de générer la version PDF et d'automatiser la génération de la dernière, vous souhaitez probablement ajouter un bouton qui générera directement le PDF côté serveur, et même ajouter quelques personnalisations. C'est à cela, entre autres, que Marionnettiste sert.
Puppeteer est un moyen courant et naturel de contrôler Chrome. Il offre un accès complet aux fonctionnalités du navigateur et, plus important encore, peut exécuter Chrome en mode entièrement sans tête sur un serveur distant [...]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Schéma du fonctionnement de Puppeteer côté serveur |
La génération de l'application React est effectuée par un navigateur Web. Nous avons besoin d'un environnement minimal capable d'exécuter du javascript pour rendre un DOM. Marionnettiste le fera en lançant un chrome sans tête. Désormais, et puisque la génération se fait sur le serveur, le navigateur web n'a plus besoin d'avoir une interface utilisateur graphique (GUI). Chromium avec générer la version imprimable : la même page que l'utilisateur voit sur son navigateur Web mais avec les isPrintable
accessoires activés. Ensuite, Puppeteer exécutera la pdf
fonction sur la page avec des options personnalisées qui déclencheront l'impression de la page.
Ajoutez simplement le bouton avec l'URL qui appelle le service d'impression :
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
Il downloadUrl
s'agit en fait d'une requête GET sur votre serveur qui exécutera Puppeteer sur le serveur et renverra le contenu avec le type de contenuapplication/pdf
Alors, à quoi ressemble ce code Puppeteer ?
Pour pouvoir réellement télécharger le PDF, il vous suffit de quelques lignes de code.
Le code minimal ressemblerait alors à :
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Ce sont les étapes courantes dont vous aurez besoin pour générer le PDF. En fonction de votre backend, vous ne souhaitez probablement pas ne pas télécharger le PDF sur le serveur mais le rendre sur un objet de réponse, pour le renvoyer au client (le navigateur Web de l'utilisateur). Il faut ensuite adapter la page.pdf()
méthode avec const buffer = await page.pdf({ format: 'a4'});
et renvoyer ce tampon sur la _blank
page que l'utilisateur a ouverte sur son navigateur, en attente d'une réponse.
Vous pouvez bien sûr adapter les options dont vous disposez naturellement sur votre navigateur, comme la taille du papier, l'échelle, les marges, etc. à l'aide de la documentation officielle : https://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions .
Une option intéressante que je recommande, principalement parce que celle par défaut fournie par Google Chrome est vraiment moche, est le modèle d'en-tête ou de pied de page. Il vous suffit de lire un modèle de fichier HTML et de lui transmettre les données que vous souhaitez afficher, telles que la date actuelle, le numéro de page pour chaque page ou même une image/logo :
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
à l'aide d'un modèle html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Vous avez maintenant fourni à votre PDF un pied de page entièrement personnalisé.
Il existe de nombreuses autres options concernant, la génération de PDF, mais aussi pour les étapes précédentes de lancement du navigateur, d'ouverture d'une nouvelle page, d'aller à l'URL, qui vous permettront de personnaliser entièrement votre génération de PDF sur le serveur.
Enfin, en adaptant votre code React/CSS et en utilisant Puppeteer, vous pouvez facilement fournir un PDF entièrement personnalisé de votre page. De plus, Puppeteer fait tout le travail côté serveur. Ce qui rend cette fonctionnalité totalement transparente, assez rapide pour l'utilisateur final, et avec le même résultat pour chaque utilisateur sur n'importe quel navigateur ! Puppeteer est vraiment puissant et possède de nombreuses options qui rendent la génération de PDF assez facile pour les développeurs, et avec un rendu beaucoup plus personnalisé et beau que celui par défaut sur les navigateurs des utilisateurs.
Source : https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659020400
Recientemente tuve que proporcionar una nueva funcionalidad en mi proyecto: la de "descargar como PDF". Lo primero que me pregunté fue ¿por qué deberíamos proporcionar esta funcionalidad? ¿No existe ya de forma nativa con casi todos los navegadores web con la opción de clic derecho del mouse / imprimir / "guardar como pdf"? Bueno, probé en mi página web y el resultado fue realmente decepcionante:
Ejemplo en una página aleatoria de Boursorama , que se parece a:
representación:
Fue entonces cuando dije "bien, esta función puede valer la pena", pero ¿cómo debo hacerlo? Hay muchas bibliotecas de código abierto que pueden generar archivos PDF. Pero mi elección fue naturalmente a la conocida biblioteca desarrollada por Google: Titiritero. Según yo, es la forma más fácil de lidiar con la generación de PDF de aplicaciones de una sola página. Puede que no sea así si no trabaja con paquetes de JavaScript sino con HTML/CSS simple. De hecho, existen soluciones más sencillas para este caso de uso, como wkhtmltopdf, html-pdf-node o jspdf, por ejemplo.
En este artículo, quiero darte algunos consejos para generar hermosos PDF de SPA con Puppeteer. En primer lugar, te explicaré cómo puedes crear una versión imprimible de tu página con React y Puppeteer. Luego, te mostraré cómo usar Titiritero para la generación de tu nueva página imprimible.
Para esta parte, en realidad no necesita tener Puppeteer ni ningún otro servicio de impresión configurado. Puede realizar los cambios en su código como de costumbre, y luego ctrl+P en su página para ver cómo se ve:
Sin embargo, el circuito de retroalimentación no es tan rápido como de costumbre.
Para adaptar su código para la impresión, debe omitir las 2 diferencias principales entre una página web y un PDF:
Crea la "versión imprimible" de tu SPA con React. Para crear la versión imprimible de nuestra página, deberá agregar/eliminar/modificar los diferentes componentes que componen la página.
Básicamente tienes 2 soluciones para esta parte:
Si opta por la segunda solución (que es menos costosa), deberá adaptar sus componentes existentes. Por ejemplo, si tiene una tabla con 3 pestañas, probablemente querrá mostrar el contenido de todas las pestañas. Algo así como mostrar las pestañas una tras otra puede funcionar:
Solo dinámico:
<Table selectedTabIndex="tab1" />
Dinámico y Estático:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
En este caso, los isPrintable
accesorios determinarán si mostrar las 3 pestañas o solo la primera. Puede pasar estos accesorios a cada componente dinámico de su página, que necesita ser adaptado para la impresión.
Como puede ver con el ejemplo de Boursorama, sus componentes pueden cortarse entre 2 páginas al intentar imprimir su página. Sucede porque su navegador web no tiene idea de dónde romper la página si no se lo dice. Aquí es donde break-inside
interviene la propiedad CSS. Obviamente, no desea que su conjunto anterior de pestañas se corte en el medio. Ni sus gráficos ni casi ningún componente de su página. Entonces tendrías que adaptar el código anterior para agregar esta propiedad CSS. Funcionaría con inline-css, pero probablemente no desee agregarlo style={{ breakInside: 'avoid' }}
en todas partes en sus archivos jsx/tsx.
Preferirías usar hojas de estilo. Y en lugar de agregar esta propiedad en cada clase CSS ya existente, querrá usar la media @print
opción. ¡Esto le permitirá personalizar su página web solo para imprimir! Por ejemplo, puede querer que su texto sea un poco más grande o que tenga un color gris suave en la versión imprimible, por cualquier razón estética o conveniencia.
Simplemente agregaremos esto en @media object
su archivo css:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Estos pocos consejos de CSS deberían ayudarlo a mejorar mucho la representación de su página web.
Ahora, su página está lista para imprimir. Lo sabes cuando pasas los isPrintable
accesorios a tu página, haces clic con el botón derecho + imprimir en tu navegador y te sientes bastante cómodo con lo que ves. Aquí viene la parte de la impresión. Ahora tiene una versión imprimible de su página web, pero los usuarios no tienen idea de ella, e incluso si presionan ctrl + P en el sitio web, verán la versión "dinámica" de la página web. Para permitirles generar la versión PDF y automatizar la generación de la última, probablemente desee agregar un botón que genere directamente el lado del servidor PDF e incluso agregar algo de personalización. Para esto se utiliza, entre otras cosas, Titiritero.
Titiritero es una forma común y natural de controlar Chrome. Proporciona acceso completo a las funciones del navegador y, lo que es más importante, puede ejecutar Chrome en modo totalmente autónomo en un servidor remoto [...]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Esquema de cómo funciona Titiritero del lado del servidor |
La generación de la aplicación React se realiza mediante un navegador web. Necesitamos el entorno mínimo capaz de ejecutar javascript para representar un DOM. Titiritero lo hará lanzando un cromo sin cabeza. A partir de ahora, y dado que la generación se realiza en el servidor, el navegador web no necesita tener una interfaz gráfica de usuario (GUI). Chromium genera la versión imprimible: la misma página que el usuario ve en su navegador web pero con los isPrintable
accesorios activados. Entonces Puppeteer ejecutará la pdf
función en la página con algunas opciones personalizadas que activarán la impresión de la página.
Simplemente agregue el botón con la URL que llama al servicio de impresión:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
En downloadUrl
realidad, es una solicitud GET en su servidor que ejecutará Titiritero en el servidor y devolverá contenido con tipo de contenidoapplication/pdf
Entonces, ¿cómo es este código de Titiritero?
Para poder descargar el PDF, solo necesita unas pocas líneas de código.
El código mínimo se vería así:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Estos son los pasos comunes que necesitará para generar el PDF. Dependiendo de su back-end, probablemente no quiera descargar el PDF en el servidor sino representarlo en un objeto de respuesta, para enviarlo de regreso al cliente (el navegador web del usuario). Luego debe adaptar el page.pdf()
método const buffer = await page.pdf({ format: 'a4'});
y devolver este búfer en la _blank
página que el usuario abrió en su navegador, esperando una respuesta.
Por supuesto, puede adaptar las opciones que naturalmente tiene en su navegador, como el tamaño del papel, la escala, los márgenes, etc. con la ayuda de la documentación oficial: https://github.com/puppeteer/puppeteer/blob/v10 .4.0/docs/api.md#pagepdfoptions .
Una buena opción que recomiendo, principalmente porque la predeterminada que proporciona Google Chrome es realmente fea, es la plantilla de encabezado o pie de página. Simplemente lea una plantilla de archivo HTML y pásela por los datos que desea mostrar, como la fecha actual, el número de página para cada página o incluso una imagen/logotipo:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
utilizando una plantilla html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Ahora ha proporcionado a su PDF un pie de página totalmente personalizado.
Hay muchas otras opciones con respecto a la generación de PDF, pero también para los pasos previos de iniciar el navegador, abrir una nueva página, ir a la URL, que le permitirán personalizar completamente su generación de PDF en el servidor.
Finalmente, al adaptar su código React/CSS y usar Puppeteer, puede proporcionar fácilmente un PDF totalmente personalizado de su página. Además, Puppeteer está haciendo todas las cosas del lado del servidor. ¡Lo que hace que esta función sea completamente transparente, bastante rápida para el usuario final y con el mismo resultado para todos los usuarios en cualquier navegador! Puppeteer es realmente poderoso y tiene muchas opciones que hacen que la generación de PDF sea bastante fácil para los desarrolladores, y con un renderizado mucho más personalizado y hermoso que el predeterminado en los navegadores de los usuarios.
Fuente: https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1659016800
Gần đây tôi đã phải cung cấp một chức năng mới cho dự án của mình: chức năng "tải xuống dưới dạng PDF". Điều đầu tiên tôi tự hỏi là tại sao chúng tôi nên cung cấp chức năng này? Không phải nó đã tồn tại nguyên bản với hầu hết các trình duyệt web với tùy chọn nhấp chuột phải / print / "save as pdf"? Tôi đã thử trên trang web của mình và kết quả thực sự đáng thất vọng:
Ví dụ trên trang ngẫu nhiên Boursorama , giống như sau:
kết xuất:
Đó là khi tôi nói "ok, tính năng này có thể đáng giá", nhưng tôi nên làm như thế nào? Có nhiều thư viện mã nguồn mở có thể tạo PDF. Nhưng sự lựa chọn của tôi tự nhiên đến với thư viện nổi tiếng do google phát triển: Puppeteer. Theo tôi, đó là cách dễ nhất để giải quyết việc tạo ra các Ứng dụng Trang đơn PDF. Có thể không phải như vậy nếu bạn không xử lý các gói javascript nhưng với HTML / CSS thuần túy. Thật vậy, có những giải pháp dễ dàng hơn cho trường hợp sử dụng này như wkhtmltopdf, html-pdf-node hoặc jspdf chẳng hạn.
Trong bài viết này, tôi muốn cung cấp cho bạn một số mẹo để tạo các tệp PDF đẹp của các SPA với Puppeteer. Đầu tiên, tôi sẽ giải thích cho bạn cách bạn có thể tạo phiên bản có thể in được trên trang của mình bằng React và Puppeteer. Sau đó, tôi sẽ chỉ cho bạn cách sử dụng Puppeteer để tạo trang có thể in mới của bạn.
Đối với phần này, bạn thực sự không cần phải thiết lập Puppeteer hoặc bất kỳ dịch vụ máy in nào khác. Bạn có thể thực hiện các thay đổi đối với mã của mình như bình thường, sau đó nhấn ctrl + P trên trang của bạn để xem nó trông như thế nào:
Tuy nhiên, vòng lặp phản hồi không nhanh như bình thường.
Để điều chỉnh mã của bạn để in, bạn phải bỏ qua 2 điểm khác biệt chính giữa trang web và PDF:
Tạo "phiên bản có thể in" của SPA của bạn bằng React. Để tạo phiên bản có thể in của trang của chúng tôi, bạn sẽ phải thêm / xóa / sửa đổi các thành phần khác nhau tạo nên trang.
Về cơ bản, bạn có 2 giải pháp cho phần này:
Nếu bạn chọn giải pháp thứ hai (ít tốn kém hơn), bạn sẽ phải điều chỉnh các thành phần hiện có của mình. Ví dụ: nếu bạn có một bảng với 3 tab, có thể bạn sẽ muốn hiển thị nội dung của tất cả các tab. Một cái gì đó như hiển thị các tab lần lượt có thể thực hiện thủ thuật:
Chỉ động:
<Table selectedTabIndex="tab1" />
Động và Tĩnh:
const tabNames = ['tab1', 'tab2', 'tab3']
(isPrintable ?
tabNames.map(tabName => <Table key={tabName} selectedTab={tabName} /> :
<Table selectedTabIndex='tab1' />
);
Trong trường hợp này, các isPrintable
đạo cụ sẽ quyết định hiển thị 3 tab hay chỉ hiển thị tab đầu tiên. Bạn có thể chuyển đạo cụ này cho mọi thành phần động trên trang của mình, cần được điều chỉnh để in.
Như bạn có thể thấy với ví dụ Boursorama, các thành phần của bạn có thể bị cắt giữa 2 trang khi cố gắng in trang của bạn. Nó xảy ra vì trình duyệt web của bạn không biết phải ngắt trang ở đâu nếu bạn không nói cho anh ta biết. Đây là nơi thuộc tính break-inside
CSS bước vào. Rõ ràng là bạn không muốn tập hợp các tab trước đó của mình bị cắt ở giữa. Không có đồ thị của bạn hoặc hầu như bất kỳ thành phần nào trên trang của bạn. Sau đó, bạn sẽ phải điều chỉnh mã trước đó để thêm thuộc tính CSS này. Nó sẽ hoạt động với inline-css nhưng bạn có thể không muốn thêm mọi thứ style={{ breakInside: 'avoid' }}
vào mọi nơi trong tệp jsx / tsx của mình.
Bạn muốn sử dụng bảng định kiểu. Và thay vì thêm thuộc tính này trên mọi lớp CSS đã tồn tại, bạn sẽ muốn sử dụng media @print
tùy chọn. Điều này sẽ cho phép bạn tùy chỉnh trang web của mình để chỉ in! Ví dụ: bạn có thể muốn văn bản của mình lớn hơn một chút hoặc có màu xám mịn trên phiên bản có thể in được, vì bất kỳ lý do thẩm mỹ hoặc tiện lợi nào.
Chúng tôi sẽ chỉ thêm cái này vào @media object
tệp css của bạn:
media @print {
body: {
font-size: "16px";
color: "lightgrey";
}
.no-break-inside {
// apply this class to every component that shouldn't be cut off between to pages of your PDF
break-inside: "avoid";
}
.break-before {
// apply this class to every component that should always display on next page
break-before: "always";
}
}
<MyComponent isPrintable=true className="no-break-inside" />
Một vài mẹo CSS này sẽ giúp bạn cải thiện rất nhiều việc hiển thị trang web của mình.
Bây giờ, trang của bạn đã sẵn sàng để in. Bạn biết điều đó khi chuyển các isPrintable
đạo cụ đến trang của mình, nhấp chuột phải + in trên trình duyệt của mình và bạn khá thoải mái với những gì mình nhìn thấy. Đây là phần in ấn. Bây giờ bạn có phiên bản có thể in của trang web của mình, nhưng người dùng không biết về nó và ngay cả khi nhấn ctrl + P trên trang web, họ sẽ thấy phiên bản "động" của trang web. Để cho phép họ tạo phiên bản PDF và tự động tạo phiên bản mới nhất, bạn có thể muốn thêm một nút sẽ tạo trực tiếp phía máy chủ PDF và thậm chí thêm một số tùy chỉnh. Đây là thứ, trong số những thứ khác, Puppeteer được sử dụng để làm.
Puppeteer là một cách phổ biến và tự nhiên để điều khiển Chrome. Nó cung cấp quyền truy cập đầy đủ vào các tính năng của trình duyệt và quan trọng nhất là có thể chạy Chrome ở chế độ hoàn toàn không sử dụng đầu trên máy chủ từ xa […]
—Dima Bekerman, https://www.imperva.com/blog/headless-chrome-devops-love-it-so-do-hackers-heres-why/
![]() |
---|
Lược đồ về cách Puppeteer hoạt động phía máy chủ |
Việc tạo ra ứng dụng React được thực hiện bởi trình duyệt web. Chúng tôi cần môi trường tối thiểu có thể thực thi javascript để hiển thị DOM. Puppeteer sẽ làm điều đó bằng cách phóng một crôm không đầu. Kể từ bây giờ, và vì quá trình tạo được thực hiện trên máy chủ, nên trình duyệt web không cần phải có giao diện người dùng đồ họa (GUI). Chromium với tạo phiên bản có thể in: cùng một trang mà người dùng nhìn thấy trên trình duyệt web của mình nhưng đã isPrintable
kích hoạt đạo cụ. Sau đó Puppeteer sẽ thực thi pdf
chức năng trên trang với một số tùy chọn tùy chỉnh sẽ kích hoạt việc in trang.
Chỉ cần thêm nút có URL gọi dịch vụ máy in:
<Button onClick={window.open(downloadUrl, "_blank")}>Download as PDF</Button>
Đây downloadUrl
thực sự là một yêu cầu GET trên máy chủ của bạn sẽ thực thi Puppeteer trên máy chủ và trả về nội dung có kiểu nội dungapplication/pdf
Vậy mã Puppeteer này trông như thế nào?
Để có thể thực sự tải xuống PDF, bạn chỉ cần một vài dòng mã.
Sau đó, mã tối thiểu sẽ trông giống như sau:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch(); // launch a browser (chromium by default but you can chose another one)
const page = await browser.newPage(); // open a page in the browser
await page.goto("https://printable-version-of-my-wbe-page.com", {
waitUntil: "networkidle2",
}); // visit the printable version of your page
await page.pdf({ format: "a4", path: "./my_file.pdf" }); // generate the PDF 🎉
await browser.close(); // don't forget to close the browser. Otherwise, it may cause performances issues or the server may even crash..
})();
Đây là các bước phổ biến bạn sẽ cần để tạo tệp PDF. Tùy thuộc vào chương trình phụ trợ của bạn, có thể bạn không muốn tải xuống tệp PDF trên máy chủ mà hiển thị nó trên một đối tượng phản hồi, để gửi nó trở lại máy khách (trình duyệt web của người dùng). Sau đó, bạn nên điều chỉnh page.pdf()
phương pháp const buffer = await page.pdf({ format: 'a4'});
và trả lại vùng đệm này trên _blank
trang mà người dùng đã mở trên trình duyệt của mình, chờ phản hồi.
Tất nhiên, bạn có thể điều chỉnh các tùy chọn tự nhiên có trên trình duyệt của mình, như khổ giấy, tỷ lệ, lề, v.v. với sự trợ giúp của tài liệu chính thức: https://github.com/puppeteer/puppeteer/blob/v10 .4.0 / docs / api.md # pagepdfoptions .
Một tùy chọn thú vị mà tôi đề xuất, chủ yếu là vì tùy chọn mặc định do Google Chrome cung cấp thực sự xấu, là mẫu đầu trang hoặc chân trang. Chỉ cần đọc mẫu tệp HTML và chuyển nó qua dữ liệu bạn muốn hiển thị, chẳng hạn như ngày hiện tại, số trang cho mỗi trang hoặc thậm chí là hình ảnh / biểu trưng:
const footerBase = fs.readFileSync("./footer.html", "utf8");
customFooter = footerBase
.replace("{{date}}", new Date())
.replace("{{image_data}}", imageData);
await page.pdf({ format: "a4", footerTemplate: customFooter });
sử dụng mẫu html
<style>
#logo {
height: 40px;
content: url("data:image/png;base64,{{image_data}}");
}
</style>
<div id="body">
<div id="page-number-paragraph">
<span id="date">{{date}}</span>
<span>Page</span>
<span class="pageNumber"/></span>
<span>/</span>
<span class="totalPages"></span>
</div>
<div id="brand-container">
<span id="logo"></span>
</div>
</div>
Bây giờ bạn đã cung cấp cho tệp PDF của mình một chân trang được tùy chỉnh hoàn toàn.
Có rất nhiều tùy chọn khác liên quan đến việc tạo PDF, mà còn đối với các bước khởi chạy trình duyệt trước đó, mở trang mới, truy cập URL, điều này sẽ cho phép bạn tùy chỉnh hoàn toàn việc tạo PDF của mình trên máy chủ.
Cuối cùng, bằng cách điều chỉnh mã React / CSS của bạn và sử dụng Puppeteer, bạn có thể dễ dàng cung cấp một tệp PDF hoàn toàn tùy chỉnh cho trang của mình. Hơn nữa, Puppeteer đang làm tất cả những thứ ở phía máy chủ. Điều này làm cho tính năng này hoàn toàn minh bạch, khá nhanh cho người dùng cuối và mang lại kết quả tương tự cho mọi người dùng trên bất kỳ trình duyệt nào! Puppeteer thực sự mạnh mẽ và có nhiều tùy chọn giúp tạo PDF khá dễ dàng cho các nhà phát triển, đồng thời kết xuất tùy chỉnh và đẹp hơn nhiều so với mặc định trên trình duyệt của người dùng.
Nguồn: https://blog.theodo.com/2021/10/pdf-generation-react-puppeteer/
1658335440
Puppeteer es una biblioteca de JavaScript que le permite crear secuencias de comandos e interactuar con las ventanas del navegador.
En esta guía, exploraremos los aspectos básicos del uso de Puppeteer con Node.js para que pueda comenzar a automatizar sus pruebas.
Puppeteer es una biblioteca de Node.js desarrollada por Google que le permite controlar Chrome sin interfaz a través del protocolo DevTools.
Es una herramienta para automatizar las pruebas en su aplicación utilizando dispositivos Chrome o Chromebit sin interfaz de usuario, sin necesidad de extensiones de navegador como Selenium Webdriver o PhantomJS.
Puppeteer le permite automatizar las pruebas de sus aplicaciones web. Con él, puede ejecutar pruebas en el navegador y luego ver los resultados en tiempo real en su terminal.
Puppeteer utiliza el protocolo WebDriver para conectarse con el navegador y simular la interacción del usuario con elementos o páginas HTML.
Node.js es un tiempo de ejecución de JavaScript de código abierto basado en el motor V8 de Chrome que se ejecuta en los sistemas operativos Linux, Mac OS X y Windows. Fue lanzado por primera vez en 2009 por Ryan Dahl, quien fue uno de sus colaboradores originales (con la ayuda de Douglas Crockford).
Node.js se ha vuelto inmensamente popular a lo largo de los años como parte esencial de muchos proyectos de desarrollo de software. Tiene amplias capacidades cuando se trata de codificar ciertas tareas, como aplicaciones del lado del servidor o protocolos de red punto a punto como Websockets.
Primero cree un directorio con el que trabajará haciendo clic derecho en su ubicación preferida y eligiendo una nueva carpeta. También puede usar el comando mkdir dir-name
en su terminal.
Luego cree un app.js
archivo en su carpeta y agregue el node.js
código como se muestra a continuación:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.freecodecamp.org/');
await browser.close();
})();
aplicación.js
El código anterior crea una instancia del navegador que permite iniciar Puppeteer. Asegurémonos de entender el código anterior:
browser.newPage()
crea una nueva páginapage.goto()
proporciona la URL parabrowser.newPage()
browser.close()
cierra el proceso en ejecuciónAhora abra su terminal y cd
en la carpeta. Luego ejecute npm init
para crear un package.json
archivo.
Presiona enter y luego escribe sí si te preguntan si está bien.
Su salida se verá así:
paquete.json
Siga las instrucciones de configuración para instalar las dependencias que usaremos en nuestro proyecto.
Para usar Puppeteer con Node.js, deberá instalar varios paquetes y configurar algunas variables de entorno. Esta parte lo guiará a través de los pasos que deberá seguir para usar Puppeteer en sus pruebas:
Solo necesita completar el último paso si desea ejecutar pruebas en un navegador real en lugar de solo probar con scripts de controladores web.
Si este es su caso, continúe e instale el módulo del controlador selenium-web desde el administrador de paquetes npm escribiendo npm i selenium-webdriver --save
.
La instalación de las dependencias generará node_modules
un package-lock.json
archivo como se muestra a continuación:
paquete-bloqueo.json
Las capturas de pantalla son una excelente manera de capturar información en su navegador. ¡Puppeteer te tiene cubierto!
Para tomar una captura de pantalla de la página web a la que navegó, agregue el fragmento de código a continuación:
await page.screenshot({path: 'example.png'});
Para ejecutar la aplicación:
cd puppeter-tut
cd src
Luego escriba el siguiente comando en su terminal:
node app.js
También puede crear un PDF agregando el siguiente fragmento en su código:
await page.pdf({ path: 'example.pdf' });
El fragmento de código anterior nos dará el resultado que se muestra a continuación:
Para probar su configuración, cree una test
carpeta en su código, luego agregue example.test.js
.
Su archivo debe contener el siguiente código:
const puppeteer = require('puppeteer')
describe("My first Setup Testing",()=>{
it("Home landing page",async()=>{
const browser = await puppeteer.launch({headless:false})
});
});
ejemplo.test.js
Ejecute su prueba usando npm run test
. Después de ejecutar su prueba, obtendrá el siguiente resultado:
Aquí hay un enlace de GitHub al código fuente del tutorial .
Como desarrollador web, puede utilizar Titiritero para ejecutar secuencias de comandos en el navegador Chrome sin interfaz gráfica de usuario y acceder al objeto de la ventana. Esto es útil cuando se prueban aplicaciones que necesitan acceso a recursos web como localStorage o cookies.
Para usar una instancia de navegador con Puppeteer, solo necesita pasar { headless: false }
al método de inicio. Es asincrónico, por lo que no bloqueará el hilo principal y hará que su aplicación no responda.
Lo mejor de este método es que, una vez lanzado, solo debe usarse una vez. De lo contrario, obtendrá un error al intentar acceder a cualquier página web de Puppeteer nuevamente.
Aquí hay un ejemplo:
let browser; (async() => { if(!browser) browser = await puppeteer.launch({headless: false});
Entonces, ¡ahí lo tienes! Ahora ya sabe cómo comenzar con Puppeteer y Node.js.
Espero que esta guía le haya ayudado a familiarizarse más con la herramienta y sus capacidades. No dude en ponerse en contacto conmigo si tiene alguna pregunta o sugerencia.
Fuente: https://www.freecodecamp.org/news/how-to-use-puppeteer-with-nodejs/
1658331660
Puppeteer é uma biblioteca JavaScript que permite criar scripts e interagir com as janelas do navegador.
Neste guia, exploraremos os fundamentos do uso do Puppeteer com Node.js para que você possa começar a automatizar seus testes.
Puppeteer é uma biblioteca Node.js desenvolvida pelo Google que permite controlar o Chrome sem periféricos por meio do DevTools Protocol.
É uma ferramenta para automatizar testes em seu aplicativo usando dispositivos Chrome ou Chromebit headless, sem exigir extensões de navegador como Selenium Webdriver ou PhantomJS.
O Puppeteer permite automatizar o teste de seus aplicativos da web. Com ele, você pode executar testes no navegador e depois ver os resultados em tempo real no seu terminal.
O Puppeteer usa o protocolo WebDriver para se conectar ao navegador e simular a interação do usuário com elementos ou páginas HTML.
O Node.js é um tempo de execução JavaScript de código aberto criado no mecanismo V8 do Chrome que é executado nos sistemas operacionais Linux, Mac OS X e Windows. Foi lançado pela primeira vez em 2009 por Ryan Dahl, que foi um de seus contribuidores originais (com alguma ajuda de Douglas Crockford).
O Node.js tornou-se imensamente popular ao longo dos anos como parte essencial de muitos projetos de desenvolvimento de software. Possui amplos recursos quando se trata de codificar determinadas tarefas, como aplicativos do lado do servidor ou protocolos de rede ponto a ponto, como Websockets.
Primeiro crie um diretório com o qual você trabalhará clicando com o botão direito do mouse no local de sua preferência e escolhendo uma nova pasta. Você também pode usar o comando mkdir dir-name
no seu terminal.
Em seguida, crie um app.js
arquivo em sua pasta e adicione o node.js
código conforme mostrado abaixo:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.freecodecamp.org/');
await browser.close();
})();
app.js
O código acima cria uma instância do navegador que permite iniciar o Puppeteer. Vamos nos certificar de que entendemos o código acima:
browser.newPage()
cria nova páginapage.goto()
fornece o URL parabrowser.newPage()
browser.close()
fecha o processo em execuçãoAgora abra seu terminal e cd
na pasta. Em seguida, execute npm init
para criar um package.json
arquivo.
Pressione enter e digite yes se perguntado 'isso está ok'.
Sua saída ficará assim:
pacote.json
Siga as instruções de configuração para instalar as dependências que usaremos em nosso projeto.
Para usar o Puppeteer com Node.js, você precisará instalar vários pacotes e configurar algumas variáveis de ambiente. Esta parte o guiará pelas etapas que você precisará seguir para usar o Puppeteer em seus testes:
Você só precisa concluir a última etapa se quiser executar testes em um navegador real, em vez de apenas testar em scripts de driver da Web.
Se este for o seu caso, vá em frente e instale o módulo do driver selenium-web do gerenciador de pacotes npm digitando npm i selenium-webdriver --save
.
A instalação das dependências irá gerar node_modules
e um package-lock.json
arquivo conforme mostrado abaixo:
pacote-lock.json
As capturas de tela são uma ótima maneira de capturar informações em seu navegador. Bem, o Puppeteer tem tudo para você!
Para fazer uma captura de tela da página da Web para a qual você navegou, adicione o snippet de código abaixo:
await page.screenshot({path: 'example.png'});
Para executar o aplicativo:
cd puppeter-tut
cd src
Em seguida, digite o comando abaixo no seu terminal:
node app.js
Você também pode criar um PDF adicionando o seguinte snippet em seu código:
await page.pdf({ path: 'example.pdf' });
O trecho de código acima nos dará a saída mostrada abaixo:
Para testar sua configuração, crie uma test
pasta em seu código e adicione example.test.js
.
Seu arquivo deve conter o seguinte código:
const puppeteer = require('puppeteer')
describe("My first Setup Testing",()=>{
it("Home landing page",async()=>{
const browser = await puppeteer.launch({headless:false})
});
});
exemplo.teste.js
Execute seu teste usando npm run test
. Depois de executar seu teste, você obterá a seguinte saída:
Aqui está um link do GitHub para o código-fonte do tutorial .
Como desenvolvedor web, você pode usar o Puppeteer para executar scripts no navegador Chrome headless e acessar o objeto window. Isso é útil ao testar aplicativos que precisam de acesso a recursos da Web, como localStorage ou cookies.
Para usar uma instância do navegador com o Puppeteer, você só precisa passar { headless: false }
para o método launch. É assíncrono, portanto, não bloqueará o thread principal e fará com que seu aplicativo não responda.
O melhor desse método é que, uma vez lançado, ele deve ser usado apenas uma vez. Caso contrário, você receberá um erro ao tentar acessar qualquer página da Web do Puppeteer novamente.
Aqui está um exemplo:
let browser; (async() => { if(!browser) browser = await puppeteer.launch({headless: false});
Então, aí está! Agora você sabe como começar a usar o Puppeteer e o Node.js.
Espero que este guia tenha ajudado você a se familiarizar mais com a ferramenta e seus recursos. Sinta-se à vontade para entrar em contato comigo se tiver dúvidas ou sugestões.
Fonte: https://www.freecodecamp.org/news/how-to-use-puppeteer-with-nodejs/