+++ categories = ["Без категории"] date = "2018-04-29" excerpt = "Описание всей работы обёртки над Hugo, а так же все команды для работы. Где хоститься блог и как получить такой же." image = "about-the-blog-2/cover.jpg" tags = ["hugo","JavaScript", "node.js", "static site", "github pages"] thumbnail = "about-the-blog-2/thumbnail.jpg" title = "Как работает этот блог - Часть 2" +++
В прошлой части, я закончил на том, что получил набор HTML файлов. Эти файлы просто лежат у меня на диске и никто их не видит. Теперь надо решить задачу с показом нашёго сайта миру.
И у нас нет с этим проблем, так как это просто набор файлов. Есть множество хостингов, которые предоставляют эту функцию, причем дешевле, чем хостинг с чем-то вроде PHP. Но есть вариант ещё и без использования хостингов. Это же просто набор файлов! Как удачно, что некоторые сервисы предоставляют показывать статику бесплатно. Один из таких - это GitHub Pages. Есть и другие, но надо поискать 😃. Такую функцию предоставлял ещё Dropbox. Ты просто закибывал файлы в папку, у себя на компе и после они сразу появлялось на твоем сайте.
Но я использую GitHub. Из-за того, что я и так зарегистрирован на нём и он относительно надёжен. Что надо сделать для получения своего заветного сайта на их платформе?
- Зарегистрироваться
- Создать репозиторий с названием USERNAME.github.io (Вместо USERNAME - свой ник)
- Все!
После этого, сайт будет доступен по этому адресу, у меня это grishy.github.io. Так же можно купить свой домен второго уровня. А-ля grishy.ru. Насчет его подключения инструкцию можно найти в настройках репозитория USERNAME.github.io.
Так же надо после добавить файл CNAME с именем внутри, в корень репозитория. Это если вы подключили свой домен.
Так вот, насчёт постобработки. Мне не хватило встроенных функций и из-за этого я добавил ещё один уровень, задачи которого:
- Запуск локальной копии, для просмотра того, что написал. Он не до конца функционален, из-за того что показывается без нижних пунктов.
- Генерация статики, со всем постобработками.
- Автозагрузка на github изменений.
Это все оформлено в один index.js
файл. Для вызова мне в корне надо написать код ниже. На самом деле script
это папка и в ней храниться index.js
файл.
Вот список всех команд:
> blog
$ node script help
$ node script
$ node script gen
$ node script deploy
Сделано так, из-за того что я хотел выделить все, что связано с генерацией в одну часть. В этой папке у меня хранятся бинарники Hugo, модули для Node.js и т.д. Теперь более подробно про все команды.
Это самое простое. Нужно вызвать Hugo из провального бинарника. Для Windows - это hugo.exe
, для Linux - hugo
, Mac я не рассматривал. После запуска у меня выводится ссылка в консоль, где собственно уже можно все посмотреть в браузере.
Решить проблему с выводом статьи после всех обработок, а не до при такой архитектуре можно сделав саму свой локальный сервер. Можно заранее сгенерировать все, провести обработки и после запустить. Сейчас, если это надо, то можно использовать Python. Запустив маленький локальный сервер на 8000 порту. Но это как-то мне не мешает пока. Тем более, что есть вариант номер два, о котором ниже.
python -m http.server 8000
Мне нужно было сделать подсветку кода, вставку математических формул, нормально отображение дат на русском языке. Все, большего не надо. Примерный план действий:
- Получить файл
- Распарсить
- Найти элементы, которые надо изменить
- Изменить их!
- Записать назад изменения в файл
Очень напоминает работу Gulp из прошлой статьи, где он собирал мне стили для сайта. Но я решил сделать руками, тем более что тут работы не много и мне не хотелось писать плагины для Gulp.
Для начала возьмет файлы, которые надо обработать. В нашём случает это все .html
файлы. Воспользуемся модулем glob, для взятия файлов по маске.
glob(CONFIG['public'] + '**/**/*.html', function (err, files) {
// ...
})
Конструкция вида **/**/*.html
говорит, что надо взять файлы с любым именем и любым уровнем вложенности. Дальше нам в функцию будет передана ошибка и список подходящих файлов.
Для каждого файла мы запустим обработку. Тут видно, что файл я начал писать этот скрипт, пока ещё новый стандарт не поддерживался в node.js. Из-за того, что я тут использую "сокращенный синтаксис" записи функций. Хотя там есть ещё отличия, от предыдущего способа, но чаще всего они не важны.
files.forEach((item) => {
// ...
})
Начинается все с того, что нам надо загрузить файл и распарсить его. Не работать же нас с сырым текстом и регулярками. Хотя идея с регулярками не так и плоха. Мы читаем файл, и записываем результат парсинга в переменную $
.
var html = fs.readFileSync(item, 'utf8')
var $ = cheerio.load(html, {
decodeEntities: false
})
Так как изменения файла будут только внутри маленьких кусочков, то их можно запустить параллельно. Так мы увеличим скорость обработки одной записи. Воспользуется пакетом async.
async.parallel([
(callback) => {
// 1
},
(callback) => {
// 2
},
// ...
], function () {
// 3
});
Он запустит параллельно функции 1
и 2
. После того, как они все закончили работу, вызовется функция номер 3
. Кол-во функций типа 1 и 2 может быть не ограничено, но у меня их 3. После того, как функция типа 1 и т.д. закончили свою заботу, они должны вызвать callback
. Так async поймет, что эта функция закончила. Кажется не самым удобным и почему он сам не поймёт? Тут немного надо углубиться в JavaScript, делать я это конечно же не буду. Гуглите Event Loop. Просто в JS все работает асинхронно, не так как в Python, C++ и других.
Как выглядит у меня до обработки:
Надо найти элемент, в котором записано время и преобразовать.
Стандартно у меня время записано в формате Unix. Определяется как количество секунд, прошедших с полуночи 1 января 1970 года. Как я и сказал, вызываем callback()
.
$('.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. Мы находим элементы с кодом, отдаем в hl.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
и вытащить из него сам язык. После преобразовать и вставить на прошлое место. Тут ещё код преобразуется в эскейп последовательности, чтобы браузер не убрал из кода символы, которые ему не нравятся. Они могут совпадать с заранее заданными.
escapeHTML("<div>Blah blah blah</div>");
// => "<div>Blah blah blah</div>"
Как видите, все угловые скобки были заменены. Визуально это никак в браузере не измениться, зато теперь я спокоен, что браузер не начнёт их как то преобразовывать.
Найти все формулы в тексте, и заменить их на сгенерированные SVG картинки. Знаете, я сейчас не посмотрел на код, и мне не понятно, почему я в таком стиле написал. Зачем я использовал async.every
?
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 >} }
То вы увидите: {{< tex >}} \sigma = \sqrt{ \frac{1}{N} \sum_{i=1}^N (x_i -\mu)^2} {{< /tex >}}
В этом примере, как раз видно, когда вызов callback
помогает. Я не знаю когда закончится обработка, но когда она закончиться, async сразу об этом узнает (последние строки).
Вот и все, после этого я сохраняю результат в HTML, перезаписывая прошлый файл.
После того, как все сгенерировалось, оно лежит в папке под названием public
, вместе с блогом. Теперь надо закоммитить эту папку. Я копирую blog/public
в grishy.github.io
. У меня разделены данные доя блога и сам блог. Но сохраняются они одновременно. Т.е. когда я пишу node script deploy
, то все изменения в обеих папках запоминаются и отправляются. Перед этим я ещё генерирую название для коммита, которое начинается со смайлика и дальше дата.
Для загрузки я использую готовый node.js модуль - simple-git. После этого, в течении 1 минуты все изменения будут видны в блоге.
Как-то много вышло, писал сразу, не особо думаю, что будет дальше 😄. Наверное будет 3 часть, где будет рассмотрена производительность, что улучшить, какие баги и подводные камни.