Пошаговое руководство по созданию нового веб-сайта

Свой первый блог я завел в 2010 году. В то время использование WordPress было очевидным. Это был хороший выбор, потому что он прост в использовании и имеет тысячи тем и плагинов. WordPress — отличный движок, но у него есть свои недостатки. Он требователен к ресурсам, и существует множество уязвимостей, связанных с WordPress. Одно из возможных решений — поставить весь сайт за CloudFront или любой другой CDN. CDN хорошо масштабируется и обеспечивает безопасность сайта, но в моем случае блог был только архивом, и я не хотел, чтобы мой сервер работал. Вот почему я выбрал GitHub Pages.

GitHub Pages — это «хостинг для бедных». Это бесплатно, и вы можете указать на него свой домен. Недостатком является то, что он может размещать только статические сайты, поэтому мне пришлось создать статический сайт из моего блога WordPress.

К счастью, WordPress может экспортировать (Инструменты/Экспорт в админке) весь контент сайта в файл XML, поэтому мне нужно было только разработать простой генератор статического сайта, который генерирует контент из экспорта. Я выбираю TypeScript для разработки, потому что я знаком с ним, и для этого есть много крутых и простых в использовании JS-библиотек.

Во-первых, мне нужно было найти простой и удобный в использовании анализатор XML. После непродолжительного поиска в Google я нашел fast-xml-parser. Этот синтаксический анализатор создает дерево объектов JS из XML, которое можно легко обрабатывать.

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

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

Чтобы создать шаблоны ejs, я скачал HTML-файлы, сгенерированные WordPress, и добавил к ним теги ejs. Я создал два шаблона. Один для сообщений, а другой для оглавления.

Структура экспорта XML очень проста. Это RSS-канал, созданный из элементов. У каждого элемента есть тип (пост, вложение и т. д.), но мне нужны были только посты и вложения. Код выглядит следующим образом:

(async function () {
    const parser = new XMLParser();
    let wp_export = parser.parse(readFileSync('wordpress-export.xml'));
    let posts = wp_export.rss.channel.item;

    let pinned_posts: any[] = []
    let post_list: any[] = []
    for (const post of posts) {

        // download attachments
        if (post['wp:post_type'] == 'attachment') {
            const url = post['wp:attachment_url'];
            for (const post_meta of post['wp:postmeta']) {
                if (post_meta['wp:meta_key'] == '_wp_attached_file') {
                    const file_path = post_meta['wp:meta_value']
                    const full_path = `wp-content/uploads/${file_path}`
                    mkdirSync(dirname(full_path), { recursive: true });
                    const file = createWriteStream(full_path);
                    http.get(url, (resp) => {
                        resp.pipe(file);
                        file.on("finish", () => {
                            file.close();
                        });
                    })
                }
            }
        }

        // generate post page if it's published
        if (post['wp:post_type'] == 'post' && post['pubDate']) {
            post['content:encoded'] = post['content:encoded'].split(/\r?\n|\r|\n/g).reduce((accumulator: string, currentValue: string) => accumulator + `<p>${currentValue}</p>`)

            const content = await ejs.renderFile("template.ejs", { post: post }, { async: true })
            mkdirSync(`archives/${post['wp:post_id']}`, { recursive: true });
            writeFileSync(`archives/${post['wp:post_id']}/index.html`, content)

            const element = {
                id: post['wp:post_id'],
                title: post.title,
                summary: truncate(post['content:encoded'].replace(/<[^>]*>?/gm, ''), 300)
            }

            if (pinned_post_ids.includes(post['wp:post_id'])) {
                pinned_posts.push(element)
            } else {
                post_list.push(element)
            }
        }
    }

    // generate toc
    pinned_posts.sort((a, b) => { return b.id - a.id })
    let merged_posts = pinned_posts.concat(post_list.sort((a, b) => { return b.id - a.id }))

    // readme.md
    let readme = `
# my-wordpress-blog
This is a backup of my Wordpress blog. (http://lf.estontorise.hu/)


`
    for (const post of merged_posts)
        readme += `[${post.title}](https://thebojda.github.io/my-wordpress-blog/archives/${post.id})\n\n`
    writeFileSync('README.md', readme)

    // index.html
    const content = await ejs.renderFile("template_toc.ejs", { posts: merged_posts }, { async: true })
    writeFileSync(`index.html`, content)
})()

Код перебирает элементы и проверяет их тип. Если тип — «вложение», он считывает значение метаданных _wp_attached_file, содержащих URL-адрес вложения, и загружает его с помощью модуля HTTP.

Если тип элемента «публикация» и он опубликован (pubDate не пусто), сгенерируйте страницу. Содержимое страницы находится в теге content:encoded в формате HTML с небольшим искажением. Каждая строка представляет собой отдельный абзац, поэтому вы должны преобразовать разрывы строк в абзацы. Это делает следующая строка кода:

post['content:encoded'] = 
  post['content:encoded']
  .split(/\r?\n|\r|\n/g)
  .reduce((accumulator: string, currentValue: string) => 
    accumulator + `<p>${currentValue}</p>`)

Десять лет назад, когда я начинал свой блог, я ничего не знал о SEO, поэтому ссылки на посты выглядели так: …/archives/123, где последняя цифра — это идентификатор поста. В лучшем случае URL поста будет более выразительным и содержит ключевые слова, но в обоих случаях вы столкнетесь с проблемой, что GitHub Pages не поддерживает HTML-страницы без расширения «.html».

Если вы загрузите файл HTML без расширения, браузер загрузит его, а не покажет. По этой причине вы должны преобразовать эти URL-адреса в каталоги, содержащие файл index.html. Например, /archives/123 нужно преобразовать в /archives/123/index.html. С этой новой структурой все работает как часы.

Последний блок кода генерирует ToC HTML и файл readme.md. Второй может быть очень полезен, если кто-то найдет вашу страницу на GitHub, потому что он может легко перейти к вашим сообщениям.

Когда создание статической страницы было завершено, я загрузил свой сайт на GitHub и включил страницы GitHub в настройках.

После того, как я установил запись CNAME в админке моего провайдера DNS, GitHub проверил ее. Установив флажок Enforce HTTPS, GitHub сгенерировал HTTPS-сертификат, и через некоторое время сайт был готов. Здесь вы можете посмотреть результат: https://lf.estontorise.hu. Блог на венгерском языке, поэтому вы, вероятно, не поймете содержание, но вы можете видеть, что все работает хорошо, а URL-адреса совпадают с URL-адресами WordPress.

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

Это был мой короткий путь от WordPress до GitHub Pages. Я надеюсь, что эта короткая статья поможет вам перенести свой блог, если вы хотите.

(Вы можете найти все в моем репозитории GitHub.)