понедельник, 16 ноября 2015 г.

Шаблоны и SSI инклюды в статичные файлы на Node.js

Файл templater.js

// Для правильной работы необходимо установить библиотеку "glob": npm install glob
//
// Данный сценарий будет искать все HTML-файлы в корневой папке, в которой он находится, или в папке, которую вы зададите,
// и обрабатывать статичные инклюды обернутые в HTML-комментарии следующим образом:
// <!-- #include "example/foo.html" -->
// Вы также можете добавить переменные, подлежащие замене, используя синтакиси mustache
// (пока поддерживается только самый простой способ замены):
// <!-- #include "inc/header.html" title="Example Title" foo="bar" -->
// значения внутри {{title}} и {{foo}} будут заменены на соотвестующий значения, прописанные в комментарии.
// После обработки шаблонов будут сформированы итоговые файлы, которые будут помещены в указанную вами папку.
//
// Пример кода шаблона
//
// Файл /index.html
// <!DOCTYPE html>
//     <!-- #include "tpl/header.html" title="Example" header="Sample Title" -->
//         <div role="main">
//             content goes here
//         </div>
//     <!-- #include "tpl/footer.html" msg="yeah" -->
//     </body>
// </html>
//
// Файл /tpl/header.html
// <html>
//     <head>
//         <meta charset="utf-8">
//         <title>{{  title  }}</title>
//         <link rel="stylesheet" href="css/main.css">
//     </head>
//     <body>
//         <h1>{{header}}</h1>
//
// Файл /tpl/footer.html
//         <footer>{{msg}}</footer>
//
// Итоговый файл /result/index.html
// <!DOCTYPE html>
// <html>
//     <head>
//         <meta charset="utf-8">
//         <title>Example</title>
//         <link rel="stylesheet" href="css/main.css">
//     </head>
//     <body>
//         <h1>Sample Title</h1>
//         <div role="main">
//             content goes here
//         </div>
//         <footer>yeah</footer>
//     </body>
// </html>

// Конфигурационные настройки
var config = {
      inputDirectoryPath: './'
    , outputDirectoryPath: './result/'
    , fileEncoding: 'utf-8'
};

// Загрузка модулей
var glob = require('glob')
    , fs = require('fs');

// Регулярное выражения для поиска комментария инклюда
// ($1) = file name - путь к файлу и его имя "folder/file.html"
// ($2) = variables - переменные title="Example Title" foo="bar"
//                               |       <!--      #include   "file.html" variable="value" -->   |
var regExpForInclude = /^\s*<!--\s*\#include\s*["']([^"']+)["']\s*(.+)?\s*-->\s*$/gm;

// Регулярное выражения для поиска объявленных переменных
// ($1) = variable name - имя переменной
// ($2) = variable value - значение переменной
//                   | some-variable_name    =      "value" |
var regExpForVariables = /([-_\w]+)\s*=\s*["']([^"']+)["']/g;

// Регулярное выражения для поиска мест, куда должно быть вставлено значение переменной
//                                  | {{       some-variable_name       }} |
var regExpForMustache = /\{\{\s*([-_\w]+)\s*\}\}/g;

// Обработать все файлы в исходной директории и поместить результаты обработки в виде файлов в заданную папку
glob(config.inputDirectoryPath + '*.html', function (error, files) {
    if (error) {throw error;}
    files.forEach(function (filePath) {
        fs.readFile(filePath, config.fileEncoding, function (error, fileData) {
            if (error) {throw error;}
            fileData = fileData.replace(regExpForInclude, function (match, fileName, variablesString) {
                var content = fs.readFileSync(fileName, config.fileEncoding);
                content = replaceVariables(content, parseVariables(variablesString));
                content = renderTemplate(content, parseVariables(variablesString) /* или {value: 1}*/).replace(/(\s+<)/g, '\n$1'); // ДОБАВЛЕНО МНОЙ
                return content;
            });
            fs.writeFile(config.outputDirectoryPath + filePath, fileData, config.fileEncoding, function (error) {
                if (error) {throw error;}
                console.log('complete: '+ filePath);
            });
        });
    });
});

// Функция преобразования строки с набором переменных в объект, состоящий из комбинаций "имя переменной: значение переменной"
function parseVariables (variablesString) {
    var obj = {}
        , match;
    while (match = regExpForVariables.exec(variablesString)) {
        obj[match[1]] = match[2]; // obj[some-variable_name] = variable_value;
    }
    return obj;
}

// Функция для замены имен переменных внутри шаблона на значения этих переменных
function replaceVariables (template, variablesObject, regexp) {
    function replaceFunction (match, variableName) {
        return (variableName in variablesObject) ? variablesObject[variableName] : '';
    }
    return template.replace(regexp || regExpForMustache, replaceFunction);
}

// Функция для выполнения кода внутри шаблона
function renderTemplate (templateString, dataObject) {
    // <% for (var i = 0; i < 3; i++) { %><p><%= i %> <%= someArray[i] %></p><% } %>
    var functionArgument ="dataObject"
        , functionBody = "var code = [];"
                              + "with (dataObject) {"
                              +     "code.push('" + templateString
                                                                                      .replace(/[\r\t\n]/g, "")
                                                                                      .split("<%").join("\t")
                                                                                      .replace(/((^|%>)[^\t]*)'/g, "$1\r")
                                                                                      .replace(/\t=(.*?)%>/g, "',$1,'")
                                                                                      .split("\t").join("');")
                                                                                      .split("%>").join("code.push('")
                                                                                      .split("\r").join("\\'")
                              +     "');"
                              + "}"
                              + "return code.join('');";
    return new Function(functionArgument, functionBody).call(null, dataObject);
}

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

Файл index.html

<!DOCTYPE html>
    <!-- #include "tpl/header.html" title="Example" header="Sample Title" superman="['one', 'two', 'three']" -->
        <div role="main">
            content goes here
        </div>
    <!-- #include "tpl/footer.html" msg="yeah" -->
    </body>
</html>

Файл tpl/header.html

<html>
    <head>
        <meta charset="utf-8">
        <title>{{  title  }}</title>
        <link rel="stylesheet" href="css/main.css">
    </head>
    <body>
        <h1>{{header}}</h1>
        <h2><%= header %></h2>
        <% for (var i = 0; i < 3; i++) { %><p><%= i %> <%= superman[i] %></p><% } %>

Файл tpl.footer.html

        <footer>{{msg}}</footer>

Итоговый файл result/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Example</title>
        <link rel="stylesheet" href="css/main.css">
    </head>
    <body>
        <h1>Sample Title</h1>
        <h2>Sample Title</h2>
        <p>0Example</p><p>1Example</p><p>2Example</p><p>3Example</p><p>4Example</p><p>5Example</p><p>6Example</p><p>7Example</p><p>8Example</p><p>9Example</p>
        <div role="main">
            content goes here
        </div>
        <footer>yeah</footer>
    </body>
</html>

Комментариев нет:

Отправить комментарий