В прошлой части, я закончил на том, что получил набор 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 и т.д. Теперь более подробно про все команды.
Запуск локальной копии ($ node script
)
Это самое простое. Нужно вызвать Hugo из провального бинарника. Для Windows - это hugo.exe
, для Linux - hugo
, Mac я не рассматривал. После запуска у меня выводится ссылка в консоль, где собственно уже можно все посмотреть в браузере.
Решить проблему с выводом статьи после всех обработок, а не до при такой архитектуре можно сделав саму свой локальный сервер. Можно заранее сгенерировать все, провести обработки и после запустить. Сейчас, если это надо, то можно использовать Python. Запустив маленький локальный сервер на 8000 порту. Но это как-то мне не мешает пока. Тем более, что есть вариант номер два, о котором ниже.
python -m http.server 8000
Генерация статики ($ node script gen
)
Мне нужно было сделать подсветку кода, вставку математических формул, нормально отображение дат на русском языке. Все, большего не надо. Примерный план действий:
- Получить файл
- Распарсить
- Найти элементы, которые надо изменить
- Изменить их!
- Записать назад изменения в файл
Очень напоминает работу 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>"
Как видите, все угловые скобки были заменены. Визуально это никак в браузере не измениться, зато теперь я спокоен, что браузер не начнёт их как то преобразовывать.
Математические формулы - MathJax
Найти все формулы в тексте, и заменить их на сгенерированные 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, перезаписывая прошлый файл.
Загрузка на GitHub ($ node script deploy
)
После того, как все сгенерировалось, оно лежит в папке под названием public
, вместе с блогом. Теперь надо закоммитить эту папку. Я копирую blog/public
в grishy.github.io
. У меня разделены данные доя блога и сам блог. Но сохраняются они одновременно. Т.е. когда я пишу node script deploy
, то все изменения в обеих папках запоминаются и отправляются. Перед этим я ещё генерирую название для коммита, которое начинается со смайлика и дальше дата.
Для загрузки я использую готовый node.js модуль - simple-git. После этого, в течении 1 минуты все изменения будут видны в блоге.
Как-то много вышло, писал сразу, не особо думаю, что будет дальше 😄. Наверное будет 3 часть, где будет рассмотрена производительность, что улучшить, какие баги и подводные камни.
https://github.com/grishy/blog/blob/hugo/content/post/about-the-blog-2.md