# Как работает этот блог - Часть 2

> Как устроен этот блог изнутри: Часть 2. Деплой. Как бесплатно хостить сайт на GitHub Pages, прикрутить свой домен и автоматизировать сборку на Node.js, чтобы не делать всё руками.

**Published:** 2018-04-29

**Canonical:** <https://grishy.dev/ru/posts/about-the-blog-2/>

**Translations:** [Русский](https://grishy.dev/ru/posts/about-the-blog-2.md)

![Как работает этот блог - Часть 2](https://grishy.dev/_astro/cover.Dlopj3id.avif)

---

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

И у нас нет с этим проблем, так как это просто набор файлов. Есть множество хостингов, которые предоставляют эту функцию, причем дешевле, чем хостинг с чем-то вроде PHP. Но есть вариант ещё и без использования хостингов. Это же просто набор файлов! Как удачно, что некоторые сервисы предоставляют показывать статику бесплатно. Один из таких - это [GitHub Pages](https://pages.github.com/). Есть и другие, но надо поискать 😃. Такую функцию предоставлял ещё Dropbox. Ты просто закидывал файлы в папку, у себя на компе и после они сразу появлялось на твоем сайте.

Но я использую *GitHub*. Из-за того, что я и так зарегистрирован на нём и он относительно надёжен. Что надо сделать для получения своего заветного сайта на их платформе?

1. Зарегистрироваться
2. Создать репозиторий с названием **USERNAME.github.io** (Вместо USERNAME - свой ник)
3. Все!

После этого сайт будет доступен по этому адресу, у меня это [grishy.github.io](http://grishy.github.io). Также можно купить свой [домен второго уровня](https://ru.wikipedia.org/wiki/%D0%94%D0%BE%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5_%D0%B8%D0%BC%D1%8F_%D0%B2%D1%82%D0%BE%D1%80%D0%BE%D0%B3%D0%BE_%D1%83%D1%80%D0%BE%D0%B2%D0%BD%D1%8F). А-ля **grishy.ru**. Насчет его подключения инструкцию можно найти в настройках репозитория *USERNAME.github.io*.

![Настройка домена в GitHub Pages](https://grishy.dev/_astro/githubDomain.Dv_ePyJm.avif)

Также надо после добавить файл CNAME с именем внутри в корень репозитория. Это если вы подключили свой домен.

Так вот, насчёт постобработки. Мне не хватило встроенных функций и из-за этого я добавил ещё один уровень, задачи которого:

1. Запуск локальной копии, для просмотра того, что написал. Он не до конца функционален, из-за того что показывается без нижних пунктов.
2. Генерация статики, со всеми постобработками.
3. Автозагрузка на github изменений.

Это все оформлено в один `index.js` файл. Для вызова мне в корне надо написать код ниже. На самом деле `script` это папка и в ней хранится `index.js` файл.

Вот список всех команд:

```bash
> blog
$ node script help
$ node script
$ node script gen
$ node script deploy
```

Сделано так, из-за того что я хотел выделить все, что связано с генерацией в одну часть. В этой папке у меня хранятся бинарники *Hugo*, модули для Node.js и т.д.
Теперь более подробно про все команды.

## Запуск локальной копии (`$ node script`)

Это самое простое. Нужно вызвать Hugo из локального бинарника. Для Windows - это `hugo.exe`, для Linux - `hugo`, Mac я не рассматривал. После запуска у меня выводится ссылка в консоль, где уже можно всё посмотреть в браузере.

![Вывод Hugo сервера в терминале](https://grishy.dev/_astro/scriptServer.LFzeJ4kt.avif)

Решить проблему с выводом статьи после всех обработок, а не до них, при такой
архитектуре можно, если сделать свой локальный сервер. Можно заранее
сгенерировать всё, провести обработки и после запустить. Сейчас, если это надо,
можно использовать Python и маленький локальный сервер на 8000 порту. Но пока
мне это не мешает. Тем более есть вариант номер два, о котором ниже.

```bash
python -m http.server 8000
```

## Генерация статики (`$ node script gen`)

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

1. Получить файл
2. Распарсить
3. Найти элементы, которые надо изменить
4. **Изменить их!**
5. Записать назад изменения в файл

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

### Получение всех файлов

Для начала возьмем файлы, которые надо обработать. В нашем случае это все `.html` файлы. Воспользуемся модулем [glob](https://github.com/isaacs/node-glob) для взятия файлов по маске.

```js
glob(CONFIG["public"] + "**/**/*.html", function (err, files) {
  // ...
});
```

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

Для каждого файла мы запустим обработку. Тут видно, что я начал писать этот скрипт, пока ещё новый стандарт не поддерживался в node.js. Из-за того, что я тут использую "сокращенный синтаксис" записи функций. Хотя там есть ещё отличия, от предыдущего способа, но чаще всего они не важны.

```js
files.forEach((item) => {
  // ...
});
```

### Обработка файла

Начинается всё с того, что нам надо загрузить файл и распарсить его. Не работать же нам с сырым текстом и регулярками. Хотя идея с регулярками не так и плоха. Мы читаем файл и записываем результат парсинга в переменную `$`.

```js
var html = fs.readFileSync(item, "utf8");

var $ = cheerio.load(html, {
  decodeEntities: false,
});
```

Так как изменения файла будут только внутри маленьких кусочков, то их можно запустить параллельно. Так мы увеличим скорость обработки одной записи. Воспользуется пакетом [async](https://github.com/caolan/async).

```js
async.parallel(
  [
    (callback) => {
      // 1
    },
    (callback) => {
      // 2
    },
    // ...
  ],
  function () {
    // 3
  },
);
```

Он запустит параллельно функции `1` и `2`. После того, как они закончат работу,
вызовется функция номер `3`. Кол-во функций типа **1** и **2** может быть не
ограничено, но у меня их *3*. После того, как функция типа **1** и т.д. закончит
свою задачу, она должна вызвать `callback`. Так **async** поймёт, что эта
функция закончила. Кажется не самым удобным, и почему он сам не поймёт? Тут
немного надо углубиться в JavaScript, делать я это конечно же не буду. Гуглите
**Event Loop**. Просто в JS всё работает асинхронно, не так как в Python, C++ и
других.

![Схема Event Loop в JavaScript](https://grishy.dev/_astro/eventLoop.B7SybGTQ.avif)

#### Время

Как выглядит у меня до обработки:

![Отображение времени до форматирования](https://grishy.dev/_astro/time.ZKVKbMc5.avif)

Надо найти элемент, в котором записано время и преобразовать.
Стандартно у меня время записано в формате **Unix**. Определяется как количество секунд, прошедших с полуночи 1 января 1970 года. Как я и сказал, вызываем `callback()`.

```js
$(".Unix-time").each((i, el) => {
  var block = $(el);
  var timeF = moment.unix(block.text());
  block.text(timeF.format("DD MMMM YYYY").toUpperCase());
});
callback();
```

#### Подсветка кода

Для подсветки кода, используется [highlight.js](https://highlightjs.org/). Мы находим элементы с кодом, отдаем в hl.js и что он вернул, вставляем на тоже место.

```js
$("pre code").each((i, el) => {
  var block = $(el);
  var cls = block.attr("class");
  var code = block.text();

  if (cls == undefined) {
    block.html(hljs.highlightAuto(code).value);
  } else if (cls == "language-nohighlight") {
    // 'Есть сказали ничего не делать, не делаем :) '
  } else {
    // [0] - вся строка (по умолчанию в регулярках)
    // [1] - язык
    var lang = /(?:language-)(.*)/g.exec(cls)[1];
    if (lang != null) {
      var hlHTML = hljs.highlight(lang, code).value;
      block.attr("class", lang + " hljs");
      // <<span class="hljs-name">path</span>
      // Одна из скобок удаляется.
      let escHTML = us_s.escapeHTML(hlHTML);
      block.html(escHTML);
    }
  }
});
```

Думаю, объяснять почти не надо. Если я не написал стандартно, какой язык
использовать, то надо хоть как-то подсветить. Если сказал, что ничего делать не
надо, пропускаем. Иначе берём из класса элемента атрибут `class` и вытаскиваем
из него сам язык. После преобразуем и вставляем на прежнее место. Тут ещё код
преобразуется в escape-последовательности, чтобы браузер не убрал из кода
символы, которые ему не нравятся. Они могут совпадать с заранее заданными.

```javascript
escapeHTML("<div>Blah blah blah</div>");
// => "&lt;div&gt;Blah blah blah&lt;/div&gt;"
```

Как видите, все угловые скобки были заменены. Визуально это никак в браузере не
изменится, зато теперь я спокоен, что браузер не начнёт их как-то
преобразовывать.

#### Математические формулы - MathJax

Найти все формулы в тексте, и заменить их на сгенерированные SVG картинки. Знаете, я сейчас не посмотрел на код, и мне не понятно, почему я в таком стиле написал. Зачем я использовал `async.every`?

```js
async.every(
  $("span.mathjax"),
  function (el, cb) {
    let block = $(el);
    let math = block.html();

    mjAPI.typeset(
      {
        math: math,
        format: "TeX",
        svg: true,
      },
      function (data) {
        block.html(data.svg);
        cb(null, true);
      },
    );
  },
  () => {
    callback();
  },
);
```

Теперь, если я напишу в тексте:

```
{ {< tex >} }
    \sigma = \sqrt{ \frac{1}{N} \sum_{i=1}^N (x_i -\mu)^2}
{ {< /tex >} }
```

То вы увидите:

$\sigma = \sqrt{ \frac{1}{N} \sum_{i=1}^N (x_i -\mu)^2}$

В этом примере как раз видно, когда вызов `callback` помогает. Я не знаю, когда закончится обработка, но когда она закончится, async сразу об этом узнает (последние строки).

Вот и все, после этого я сохраняю результат в HTML, перезаписывая прошлый файл.

## Загрузка на GitHub (`$ node script deploy`)

После того, как все сгенерировалось, оно лежит в папке под названием `public`, вместе с блогом. Теперь надо закоммитить эту папку. Я копирую `blog/public` в `grishy.github.io`. У меня разделены данные для блога и сам блог. Но сохраняются они одновременно. Т.е. когда я пишу `node script deploy`, то все изменения в обеих папках запоминаются и отправляются. Перед этим я ещё генерирую название для коммита, которое начинается со смайлика и дальше дата.

![Git коммиты с эмодзи и датой](https://grishy.dev/_astro/commits.D7r4Neu2.avif)

Для загрузки я использую готовый *node.js* модуль - [simple-git](https://www.npmjs.com/package/simple-git). После этого в течение 1 минуты все изменения будут видны в блоге.

Как-то много вышло, писал сразу, не особо думаю, что будет дальше 😄. Наверное будет 3 часть, где будет рассмотрена производительность, что улучшить, какие баги и подводные камни.

---

<https://github.com/grishy/blog/blob/hugo/content/post/about-the-blog-2.md>
