Запуск Eleventy Serverless на AWS [электронная почта защищена]

Запуск Eleventy Serverless на AWS [электронная почта защищена]

15 марта 2023 г.

Запуск Eleventy Serverless на AWS Lambda@Edge

110 – это здорово. Это генератор статических сайтов, написанный на JavaScript, для «быстрой сборки и даже более быстрых веб-сайтов». Он в 10–20 раз быстрее, чем альтернативы, такие как Gatsby или Next.js. Вы получаете весь свой контент в статическом виде и готовый к доставке через CDN. Вам не нужно беспокоиться о рендеринге на стороне сервера, чтобы развернуть эти красивые социальные сети. И, если у вас есть большой набор данных, это прекрасно — Eleventy может без проблем генерировать десятки тысяч страниц. .

Что делать, если у вас ОГРОМНЫЙ набор данных?

При создании системы безопасности Sandworm с открытым исходным кодом & проверки соответствия лицензий для пакетов JavaScript, мы хотели создать каталог красивых визуализаций отчетов для каждой библиотеки в реестре npm. То есть для каждой версии каждой библиотеки в реестре. Вскоре мы узнали — это более 30 миллионов версий пакетов. Удачи вам в создании, загрузке и поддержании такого количества HTML-страниц в актуальном состоянии в разумные сроки, верно?

Мы решили сократить наш набор данных до самых популярных пакетов. Мы рассматривали реализацию частичных сборок, при которых устаревшие страницы отчетов постоянно генерировались и загружались.

Но в итоге мы реализовали решение Eleventy Serverless, подключаемый модуль, который запускает один или несколько файлов шаблонов во время запроса для создания динамических страниц. Таким образом, вместо того, чтобы просматривать весь набор страниц во время сборки, этот плагин позволяет нам отделить «обычные» страницы контента, отображаемые при сборке, от «динамических» страниц, отображаемых по запросу. Затем мы можем просто сгенерировать и загрузить статический контент (например, домашнюю страницу, страницу с информацией и т. д.) в CI, а затем развернуть некоторый код на поставщике вычислений, который будет генерировать страницу пакета npm, когда пользователь переходит по определенному URL-адресу. Отлично!

:::информация За исключением: Eleventy Serverless создан для готовой работы с функциями Netlify, и мы работаем на AWS.

:::

Хорошая новость заключается в том, что вы можете заставить Eleventy Serverless работать в AWS Lambdas. Более того, вы можете запустить его в Lambda@Edge, который запускает ваш код глобально в расположениях AWS рядом с вашими пользователями, чтобы вы могли предоставлять полнофункциональный, настраиваемый контент с высокой производительностью и малой задержкой.

Настройка одиннадцати

Сначала давайте заставим Eleventy запустить локальную сборку.

Начнем с его установки:

npm i @11ty/eleventy --dev

Затем давайте создадим простейший шаблон для нашей статической страницы Eleventy. Мы напишем его, используя Liquid, но, поскольку он такой простой, пока не будем использовать какие-либо полезные теги шаблонов. .

Назовем его index.liquid:

<h1>Hello</h1>

Вот и все, мы готовы строить и обслуживать! Выполнить:

npx @11ty/eleventy --serve

[11ty] Writing _site/index.html from ./src/index.liquid
[11ty] Serverless: 3 files bundled to ./serverless/edge.
[11ty] Wrote 1 file in 0.12 seconds (v2.0.0)
[11ty] Watching
[11ty] Server at http://localhost:8080/

Посетите http://localhost:8080/ в своем браузере на этом этапе, и вы должны увидеть заголовок «Привет», который мы создали выше. Аккуратно!

Настройка подключаемого модуля Eleventy Serverless

Подключаемый модуль Serverless входит в состав Eleventy и не требует установки чего-либо с помощью npm. Однако нам нужно настроить его.

Для этого нам нужно создать конфигурационный файл Eleventy:

// .eleventy.js
const { EleventyServerlessBundlerPlugin } = require("@11ty/eleventy");

module.exports = function(eleventyConfig) {
  eleventyConfig.addPlugin(EleventyServerlessBundlerPlugin, {
    name: "edge",
    functionsDir: "./serverless/",
    redirects: false,
  });

  return {
    dir: {
      input: 'src',
    },
  };
};

Давайте разберем конфигурацию плагина:

  • Уникальное имя каждого подключаемого модуля будет определять имя выходного каталога сборки и потребуется при назначении постоянных ссылок. Вы также можете создать несколько подключаемых модулей, чтобы разные функции обрабатывали разные страницы.

* functionsDir позволяет указать путь к выходному каталогу сборки; в нашем случае плагин будет генерировать файлы в каталоге ./serverless/edge относительно корня приложения.

* redirects настраивает, как должны обрабатываться перенаправления Netlify — поскольку мы не работаем на Netlify, мы устанавливаем для этого параметра значение false, чтобы пропустить создание файла netlify.toml.

* Наконец, в объекте конфигурации, который мы возвращаем в Eleventy, мы указываем входной каталог для нашего контента, чтобы все было аккуратно. Мы также продолжим и переместим созданный ранее файл index.liquid в каталог src.

Далее снова создадим сборку, запустив npx @11ty/eleventy и изучив, что выводится в ./serverless/edge.

Вы должны увидеть следующее:

  • Несколько файлов js и json, начинающихся с одиннадцать-. Некоторые из них являются файлами конфигурации, а некоторые предназначены для информирования сборщика Netlify о зависимостях функций — нам они не понадобятся для Lambda.

* eleventy.config.js, копия основного файла конфигурации в корне приложения.

* Каталог src с шаблоном index.liquid.

* Файл index.js с фактическим кодом бессерверного обработчика, который мы обновим и развернем в Lambda.

Давайте проигнорируем артефакты сборки, которые нам не нужны в нашем репозитории, и пока оставим только файл index.js.

Добавьте это в свой файл .gitignore:

serverless/edge/**
!serverless/edge/index.js

Хорошо, теперь мы готовы создать нашу первую динамически сгенерированную страницу.

Давайте создадим для него еще один простой файл Liquid в src/edge.liquid:

---
permalink:
  edge: /hello/
---
<h1>Hello@Edge</h1>

Вы заметите, что для этого файла мы добавили некоторые вступительные данные в жидкий шаблон.

В частности, мы определили постоянную ссылку для нашей страницы, на которую она будет реагировать при работе с подключаемым модулем Edge. Eleventy не будет создавать страницу edge.html при создании — эта страница будет создана только путем вызова кода бессерверного обработчика.

Сделать вещи совместимыми с Lambda

Теперь посмотрим, что происходит с serverless/edge/index.js. Он генерируется только в исходной сборке, поэтому мы можем его изменить — и нам это обязательно понадобится для поддержки Lambda@Edge.

* Во-первых, мы можем удалить require("./eleventy-bundler-modules.js"), так как он нужен только для процесса сборки Netlify;

* Далее нам нужно получить ссылку на текущий путь запроса и запрос, так как Eleventy нужна эта информация, чтобы знать, какой контент генерировать. С Netlify вы получаете их через event.rawUrl, event.multiValueQueryString и event.queryStringParameters. С помощью Lambda@Edge мы будем получать события, созданные CloudFront, по запросам источника — см. пример в документации AWS. Мы также будем использовать querystring для анализа строки запроса.

Давайте обновим код следующим образом:

javascript const {запрос} = event.Records[0].cf; const path = ${request.uri}${request.uri.endsWith("/") ? "" : "/"}; константный запрос = querystring.parse(request.querystring); let elev = new EleventyServerless("край", { путь, запрос, functionsDir: "./", });

* Наконец, нам нужно обновить возвращаемые обработчиком объекты, чтобы они соответствовали формату, ожидаемому Lambda@Edge.

Обновить успех & статус ответов об ошибках и заголовки для:

javascript { статус: "200", заголовки: { "управление кешем": [ { ключ: "управление кешем", значение: "max-age=0", }, ], "тип содержимого": [ { ключ: "Content-Type", значение: "text/html; charset=UTF-8", }, ], }, тело: ... }

* Мы также добавили заголовок Cache-Control для настройки того, как CloudFront кэширует возвращаемые результаты. Мы можем подумать об этом при переходе к рабочей среде, но пока не будем кэшировать.

И последнее: нам нужно отделить зависимости сборки от зависимостей пограничной обработки, поэтому давайте создадим отдельный файл package.json в serverless/edge и установим @11ty/edge в качестве зависимости продукта.

По мере роста нашей пограничной функции мы будем добавлять сюда новые элементы, например клиенты базы данных.

Sandworm Audit

Вот полный код нашего обработчика для справки:

Локальное тестирование

Хорошо, давайте проверим это локально перед развертыванием! Должно быть довольно легко имитировать отправку события в нашу функцию-обработчик.

Давайте создадим простой файл test.js:

const { handler } = require('.');

(async () => {
  const response = await handler({Records: [{cf: {request: {uri: "/hello/", querystring: ""}}}]});

  console.log(response);
})();

Запустив node test.js в консоли, вы должны увидеть:

{
  status: '200',
  headers: { 'cache-control': [ [Object] ], 'content-type': [ [Object] ] },
  body: '<h1>Hello</h1>'
}

Найдите минутку, чтобы отпраздновать! Вы только что запустили свою первую сборку Eleventy в бессерверной функции. 🎊

Развертывание в AWS

Все выглядит хорошо — пришло время развернуть это на AWS. Чтобы справиться с развертыванием, мы будем использовать Serverless. Нет, не подключаемый модуль Eleventy Serverless, а Serverless, «инструмент для разработки с нулевым сопротивлением для автоматического масштабирования приложений на AWS Lambda», инструмент командной строки.

Если он у вас не установлен, запустите npm install -g serverless.

Затем создайте файл serverless/edge/serverless.yml для настройки развертывания:

* Это создаст экземпляр раздачи CloudFront, подключенный к корзине, указанной в разделе events>cloudfront>origin. Любые вызовы URL-адресов, соответствующих pathPattern, будут перенаправляться в бессерверный обработчик, а не в корзину.

* Забавный факт: функции Lambda@Edge регистрируют выходные данные консоли в своем региональном CloudWatch. То есть, если пользователь в Германии получает доступ к вашим страницам через границу по адресу eu-frankfurt-1, вы увидите журналы для этого конкретного запуска под eu-frankfurt-1 области и больше нигде. В конфигурации yml мы обязательно даем нашей функции соответствующие разрешения на запись групп журналов в любом месте.

* Мы также должны добавить исключение для файла конфигурации в .gitignore — мы хотим, чтобы это было в репозитории.

* Если у вас уже есть дистрибутив CloudFront, который вы хотите подключить к своей новой бессерверной функции, ознакомьтесь с плагин serverless-lambda-edge-pre-existing-cloudfront.

Мы готовы к развертыванию! Убедитесь, что вы экспортируете учетные данные AWS для пользователя IAM с соответствующими разрешениями для развертывания всего стека. При переходе к рабочей среде в целях безопасности вам следует создать выделенного пользователя с минимальным набором необходимых разрешений — однако мне не удалось найти полный список таких разрешений, так что это, вероятно, будет утомительным пробным и -error процесс их выяснения путем попытки развертывания и просмотра того, что не удается.

Пока он все еще находится в разработке, пользователь с правами администратора может быть проще в использовании. Запустите sls deploy --stage prod для развертывания. Если все пойдет хорошо, через пару минут вы должны увидеть URL-адрес вашего нового дистрибутива CloudFront!

Однако ваши настройки должны будут распространяться по всему миру, поэтому может потребоваться еще несколько минут, чтобы все было готово. Вы можете проверить текущий статус вашего дистрибутива на панели управления консоли AWS. После завершения развертывания при переходе к CF_URL/hello в браузере должен отображаться наш HTML-заголовок «Hello@Edge» из шаблона edge.liquid. .

Мы сделали это! 🙌

Бонус: сделать его динамичным

Теперь давайте быстро заставим нашу бессерверную функцию делать что-то асинхронное. Пусть он принимает параметр URL, который является именем покемона, и отвечает изображением указанного милого зверя. Мы будем использовать https://pokeapi.co/ для получения изображения.

Мы могли бы выполнять асинхронную работу за пределами одиннадцати, а затем вводить глобальные данные следующим образом:

const eleventy = new EleventyServerless('serverless', {
  path,
  query,
  functionsDir: './',
  config: (config) => {
    config.addGlobalData('data', yourData);
  },
});

Или, что еще лучше, начиная с Eleventy 2.0.0, мы можем использовать асинхронные фильтры.

Давайте сначала обновим наш шаблон edge.liquid, включив в него новый HTML, который нам нужен:

---
permalink:
  edge:
    - /hello/
    - /hello/:name/
---
<h1>Hello@Edge {{ eleventy.serverless.path.name }}</h1>
{% if eleventy.serverless.path.name %}
  <img src="{{ eleventy.serverless.path.name | escape | pokeimage }}" />
{% endif %}

* Мы добавили новую постоянную ссылку, которая включает параметр пути имени. Он станет доступен в каскаде данных как eleventy.serverless.path.name.

* Мы преобразуем этот параметр name с помощью двух фильтров: escape и pokeimage. Помните, что пользовательский ввод следует рассматривать как потенциально вредоносный 😉.

* Нам нужно определить наш фильтр pokeimage. Вот где происходит асинхронная магия. Добавьте это в свой файл .eleventy.js:

javascript одиннадцатиКонфигурация.addAsyncFilter ("pokeimage", асинхронная функция (имя) { константные результаты = ожидание выборки (https://pokeapi.co/api/v2/pokemon/${name}); const json = ожидание результатов.json(); вернуть json.sprites.front_default; });

Здесь мы полагаемся на встроенный в узел API fetch — хорошо, что мы установили runtime: nodejs18.x в нашем serverless.yml< /код> файл.

Давайте обновим наш файл test.js, чтобы он запрашивал URL-адрес /hello/ditto/, и снова запустим node test.js.

В выводе консоли вы должны теперь увидеть:

{
  status: '200',
  headers: { 'cache-control': [ [Object] ], 'content-type': [ [Object] ] },
  body: '<h1>Hello@Edge ditto</h1>n' +
    'n' +
    '  <img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/132.png" />n'
}

Последний sls deploy --stage prod, чтобы развернуть это, и готово! Вы освоили настройку Eleventy Serverless на Lambda@Edge.

Все страницы отчетов о пакетах Sandworm npm генерируются с использованием Eleventy Serverless и Lambda@Edge.


Оригинал