Если вы работаете в британской организации или компании в сфере Интернета или цифровых технологий, вы, вероятно, потратили хотя бы некоторое время на прошлой неделе на добавление баннера на один из своих сайтов со ссылкой на смерть королевы и соболезнования вашего работодателя.

Если вы живете в другом месте, возможно, вы работали над похожим баннером, но касающимся Covid, войны России против Украины, осуждения убийства Джорджа Флойда и/или поддержки движения BLM. Если вы работаете в центре города в очень крупной компании, у вашего работодателя может быть план реагирования на террористический акт, связанный с отправкой быстро меняющихся сообщений на ваш веб-сайт. С другой стороны, вас, возможно, попросили быстро добавить баннер, чтобы отпраздновать что-то радостное, например, заслуженный приз.

На моей предыдущей работе мы быстро торговались в начале Covid, добавляя баннеры с нулевым развертыванием на клиентские сайты, используя Диспетчер тегов Google для внедрения разметки JavaScript на их страницы, но этот подход вызывает сдвиг макета и работает медленно. В этой статье мы рассмотрим гораздо более совершенный подход, ставший возможным благодаря современным CDN и провайдерам, таким как AWS, Cloudflare, Fastly, Akamai и Netlify.

Единое реагирование на чрезвычайные ситуации на всех корпоративных веб-сайтах.

Когда 💩 поражает фанатов, важно выступить единым фронтом и сделать информацию доступной повсюду, как только она будет готова, но без накладных расходов и риска человеческой ошибки, которые возникают при попытке скоординировать слишком много команд. Кроме того, у ваших владельцев продуктов, менеджеров по доставке, разработчиков и всех остальных, вероятно, есть более ценные дела, чем каскадные изменения контента и присмотр за производственными выпусками для настройки текста баннера, который многие из ваших клиентов в конечном итоге проигнорируют (он говорит: не говорю из личного опыта — честно!)

Представьте, что вы работаете в крупной компании и вам нужно добавить свое сообщение о празднике, опасности или горе ко всему имуществу или портфолио различных веб-сайтов, включая маркетинговый сайт, сайт электронной коммерции, блог компании, сайт вакансий, интранет. , и так далее. Или работа в группе компаний, например, как у Co-op (мой работодатель) есть веб-сайт для нашего продовольственного/магазина у дома, веб-сайт электронной коммерции для нашего продовольственного бизнеса, веб-сайт для нашего бизнеса юридических услуг. , страховой бизнес, похоронный бизнес, плюс наше предложение о членстве и так далее. Есть много разных веб-сайтов, на которые можно добавить наш баннер!

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

Было бы не так уж сложно иметь одно место для доставки содержимого баннера сообщения (центрально управляемая CMS), но сложнее внедрить HTML, JavaScript и CSS в коллекцию разных сайтов одновременно, не требуя кучу разных системы должны быть обновлены, а процессы развертывания/выпуска должны быть запущены отдельно.

Как можно легко добавить глобальную часть контента на несколько сайтов?

Для демонстрации я использовал пограничную функцию Netlify для создания примера конечной точки (источника) на https://wolstenhol.me/api/fake-edge-messages-endpoint, которая возвращает одно из 3 возможных сообщений:

const imagineTheseCameFromACMS = [
  {
    theme: THEMES.emergency,
    text: "Something bad has happened, but all our staff are safe. We are posting hourly updates on our Twitter page.",
    link: "https://twitter.com/philw_",
  },
  {
    theme: THEMES.sombre,
    text: "We are saddened by X and wish Y",
  },
  {
    theme: THEMES.celebratory,
    text: `We won best place to work ${new Date().getFullYear()}!!!11`,
    link: "https://example.com/a-pr-blog-article",
  },
];

Как говорится в коде, представьте, что эта конечная точка поступает из CMS, такой как Contentful, Drupal, WordPress и т. д., и управляется центральной командой связи, которая знает, что ее нужно использовать только тогда, когда произошло что-то очень особенное (ура!), очень ужасное ( эээ…), или очень важный в глазах пиара (без комментариев 💂).

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

Вот пример простого ответа JSON, который он может вернуть:

➜  ~ curl https://wolstenhol.me/api/fake-edge-messages-endpoint
{
  "theme": "celebratory",
  "text": "We won best place to work 2022!!!11",
  "link": "https://example.com/a-pr-blog-article",
  "hash": "0f8a02f8e424c95d9768ecfb8cf5ac5772c6d0ef5706fde819f540375d39d93b"
}

или в темные времена:

➜  ~ curl https://wolstenhol.me/api/fake-edge-messages-endpoint
{
  "theme": "emergency",
  "text": "Something bad has happened, but all our staff are safe. We are posting updates on our Twitter page.",
  "link": "https://twitter.com/bigcorp",
  "hash": "d98d74b647b5eb02ce1cde89001532df2820758b11c70fa78b53d0e72080aa3e"
}

Но как превратить этот JSON в HTML, вставленный на ряд разных сайтов, все на разных технологических стеках и подключенных к их собственным CMS, или даже вставленный в статический контент вообще без CMS — но все на одной и той же CDN или CDN с аналогичным функционалом? Войди, ✨край✨…

Что такое «грань»? («Грань»? «Грань?» 🤷‍♂️)

Спросим у Google и Cloudflare:

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

В контексте этой статьи краем будет наша CDN, и мы можем запрограммировать его с помощью таких инструментов, как Cloudflare Workers, Netlify Edge Functions, Lambda@Edge (AWS), Akamai EdgeWorkers или Fastly Compute@Edge и так далее.

Эти инструменты позволяют нам изменять сетевой ответ, когда он проходит через уровень Edge/CDN, например, изменяя заголовки или тело ответа.

Я использую Cloudflare Workers, чтобы предложить версию моего личного веб-сайта без JS и без CSS, чтобы проверить, как сайт выглядит без CSS (это дает вам подсказки относительно того, использовать семантические элементы HTML или нет) и как сайт работает с заблокированным или отключенным JavaScript (или когда я написал такой ужасный JavaScript, что все рухнуло). Эти работники удаляют HTML-элементы, связанные со сценарием или стилем, а также добавляют HTTP-заголовок x-robots-tag, чтобы поисковые системы не индексировали эти страницы.

Добавление глобального баннера сообщений через край

Если я могу удалить элементы CSS и <script> со своего сайта с помощью Cloudflare worker, то должно быть легко использовать их для добавления в некоторый контент.

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

Вот пример.com обычно:

А вот пример.com при запуске через Cloudflare Worker. Если бы мы владели example.com, мы могли бы поместить worker перед ним, чтобы любой, кто запрашивает example.com, увидел бы баннер. А пока мы можем увидеть баннеры, посетив https://global-banners-from-the-edge.philgw.workers.dev:

Как это работает?

У меня нет сайта example.com, я не знаю, как он обновляется и на какой технологии он работает, но я добавил на него баннер. Если вы перезагрузите страницу, вы увидите, что другие баннеры появляются случайным образом, поскольку пример API каждый раз возвращает разное содержимое.

Мы могли бы сделать то же самое для любого другого сайта, принадлежащего нашей компании. Здесь я поместил работника перед одним из тех веб-сайтов «лучший веб-сайт для матери»…

…и перед Википедией:

Дело в том, что если бы все ваши сайты находились за Cloudflare, AWS Cloudfront с Lambda@Edge, Akamai EdgeWorkers, Fastly Compute@Edge, все размещены на Netlify или в любом другом сервисе, предлагающем пограничные рабочие процессы, то мы могли бы запустить один и тот же работник на всех из них, и один центральный источник контента баннеров может обновлять все эти разные сайты даже без необходимости открывать их CMS или их кодовую базу. В этом сила краевых функций!

Покажи мне код!

Большинство поставщиков пограничных функций предоставляют простой способ переписать содержимое страницы. Это отлично подходит для таких вещей, как A/B-тестирование или баннеры cookie без сильной зависимости от JavaScript. В случае с Cloudflare мы используем класс HTMLRewriter.

Вот упрощенная версия, в которой мы используем worker для извлечения некоторых данных и добавления HTML, JavaScript и CSS на страницу, а также изменения Cache-Control заголовка страницы:

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});
const getData = async () => {
  const response = await fetch(
    "https://wolstenhol.me/api/fake-edge-messages-endpoint"
  );
  const json = await response.json();
  return json;
};
class HeadHandler {
  constructor(data) {
    this.data = data;
    // Please excuse the use of a third-party origin CDN,
    // I wouldn't do this in prod…
    this.styles = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/herald.css" integrity="sha256-zQLpc/AA/o1D8NgVLASianBlbMPs9i4carXMzf/L4mY=" crossorigin="anonymous">`;
    this.scripts = `<script src="https://cdn.jsdelivr.net/npm/[email protected]/herald.js" integrity="sha256-AcoJNZAkXVxpi/5ZW/CXeUadY0z5rEH7h/3OAs5HnTg=" crossorigin="anonymous"></script><script>let key = "${data.hash}"; let savedKey = localStorage.getItem("banner--cta-url"); if(savedKey === key) { document.documentElement.classList.add("banner--hide"); }</script>`;
  }
  element(element) {
    element
      .append(this.styles, { html: true })
      .append(this.scripts, { html: true });
  }
}
class BodyHandler {
  constructor(data) {
    this.data = data;
    this.bannerTemplate = `<announcement-banner data-banner-key="${
      data.hash
    }" data-theme="${data.theme}"> ${
      data.link ? `<a href="${data.link}">` : ""
    } ${data.text} ${
      data.link ? `</a>` : ""
    } <button type="button" data-banner-close>Close</button></announcement-banner>`;
  }
  element(element) {
    element
      .prepend(this.bannerTemplate, { html: true });
  }
}
async function handleRequest() {
  const data = await getData();
  // In real-world usage the below line wouldn't be necessary as
  // we would work with the current request.
  const response = await fetch("https://example.com");
  const transformedResponse = new HTMLRewriter()
    .on("head", new HeadHandler(data))
    .on("body", new BodyHandler(data))
    .transform(response);
  // Don't cache the page so that we can update the banner easily.
  transformedResponse.headers.set("cache-control", "no-store, must-revalidate");
  return transformedRes;
}

Немного о самом баннере

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

Баннер появляется сразу вместе с остальным содержимым страницы и не вызывает смещения макета. Это связано с тем, что HTML-код для баннера поступает вместе с HTML-кодом остальной части страницы. Если бы мы использовали компонент React/Preact/Vue/Alpine для загрузки JSON из конечной точки примера, а затем визуализировали бы баннер, мы бы увидели сдвиг макета, поскольку содержимое баннера появилось бы после того, как остальная часть страницы была выложена.

Баннер можно закрыть, и если вы перезагрузите страницу несколько раз, вы не увидите этот баннер снова, пока не закроете другой баннер. Это связано с тем, что каждому баннеру присваивается уникальный ключ, а веб-компонент Zach Leatherman’s «вестник собаки <announcement-banner>» запоминает последний баннер, который был отклонен, и больше не будет его показывать. Некоторый JavaScript в <head> страницы гарантирует, что баннер не мерцает до того, как он будет скрыт (JavaScript в <head> запускается до того, как браузер что-либо отобразит).

Баннер размещается вверху страницы. Думаю, это самый безопасный вариант. Он вставляется сразу после открывающего тега <body>. Было бы заманчиво также попробовать добавить его в начале <main> или сразу после <header>, чтобы он отображался под заголовком сайта в героическом месте, но подумайте, как это будет работать с сгенерированной не очень семантической разметкой. на вашем сайте доски объявлений о вакансиях или с помощью JavaScript-зависимого SPA, весь HTML-контент которого в основном составляет <body><div id="root"></div><noscript>Lol, sorry!</noscript></body>.

Баннер имеет разную тематику. Я думаю, что это необходимо для гибкой системы. Вам понадобится веселая тема для хороших новостей, красная тема для экстренных ситуаций, когда происходит что-то плохое, и грустная/мрачная тема для корпоративных размышлений и сочувствия. Мы управляем темой с помощью атрибута данных в компоненте баннера и некоторого CSS, который мы внедряем на страницу. Я мог бы усерднее работать над этим аспектом и использовать более тщательный способ предотвращения проникновения стилей страницы в баннер (all: unset или CSS с ограниченной областью действия в веб-компоненте), но это не было в центре внимания этого сообщения в блоге.

Если вам нужно внести небольшие изменения для разных сайтов, это можно сделать несколькими способами. Настройками контента можно управлять в worker, а настройками стиля можно управлять, настраивая специфику CSS баннера (чтобы разрешить применение некоторых сайтов для конкретных сайтов) или создавая модифицированные темы, например celebratory--dense для более компактного праздничного баннера. для использования в веб-приложении, а не на маркетинговом сайте.

В заключение

Я думаю, что краевые работники — ВЕЛИКОЛЕПНЫ. Мы можем выпускать сильно кешированные или статические веб-сайты, но добавлять слой динамизма сверху, и все это управляется нашим провайдером CDN. Мы можем формировать сетевые запросы по мере того, как они проходят через CDN, и использовать это для добавления контента в любой HTML-ответ, независимо от того, где этот сайт размещен или как он поддерживается. Даже мертвый сайт на сервере, к которому никто не знает, как получить доступ, может по-прежнему иметь добавленный контент, если его доменное имя может указывать на службу CDN.

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

Поделиться этой записью

https://twitter.com/philw_/status/1570680943031234564