Первое, что нам нужно для создания статического сайта — это шаблон. Для этого урока мы собираемся сделать его очень простым, всего два файла, один для страниц и один для рендеринга меню/навигационной панели. Файлы для шаблона будут представлять собой простой плоский html с небольшим количеством синтаксиса Mustache, помещенный в папку шаблона проекта. Мы будем использовать Milligram CSS, чтобы придать нашей странице немного стиля.

Ниже код для файла index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{title}}</title>
    <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
    <link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">
    <link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
</head>
<body>
    <div class='navbar'>
        {{> menu }}
    </div>
    
    <div class="row">
        <div class="column column-80">{{{content}}}</div>
    </div>   
</body>

<style>
.navbar {
  margin-bottom: 10px;
}

.navbar ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #606c76;
}

.navbar li {
    float: left;
    margin-bottom: 0;
}

.navbar li a {
    display: block;
    color: white;
    text-align: center;
    padding: 14px 16px;
    text-decoration: none;
}

.navbar li a:hover {
    background-color: #ab5dda;
}
</style>
</html>

А затем частичный _menu.html, который будет отображать заголовки страниц на панели навигации.

<ul>
    {{#menu}}
        <li><a href="{{page_link}}">{{title}}</a></li>
    {{/menu}}
</ul>

Оба этих файла говорят сами за себя, но Mustache действительно мощный, поэтому мы предлагаем вам прочитать документацию, доступную на официальном сайте.

Генератор

Пришло время создать приложение NodeJs, которое, начиная с файлов, помещенных в папку (исходный код в нашем примере), будет собирать весь сайт. Ниже вы можете найти весь необходимый код, чтобы это произошло.

Поместите приведенный ниже код в файл index.js.

const watch = require('node-watch');
const showdown  = require('showdown')
const converter = new showdown.Converter()
const fse = require('fs-extra')
const fs = require('fs')
const must = require ('mustache');
const glob = require('glob')
const path  =require('path')
const liveServer = require("live-server");


const source_dir="./source"
const output_dir="./output"
const template_dir="./template" 

var page_menu=[]

var params = {
  root: "output", 
  open: true, 
  file: "index.html", 
};

var generateMenu = ()=>{
    page_menu=[]
    glob(source_dir+"/pages/**.md",  (err, files)=> {
        files.forEach((file)=>{

            const file_name = path.basename(file).replace('.md','');
            const out_file_name = file_name+'.html'
            
            page_menu.push({
                    title:file_name,
                    page_link:'/'+out_file_name
                });
        });
    });
}


var generatePage = (file)=>{
    
    const template = fse.readFileSync('template/index.html','UTF8');
    const menu = fse.readFileSync('template/_menu.html','UTF8');

    fse.readFile(file,'UTF8',(err,data)=>{            
        
        const file_name = path.basename(file).replace('.md','');
        var out_file_name = file_name + '.html'
        var html = converter.makeHtml(data);
        
        fs.stat(file,(err,stat)=>{

            var page_data = {
                title:file_name,
                publish_date: stat.atime,
                content:html,
                menu:page_menu
            }

            var rendered = must.render(template,page_data,{ menu:menu })

            fse.writeFile('output/'+out_file_name,rendered);            
            console.log("Generated %s", out_file_name)

        })
    });
}

const refresh = () =>{
    fse.emptyDir(output_dir, err => {
        if (err) return console.error(err)
            glob(source_dir+"/pages/**.md", (er, files)=> {
                files.forEach((file)=>{
                    generatePage(file)
                });
            })    
            console.log('success!')
      });
}

generateMenu()
refresh()

watch(source_dir, { recursive: true }, function(evt, name) {
    generateMenu()
    refresh()
});

liveServer.start(params);

Как вы можете видеть, каждый раз, когда файл изменяется в исходном каталоге, вызываются функции generateMenu и Refresh. Функция generateMenu перечисляет все файлы в папке и для каждого из них вставляет объект am в массив page_menu. Функция обновления делает почти то же самое, но для каждого файла уценки в папке он будет отображаться в статический html-файл с использованием функции рендеринга усов. Массив page_menu передается в качестве параметра этой функции, поэтому механизм шаблонов может отображать меню (панель навигации).

Вывод

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

Полный код этого руководства можно найти на нашей странице Github.

Если вам понравилась эта работа, пожалуйста, не забудьте поделиться ею :)