Введение в JAMstack. Как сделать статический сайт на Hugo и Okta

JavaScript
6719
Введение в JAMstack. Как сделать статический сайт на Hugo и Okta
Active vision

Если вы веб-разработчик, то скорее всего, слышали о таком термине как "JAM stack". Нет, он не имеет никакого отношения к тостам с джемом. К тому моменту как вы закончите читать эту статью, вы будете иметь представление о том, что такое JAMstack и какие у него преимущества, а также узнаете как самому сделать сайт на JAMstack. Приступим.

JAMstack, как сделать статический сайт на Hugo и Okta

Быстрая навигация

  1. # JAMstack — быстрый, масштабируемый, не дорогой
  2. # Создаем JAMstack с генератором статических сайтов Hugo
  3. # JavaScript и API
  4. # Добавляем аутентификацию Okta
  5. # Добавляем Okta Login в Hugo

"JAM" в JAMstack означает JavaScript, API и разметка(markup). Архитектура JAMstack делает веб-приложения дешевле в разработке, производительней и безопаснее.

JAMstack не заставляет вас переходить на какую-либо конкретную технологию. Вы можете использовать любую JavaScript библиотеку c которой привыкли работать (TypeScript, Elm, Clojure или WebAssembly), получать данные или отправлять обновления от сторонних или ваших собственных API. Для управления контентом можно выбрать один из множества генераторов статических сайтов, таких как Hugo, GatsbyJS, Jekyll, Next.js, Nuxt.js или VuePress, и писать контент с помощью Markdown или любой другой системы разметки, которая отображает как HTML.

1JAMstack — быстрый, масштабируемый, не дорогой

Традиционные веб-приложения и CMS полагаются на серверный код, который отправляет HTML в ответ на каждый запрос пользователя. Часто подобные системы включают базу данных и тем самым добавляют еще один уровень задержки. Масштабировать традиционную систему с большим количеством серверов — не такая простая задача. Добавьте к этому сложность кэширования и получается настоящая головная боль.

При использовании JAMstack`a каждая страница создается (компилируется) заранее, когда приложение развертывается. Все HTML, JavaScript, CSS и изображения, необходимые для приложения, сразу полностью готовы. Задача состоит в том, чтобы выполнять как можно меньше кода на стороне сервера, поскольку обслуживание статических файлов происходит быстрее и намного проще.

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

отличие статических и динамических сайтов отличие статических и динамических сайтов

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

2Создаем JAMstack с генератором статических сайтов Hugo

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

  • Next.js — язык JavaScript
  • Jekyll — язык Ruby
  • Hugo — язык Go
  • Gatsby — язык JavaScript
  • Hexo — язык JavaScript
  • Nuxt — язык JavaScript
  • GitBook — язык JavaScript
  • VuePress — язык JavaScript
  • Docusaurus — язык JavaScript
  • Pelican — язык Python
  • MkDocs — язык Python
  • и другие

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

Необходимый софт и компоненты:

  1. В зависимости от вашей операционной системы:
    Windows: Chocolatey
    macOS: Homebrew
    Linux: Linuxbrew
  2. Система контроля версий Git
  3. Если у вас еще нет любимого редактора кода, установите Visual Studio Code
  4. Бесплатный Okta Developer Account

Установка Chocolatey в Windows

В меню Пуск найдите PowerShell и откройте его от имени администратора. Затем вставьте следующий сценарий и нажмите Enter.


PowerShell
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
Установка Chocolatey

Установка Chocolatey окончена, в PowerShell должно быть написано Chocolatey (choco.exe) is now ready.

Устанавливаем Hugo

Первым делом установим Hugo. Hugo - это приложение интерфейса командной строки (CLI), которое запускается в командной строке или терминале. Откроем командную строку от имени администратора.

Chocolatey (Windows)


BASH
choco install hugo -confirm
Установка Hugo

Homebrew или Linuxbrew


BASH
brew install hugo

Создаем новое Hugo приложение

В командной строке перейдите в папку, в которой вы хотите сохранить свое приложение. К примеру, cd c:\Repository. Затем скопируйте и вставьте строку ниже, нажмите Enter


BASH
hugo new site jamstack-app

Эта команда создаст новую папку с именем jamstack-app

Добавим тему для Hugo используя Git

Существует большое количество прекрасных тем для Hugo, посмотрите сами на https://themes.gohugo.io. В этой статье мы будем использовать Tranquilpeak


BASH
cd jamstack-app
git init
git submodule add https://github.com/kakawait/hugo-tranquilpeak-theme.git themes/hugo-tranquilpeak-theme

Если вы не забыли установить Git, то в каталоге themes появится папка hugo-tranquilpeak-theme с таким содержимым:

 папка hugo-tranquilpeak-theme

Обновляем конфигурацию Hugo

Откройте проект jamstack-app в вашем любимом редакторе кода. В папке /themes/hugo-tranquilpeak-theme/exampleSite откройте файл config.toml и скопируйте все что там находится.

Затем откройте файл /config.toml из корневой папки и вставьте туда ранее скопированный код. Обновите заголовок и имя автора.

Создадим новый пост на сайте

Чтобы создать пост используем Hugo CLI


BASH
hugo new post/hello-world.md

Перейдем в редактор, там уже будет создан наш пост /content/post/hello-world.md. Для управления контентом Hugo использует Markdown. В верхней части MD файла находятся метаданные поста, добавьте какой-нибудь контент под ними и сохраните файл.


MD
---
title: "Hello World"
date: 2019-11-04T14:23:25+03:00
categories:
- category
- subcategory
tags:
- tag1
- tag2
keywords:
- tech
#thumbnailImage: //example.com/image.jpg
---
Привет! Это мой первый пост на Hugo!

Запускаем Hugo сервер

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


BASH
hugo server -D
Запускаем Hugo сервер

Откройте ваш браузер, в адресную строку вставьте url из терминала. По умолчанию он должен быть http://localhost:1313. Вернитесь обратно в редактор, поменяйте что-нибудь в тексте и сохраните файл. Hugo обнаруживает изменения и автоматически обновляет ваш сайт в браузере.

Статический сайт пример

3JavaScript и API

Букву M (Markup) из JAM мы немного разобрали. Теперь поговорим о J (JavaScript) и A (API). Файлы статичны на стороне сервера, но когда будут доставлены на клиент, они будут настолько динамичны, насколько вы этого захотите.

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

4Добавляем аутентификацию Okta

С помощью Okta мы сможем добавить аутентификацию, авторизацию в приложение за несколько минут. Первый шаг - зарегистрировать бесплатный аккаунт разработчика.

Создаем приложение Okta

После того, как вы зарегистрировались зайдите в панель управления и нажмите Applications, затем Add Application.

Создаем приложение Okta

Нажмите Native. Этот параметр поддерживает последний, более безопасный стандарт Proof Key for Code Exchange (PKCE). Затем нажмите Next.

В поле Name запишем имя нашего приложения, а в Login redirect URIs наш url http://localhost:1313. И жмем Done

Настройка Okta

Скопируем Client ID

Вы окажитесь на странице настроек приложения. Внизу будет раздел Client Credentials. Скопируйте Client ID и сохраните где-нибудь под рукой, он нам еще понадобится.

Okta Client ID

Добавляем Trusted Origin

Чтобы приложение Hugo могло обмениваться информацией с Okta, нужно добавить в Okta url адрес приложения. Сначала нажмите на вкладку API и выберите Trusted Origins.

приложение Hugo и Okta

Далее нажмите Add Origin, впишите название вашей организации в поле Name. Замените Origin URL вашим http://localhost:1313. Отметьте галочками CROS и Redirect. Нажмите Save.

Настройка Okta для Hugo

Включаем Self-Service Registration

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

Okta Self-Service Registration

Жмем кнопку Enable Registration. В появившейся форме убедимся, что в поле Self-service registration стоит Enabled.

Self-service registration Enabled Okta Registration

Копируем Organization URL

Осталось только получить только URL организации. Нажмите вкладку Dashboard. Вы увидите его в правой части экрана, также сохраните его под рукой.

Okta Organization URL

5Добавляем Okta Login в Hugo

Теперь, когда Okta настроен, мы можем добавить регистрацию и войти на свой Hugo сайт. Сейчас мы кастомизируем шаблон, создадим страницу входа и добавим Sign-In виджет Okta.

Добавляем настройки Okta в конфигурацию Hugo

Открываем файл /config.toml и найдите раздел # Menu Configuration. После пункта меню About добавьте такую конфигурацию:


TOML
[[menu.main]]
  weight = 6
  identifier = "login"
  name = "Login"
  pre = "<i class=\"sidebar-button-icon fa fa-lg fa-lock\"></i>"
  url = "/login"

В самом конце файла config.toml, добавьте новую секцию [params.okta] с информацией ниже. Замените {yourOktaClientId} и {yourOktaOrgUrl} на значения, которые вы записали ранее.


TOML
[params.okta]
    clientId = "{yourOktaClientId}"
    baseUrl = "{yourOktaOrgUrl}"
    redirectUri = "http://localhost:1313/login/"

Добавляем файлы JavaScript

В папке /static, создайте новую папку с именем js. В этой папке создайте новый файл с именем okta-login.js и добавьте в него JavaScript, который вы видите ниже. Это JS код будет использован только на странице входа.


JS
// с помощью jQuery дождемся окончания загрузки документа
$(document).ready(function () {
  // подхватим конфигурацию Okta из /layouts/partials/script.html
  var config = window.okta.config;

  // создаем экземпляр виджета Sign-In Okta
  var signIn = new OktaSignIn({
    clientId: config.clientId,          // обязательно
    baseUrl: config.baseUrl,            // обязательно
    redirectUri: config.redirectUri,    // обязательно
    authParams: {
      display: 'page',
      responseType: 'code',
      grantType: 'authorization_code'
    },
    features: {
      registration: true                // разрешаем регистрацию пользователя
    }
  });

  // проверяем не является ли текущий запрос обратным перенаправлением с Okta login
  function isRedirect() {
    return /((code|state)=)/.test(window.location.hash);
  }

  // Получаем сессию входа
  function getSession() {
    return signIn.authClient.session.get()
      .then(function (session) {
        if (session.status === "ACTIVE") {
          return session.user().then(function (user) {
            return {
              session,
              user
            }
          });
        }
        return { session, user: {} };
      })
      .catch(function (err) {
        console.error("session error", err);
      });
  }

  function showWelcomeMessage(profile) {
    $('#okta-login-firstname').html(profile.firstName)
    $('#okta-login-success').show();
  }

  // функция клика для кнопки выхода
  $('#okta-sign-out').click(function() {
    signIn.authClient.session.close().then(function() {
      location.reload();
    });
  });

  if (isRedirect()) {
    // Парсим токен полученный от Okta
    signIn.authClient.token.parseFromUrl()
      .then(function (res) {
        var accessToken = res[0];
        var idToken = res[1];
        // set tokens for the active session
        signIn.authClient.tokenManager.add('accessToken', accessToken);
        signIn.authClient.tokenManager.add('idToken', idToken);

        // используем Okta API чтобы получить текущего пользователя
        return getSession()
          .then(function(res) {
            // показываем приветственное сообщение
            showWelcomeMessage(res.user.profile);
          })
          .catch(function (err) {
            console.error("getSession error", err);
          });
      })
      .catch(function (err) {
        console.error("parseFromUrl error", err);
      });
  } else {
    // Пробуем получить сессию
    getSession()
      .then(function(res) {
        if (res.session.status === 'ACTIVE') {
          // Показываем приветственное сообщение, если сессия активна
          showWelcomeMessage(res.user.profile);
          return;
        }
        // Показываем форму входа, если сессия не активна
        signIn.renderEl({ el: '#okta-login-container' });
      })
      .catch(function(err){
        console.error(err);
      });
  }
});

Добавьте новый файл с именем okta.js в папку /static/js вставьте туда JS, который видите ниже. Этот код будет использоваться на всем сайте, кроме страницы входа.


JS
// с помощью jQuery дождемся окончания загрузки документа
$(document).ready(function () {
  // Создать экземпляр Sign-In виджета Okta
  var config = window.okta.config;
  var signIn = new OktaSignIn({
    clientId: config.clientId,
    baseUrl: config.baseUrl,
    redirectUri: config.redirectUri
  });

  // функция для получения активной сессии, если такая есть
  function getSession() {
    return signIn.authClient.session.get()
      .then(function (session) {
        if (session.status === "ACTIVE") {
          return session.user().then(function (user) {
            return {
              session,
              user
            }
          });
        }
        return { session, user: {} };
      })
      .catch(function (err) {
        console.error("session error", err);
      });
  }

  function showWelcomeMessage(profile) {
    $('#okta-info .firstName').html(profile.firstName);
    $('#okta-info').show();
  }

  // показываем приветственное сообщение, если есть активная сессия
  getSession()
    .then(function(res) {
      if (res.session.status === 'ACTIVE') {
        showWelcomeMessage(res.user.profile);
      }
    })
    .catch(function(err){
        console.error(err);
    });
});

Настраиваем тему

В папке /layouts создадим еще одну папку с именем partials и внутри добавим файл okta-login.html. И внутри вставим следующий HTML:


HTML
<div id="okta-login-container"></div>
<div id="okta-login-success" style="display:none;">
  <h2>Welcome, <span id="okta-login-firstname"></span>!</h2>
  <p>You are currently logged in.</p>
  <p><button id="okta-sign-out">Sign Out</button></p>
</div>

В папке /layouts создайте новую директорию с именем login. И в /layouts/login добавьте новый файл login.html с кодом ниже.


HTML
{{ partial "head.html" . }}
<body>
  <div id="blog">
    {{ partial "header.html" . }}
    {{ partial "sidebar.html" . }}
    {{ partial "post/header-cover.html" . }}
    <div id="main" data-behavior="{{ .Scratch.Get "sidebarBehavior" }}"
      class="{{ with .Params.coverimage }}hasCover{{ end }}
             {{ if eq .Params.covermeta "out" }}hasCoverMetaOut{{ else }}hasCoverMetaIn{{ end }}
             {{ with .Params.coverCaption }}hasCoverCaption{{ end }}">
      <article class="post">
        <div class="post-header main-content-wrap {{ if $.Params.metaalignment }}text-{{ $.Params.metaalignment }}{{ else }}text-left{{ end }}">
          <h1 class="post-title" itemprop="headline">
            {{ $.Title }}
          </h1>
          {{ partial "okta-login.html" . }}
        </div>
        {{ partial "footer.html" . }}
      </article>
    </div>
  </div>
{{ partial "foot.html" . }}
По сути это тот же файл шаблона, что и /themes/hugo-tranquilpeak-theme/layouts/_default/single.html, но с измененным кодом внутри, включая $.Title и новый partial "okta-login.html.

Правим шаблон темы

Далее мы сделаем копии еще трех файлов и изменим их. Когда Hugo рендерит сайт, он сначала ищет файлы шаблонов в папке /layouts. Любой файл, найденный в этой папке переопределяет файл с таким же названием из папки /themes/hugo-tranquilpeak-theme/layouts/

Добавьте новый файл в /layouts/partials, назовите его script.html. Откройте исходный файл script.html, расположенный по адресу /themes/hugo-tranquilpeak-theme/layouts/partials/script.html, и скопируйте все содержимое в /layouts/partials/script.html. Затем в самом низу нового файла script.html добавьте следующую разметку.


HTML
{{ if ( isset .Site.Params "okta" ) }}
  <script src="https://global.oktacdn.com/okta-signin-widget/3.1.0/js/okta-sign-in.min.js" type="text/javascript"></script>
  <script>
    window.okta = {
      config: {
        clientId: '{{ .Site.Params.okta.clientId }}',
        baseUrl: '{{ .Site.Params.okta.baseUrl }}',
        redirectUri: '{{ .Site.Params.okta.redirectUri }}'
      }
    }
  </script>
  {{ if eq ( replace ( .Permalink | relURL ) "/" "" ) "login" }}
    <script src="/js/okta-login.js"></script>
  {{ else }}
    <script src="/js/okta.js"></script>
  {{ end }}
{{ end }}

Добавьте новый файл в /layouts/partials, назовите его head.html. Как и в предыдущем шаге, копируем HTML из /themes/hugo-tranquilpeak-theme/layouts/partials/head.html в новый head.html. Добавьте HTML, который вы видите ниже перед тегом {{ partial "head_end.html" . }}.


HTML
{{ if .Site.Params.okta }}
  <link href="https://global.oktacdn.com/okta-signin-widget/3.1.0/css/okta-sign-in.min.css" type="text/css" rel="stylesheet"/>
{{ end }}

Создайте новый файл в /layouts/partials с названием footer.html. И добавьте следующий HTML код.


HTML
<footer id="footer" class="main-content-wrap">
    <div id="okta-info" style="display: none; text-align: center;">
        Hi, <span class="firstName"></span>!
    </div>
    <span class="copyrights">
        &copy; {{ now.Format "2006" }} {{ with .Site.Params.footer.copyright }}{{ . | safeHTML }}{{ else }}{{ with .Site.Author.name }}{{ . }}{{ else }}{{ with .Site.Title }}{{ . }}{{ end }}{{ end }}{{ end }}. {{ i18n "footer.all_rights_reserved" }}
    </span>
</footer>

Добавляем Placeholder для страницы входа

В папке /content создайте файл с именем login.html. Добавьте следующую разметку в этот файл.


TOML
---
title: Login
type: login
layout: login
---

Тестируем новую страницу входа

Наконец, мы готовы к тестированию! Запустите сервер Hugo, если еще не сделали этого и перейдите по адресу http://localhost:1313. Если все сделано правильно, слева в меню вы увидите новый пункт Login. Нажмите на него, чтобы увидеть форму входа. Войдите в систему с учетной записью, которую вы регистрировали в Okta. Или попробуйте создать новую учетку с другим email.

Генератор статических сайтов

После входа в свою учетку вы увидите приветственное сообщение на странице /login.

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