Статьи

Построить CMS: nodePress

Вы успешно создали плоскую файловую систему Content Management System (CMS) с помощью Go . Следующий шаг — взять тот же идеал и создать веб-сервер с использованием Node.js. Я покажу вам, как загрузить библиотеки, создать сервер и запустить сервер.

В этой CMS будет использоваться структура данных сайта, изложенная в первом руководстве « Создание CMS: структура и стилизация» . Поэтому загрузите и установите эту базовую структуру в новом каталоге.

Самый простой способ установить Node.js на Mac — это Homebrew . Если вы еще не установили Homebrew, учебник Homebrew Demystified: Ultimate Менеджер пакетов OS X покажет вам, как это сделать.

Чтобы установить Node.js с Homebrew, введите эту инструкцию в терминал:

1
brew install node

Когда вы закончите, у вас будут полностью установлены команды node и npm на вашем Mac. Для всех других платформ следуйте инструкциям на веб-сайте Node.js.

Будьте осторожны: многие менеджеры пакетов в настоящее время устанавливают Node.js версии 0.10. В этом руководстве предполагается, что у вас версия 5.3 или новее. Вы можете проверить свою версию, набрав:

1
node —version

Команда node запускает интерпретатор JavaScript. Команда npm — это менеджер пакетов для Node.js для установки новых библиотек, создания новых проектов и запуска сценариев для проекта. В Envato Tuts + есть много отличных учебных пособий и курсов по Node.js и NPM.

Чтобы установить библиотеки для веб-сервера, вы должны выполнить эти команды в программе Terminal.app или iTerm.app:

1
2
3
4
5
6
npm install express —save
npm install handlebars —save
npm install moment —save
npm install marked —save
npm install jade —save
npm install morgan —save

Express — это платформа для разработки веб-приложений. Это похоже на библиотеку goWeb в Go. Handlebars — это шаблонизатор для создания страниц. Moment — это библиотека для работы с датами. Marked — отличный конвертер Markdown в HTML в JavaScript. Jade — это сокращенный язык HTML для простого создания HTML. Morgan — это промежуточная библиотека для Express, которая генерирует стандартные файлы журналов Apache .

Альтернативный способ установки библиотек — загрузить исходные файлы для этого урока. После загрузки и разархивирования введите это в основной каталог:

1
npm —install

Это установит все необходимое для создания этого проекта.

Теперь вы можете приступить к созданию сервера. В верхнем каталоге проекта создайте файл с именем nodePress.js, откройте его в выбранном вами редакторе и начните добавлять следующий код. Я собираюсь объяснить код, как он помещен в файл.

01
02
03
04
05
06
07
08
09
10
11
12
13
//
// Load the libraries used.
//
var fs = require(‘fs’);
var path = require(«path»);
var child_process = require(‘child_process’);
var process = require(‘process’);
var express = require(‘express’);
var morgan = require(‘morgan’);
var Handlebars = require(«handlebars»);
var moment = require(«moment»);
var marked = require(‘marked’);
var jade = require(‘jade’);

Код сервера начинается с инициализации всех библиотек, используемых для создания сервера. Библиотеки, у которых нет комментария с веб-адресом, являются внутренними библиотеками Node.js.

1
2
3
4
5
6
7
8
9
//
// Setup Global Variables.
//
var parts = JSON.parse(fs.readFileSync(‘./server.json’, ‘utf8’));
var styleDir = process.cwd() + ‘/themes/styling/’ + parts[‘CurrentStyling’];
var layoutDir = process.cwd() + ‘/themes/layouts/’ + parts[‘CurrentLayout’];
var siteCSS = null;
var siteScripts = null;
var mainPage = null;

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

Переменная parts — это хеш-массив, содержащий все части веб-страницы. Каждая страница ссылается на содержимое этой переменной. Он начинается с содержимого файла server.json, находящегося в верхней части каталога сервера.

Затем я использую информацию из файла server.json для создания полных путей к каталогам styles и layouts используемых для этого сайта.

Три переменные затем устанавливаются в нулевые значения: siteCSS , siteScripts и mainPage . Эти глобальные переменные будут содержать все CSS, JavaScripts и содержимое главной страницы индекса. Эти три элемента являются наиболее востребованными элементами на любом веб-сервере. Поэтому хранение их в памяти экономит время. Если переменная Cache в файле server.json имеет значение false, эти элементы перечитываются при каждом запросе.

01
02
03
04
05
06
07
08
09
10
marked.setOptions({
  renderer: new marked.Renderer(),
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: true,
  smartypants: false
});

Этот блок кода предназначен для настройки библиотеки Marked для генерации HTML из Markdown. В основном я включаю поддержку таблиц и смарт-списков.

01
02
03
04
05
06
07
08
09
10
11
12
13
parts[«layout»] = fs.readFileSync(layoutDir + ‘/template.html’, ‘utf8’);
parts[«404»] = fs.readFileSync(styleDir + ‘/404.html’, ‘utf8’);
parts[«footer»] = fs.readFileSync(styleDir + ‘/footer.html’, ‘utf8’);
parts[«header»] = fs.readFileSync(styleDir + ‘/header.html’, ‘utf8’);
parts[«sidebar»] = fs.readFileSync(styleDir + ‘/sidebar.html’, ‘utf8’);
 
//
// Read in the page parts.
//
var partFiles = fs.readdirSync(parts[‘Sitebase’] + «parts/»);
partFiles.forEach(function(ele, index, array) {
   parts[path.basename(ele, path.extname(ele))] = figurePage(parts[‘Sitebase’] + «parts/» + path.basename(ele, path.extname(ele)));
});

Переменная parts дополнительно загружается с деталями из каталогов styles и layout . Каждый файл в каталоге parts внутри каталога site также загружается в глобальную переменную parts . Имя файла без расширения — это имя, используемое для хранения содержимого файла. Эти имена раскрываются в макросе Handlebars.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//
// Setup Handlebar’s Helpers.
//
 
//
// HandleBars Helper: save
//
// Description: This helper expects a
// «<name>» «<value>» where the name
// is saved with the value for future
// expansions.
// value directly.
//
Handlebars.registerHelper(«save», function(name, text) {
    //
    // Local Variables.
    //
    var newName = «», newText = «»;
 
    //
    // See if the name and text is in the first argument
    // with a |.
    // use the name and text arguments as given.
    //
    if(name.indexOf(«|») > 0) {
        var parts = name.split(«|»);
        newName = parts[0];
        newText = parts[1];
    } else {
        newName = name;
        newText = text;
    }
 
    //
    // Register the new helper.
    //
   Handlebars.registerHelper(newName, function() {
      return newText;
   });
 
   //
   // Return the text.
   //
   return newText;
});
 
//
// HandleBars Helper: date
//
// Description: This helper returns the date
// based on the format given.
//
Handlebars.registerHelper(«date», function(dFormat) {
   return moment().format(dFormat);
});
 
//
// HandleBars Helper: cdate
//
// Description: This helper returns the date given
// in to a format based on the format
// given.
//
Handlebars.registerHelper(«cdate», function(cTime, dFormat) {
   return moment(cTime).format(dFormat);
});

В следующем разделе кода определяются помощники Handlebars, которые я определил для использования на веб-сервере: save , date и cdate . Помощник сохранения позволяет создавать переменные внутри страницы. Эта версия поддерживает версию goPress, где параметр имеет имя и значение вместе, разделенные «|». Вы также можете указать сохранение, используя два параметра. Например:

1
2
3
4
5
{{save «name|Richard Guay»}}
{{save «newName» «Richard Guay»}}
 
Name is: {{name}}
newName is: {{newName}}

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

Помощники date и cdate форматируют текущую дату ( date ) или заданную дату ( cdate ) в соответствии с правилами форматирования библиотеки moment.js . cdate ожидает, что дата будет первым параметром и будет иметь формат ISO 8601.

1
2
3
4
5
6
7
8
9
//
// Create and configure the server.
//
var nodePress = express();
 
//
// Configure middleware.
//
nodePress.use(morgan(‘combined’))

Теперь код создает экземпляр Express для настройки фактического ядра сервера. Функция nodePress.use() устанавливает программное обеспечение промежуточного программного обеспечения. Промежуточное программное обеспечение — это любой код, который обслуживается при каждом обращении к серверу. Здесь я настроил библиотеку Morgan.js для создания правильного вывода журнала сервера.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
//
// Define the routes.
//
nodePress.get(‘/’, function(request, response) {
   setBasicHeader(response);
   if((parts[«Cache»] == true) && (mainPage != null)) {
       response.send(mainPage);
   } else {
    mainPage = page(«main»);
    response.send(mainPage);
   }
});
 
nodePress.get(‘/favicon.ico’, function(request, response) {
   var options = {
      root: parts[‘Sitebase’] + ‘images/’,
      dotfiles: ‘deny’,
      headers: {
         ‘x-timestamp’: Date.now(),
         ‘x-sent’: true
      }
   };
   response.set(«Content-Type», «image/ico»);
   setBasicHeader(response);
   response.sendFile(‘favicon.ico’, options, function(err) {
      if (err) {
         console.log(err);
         response.status(err.status).end();
      } else {
         console.log(‘Favicon was sent:’, ‘favicon.ico’);
      }
   });
});
 
nodePress.get(‘/stylesheets.css’, function(request, response) {
   response.set(«Content-Type», «text/css»);
   setBasicHeader(response);
   response.type(«css»);
   if((parts[«Cache»] == true) && (siteCSS != null)) {
    response.send(siteCSS);
   } else {
    siteCSS = fs.readFileSync(parts[‘Sitebase’] + ‘css/final/final.css’);
    response.send(siteCSS);
   }
});
 
nodePress.get(‘/scripts.js’, function(request, response) {
   response.set(«Content-Type», «text/javascript»);
   setBasicHeader(response);
   if((parts[«Cache»] == true) && (siteScripts != null)) {
    response.send(siteScripts);
   } else {
    siteScripts = fs.readFileSync(parts[‘Sitebase’] + ‘js/final/final.js’, ‘utf8’);
    response.send(siteScripts);
   }
});
 
nodePress.get(‘/images/:image’, function(request, response) {
   var options = {
      root: parts[‘Sitebase’] + ‘images/’,
      dotfiles: ‘deny’,
      headers: {
         ‘x-timestamp’: Date.now(),
         ‘x-sent’: true
      }
   };
   response.set(«Content-Type», «image/» + path.extname(request.params.image).substr(1));
   setBasicHeader(response);
   response.sendFile(request.params.image, options, function(err) {
      if (err) {
         console.log(err);
         response.status(err.status).end();
      } else {
         console.log(‘Image was sent:’, request.params.image);
      }
   });
});
 
nodePress.get(‘/posts/blogs/:blog’, function(request, response) {
   setBasicHeader(response);
   response.send(post(«blogs», request.params.blog, «index»));
});
 
nodePress.get(‘/posts/blogs/:blog/:post’, function(request, response) {
   setBasicHeader(response);
   response.send(post(«blogs», request.params.blog, request.params.post));
});
 
nodePress.get(‘/posts/news/:news’, function(request, response) {
   setBasicHeader(response);
   response.send(post(«news», request.params.news, «index»));
});
 
nodePress.get(‘/posts/news/:news/:post’, function(request, response) {
   setBasicHeader(response);
   response.send(post(«news», request.params.news, request.params.post));
});
 
nodePress.get(‘/:page’, function(request, response) {
   setBasicHeader(response);
   response.send(page(request.params.page));
});

Этот раздел кода определяет все маршруты, необходимые для реализации веб-сервера. Все маршруты запускают setBasicHeader() для установки правильных значений заголовка. Все запросы на тип страницы вызывают функцию page() , тогда как все запросы на страницу типа post вызывают функцию posts() .

По умолчанию для Content-Type используется HTML. Следовательно, для CSS, JavaScript и изображений для Content-Type явно задано соответствующее значение.

Вы также можете определять маршруты с помощью глаголов put , delete и post . Этот простой сервер использует get глагол get .

01
02
03
04
05
06
07
08
09
10
//
// Start the server.
//
var addressItems = parts[‘ServerAddress’].split(‘:’);
var server = nodePress.listen(addressItems[2], function() {
   var host = server.address().address;
   var port = server.address().port;
 
   console.log(‘nodePress is listening at http://%s:%s’, host, port);
});

Последнее, что нужно сделать перед определением различных используемых функций, — это запустить сервер. Файл server.json содержит DNS-имя (здесь это localhost ) и порт для сервера. После анализа серверная функция listen() использует номер порта для запуска сервера. Как только порт сервера открыт, скрипт регистрирует адрес и порт сервера.

01
02
03
04
05
06
07
08
09
10
11
12
13
//
// Function: setBasicHeader
//
// Description: This function will set the basic header information
// needed.
//
// Inputs:
// response The response object
//
function setBasicHeader(response) {
   response.append(«Cache-Control», «max-age=2592000, cache»);
   response.append(«Server», «nodePress — a CMS written in node from Custom Computer Tools: http://customct.com.»);
}

Первая определенная функция — это setBasicHeader() . Эта функция устанавливает заголовок ответа, чтобы указать браузеру кэшировать страницу в течение одного месяца. Он также сообщает браузеру, что сервер является сервером nodePress. Если есть какие-либо другие стандартные значения заголовков, которые вы хотите, вы бы добавили их сюда с помощью функции response.append()

01
02
03
04
05
06
07
08
09
10
11
12
13
14
//
// Function: page
//
// Description: This function processes a page request
//
// Inputs:
// page The requested page
//
function page(page) {
   //
   // Process the given page using the standard layout.
   //
   return (processPage(parts[«layout»], parts[‘Sitebase’] + «pages/» + page));
}

Функция page() отправляет шаблон макета для страницы и расположение страницы на сервере в processPage() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
//
// Function: post
//
// Description: This function processes a post request
//
// Inputs:
// type The type of post.
// cat The category of the post.
// post The requested post
//
function post(type, cat, post) {
   //
   // Process the post given the type and the post name.
   //
   return (processPage(parts[«layout»], parts[‘Sitebase’] + «posts/» + type + «/» + cat + «/» + post));
}

Функция post() аналогична функции page() , за исключением того, что в сообщениях есть больше элементов для определения каждого сообщения. В этой серии серверов сообщение содержит type , категорию и фактическое post . Тип это либо blogs либо news . Категория flatcms . Поскольку они представляют собой имена каталогов, вы можете делать их как хотите. Просто сопоставьте наименование с тем, что находится в вашей файловой системе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//
// Function: processPage
//
// Description: This function processes a page for the CMS.
//
// Inputs:
// layout The layout to use for the page.
// page Path to the page to render.
//
function processPage(layout, page) {
   //
   // Get the pages contents and add to the layout.
   //
   var context = {};
   context = MergeRecursive(context, parts);
   context[‘content’] = figurePage(page);
   context[‘PageName’] = path.basename(page, path.extname(page));
 
   //
   // Load page data.
   //
   if(fileExists(page + «.json»)) {
    //
    // Load the page’s data file and add it to the data structure.
    //
    context = MergeRecursive(context, JSON.parse(fs.readFileSync(page + ‘.json’, ‘utf8’)));
   }
 
   //
   // Process Handlebars codes.
   //
   var template = Handlebars.compile(layout);
   var html = template(context);
 
   //
   // Process all shortcodes.
   //
   html = processShortCodes(html);
 
   //
   // Run through Handlebars again.
   //
   template = Handlebars.compile(html);
   html = template(context);
 
   //
   // Return results.
   //
   return (html);
}

Функция processPage() получает макет и путь к содержимому страницы для отображения. Функция начинается с создания локальной копии глобальной переменной parts и добавления хэштега «содержимого» с результатами вызова функции figurePage() . Затем он устанавливает PageName хеша PageName на имя страницы.

Затем эта функция компилирует содержимое страницы в шаблон макета с помощью Handlebars. После этого processShortCodes() все шорткоды, определенные на странице. Затем шаблонный движок Handlebars снова просматривает код. Браузер затем получает результаты.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//
// Function: processShortCodes
//
// Description: This function takes a string and
// processes all of the shortcodes in
// the string.
//
// Inputs:
// content String to process
//
function processShortCodes(content) {
   //
   // Create the results variable.
   //
   var results = «»;
 
   //
   // Find the first match.
   //
   var scregFind = /\-\[([^\]]*)\]\-/i;
   var match = scregFind.exec(content);
   if (match != null) {
    results += content.substr(0,match.index);
      var scregNameArg = /(\w+)(.*)*/i;
      var parts = scregNameArg.exec(match[1]);
      if (parts != null) {
         //
         // Find the closing tag.
         //
         var scregClose = new RegExp(«\\-\\[\\/» + parts[1] + «\\]\\-«);
         var left = content.substr(match.index + 4 + parts[1].length);
         var match2 = scregClose.exec(left);
         if (match2 != null) {
            //
            // Process the enclosed shortcode text.
            //
            var enclosed = processShortCodes(content.substr(match.index + 4 + parts[1].length, match2.index));
 
            //
            // Figure out if there were any arguments.
            //
            var args = «»;
            if (parts.length == 2) {
               args = parts[2];
            }
 
            //
            // Execute the shortcode.
            //
            results += shortcodes[parts[1]](args, enclosed);
 
            //
            // Process the rest of the code for shortcodes.
            //
            results += processShortCodes(left.substr(match2.index + 5 + parts[1].length));
         } else {
            //
            // Invalid shortcode.
            //
            results = content;
         }
      } else {
         //
         // Invalid shortcode.
         //
         results = content;
      }
   } else {
      //
      // No shortcodes found.
      //
      results = content;
   }
   return (results);
}

Функция processShortCodes() принимает содержимое веб-страницы в виде строки и ищет все шорткоды. Шорткод — это блок кода, похожий на теги HTML. Примером может быть:

1
2
3
-[box]-
    <p>This is inside a box</p>
-[/box]-

Этот код имеет шорткод для box вокруг абзаца HTML. Там, где HTML использует < и > , шорткоды используют -[ и ]- . После имени строка, содержащая аргументы шорткода, может или не может быть там.

Функция processShortCodes() находит шорткод, получает его имя и аргументы, находит конец для получения содержимого, обрабатывает содержимое шорткодов, выполняет шорткод с аргументами и содержимым, добавляет результаты на готовую страницу и ищет следующий шорткод на остальной части страницы. Цикл выполняется путем рекурсивного вызова функции.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// Define the shortcodes function array.
//
var shortcodes = {
   ‘box’: function(args, inside) {
      return («<div class=’box’>» + inside + «</div>»);
   },
   ‘Column1’: function(args, inside) {
      return («<div class=’col1′>» + inside + «</div>»);
   },
   ‘Column2’: function(args, inside) {
      return («<div class=’col2′>» + inside + «</div>»);
   },
   ‘Column1of3’: function(args, inside) {
      return («<div class=’col1of3′>» + inside + «</div>»);
   },
   ‘Column2of3’: function(args, inside) {
      return («<div class=’col2of3′>» + inside + «</div>»);
   },
   ‘Column3of3’: function(args, inside) {
      return («<div class=’col3of3′>» + inside + «</div>»);
   },
   ‘php’: function(args, inside) {
      return («<div class=’showcode’><pre type=’syntaxhighlighter’ class=’brush: php’>» + inside + «</pre></div>»);
   },
   ‘js’: function(args, inside) {
      return («<div class=’showcode’><pre type=’syntaxhighlighter’ class=’brush: javascript’>» + inside + «</pre></div>»);
   },
   ‘html’: function(args, inside) {
      return («<div class=’showcode’><pre type=’syntaxhighlighter’ class=’brush: html’>» + inside + «</pre></div>»);
   },
   ‘css’: function(args, inside) {
      return («<div class=’showcode’><pre type=’syntaxhighlighter’ class=’brush: css’>» + inside + «</pre></div>»);
   }
};

Этот следующий раздел определяет структуру shortcodes json, которая определяет имя шорткода, связанного с его функцией. Все функции шорткода принимают два параметра: args и inside . args — это все после имени, пробела и до закрытия тега. inside все, что содержится в открывающих и закрывающих тегах шорткода. Эти функции являются базовыми, но вы можете создать шорткод для выполнения всего, что вы можете придумать в JavaScript.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// Function: figurePage
//
// Description: This function figures the page type
// and loads the contents appropriately
// returning the HTML contents for the page.
//
// Inputs:
// page The page to load contents.
//
function figurePage(page) {
   var result = «»;
 
   if (fileExists(page + «.html»)) {
      //
      // It’s an HTML file.
      //
      result = fs.readFileSync(page + «.html»);
   } else if (fileExists(page + «.amber»)) {
      //
      // It’s a jade file.
      // am still using the amber extension for compatibility
      // to goPress.
      //
      var jadeFun = jade.compileFile(page + «.amber», {});
 
      // Render the function
      var result = jadeFun({});
   } else if (fileExists(page + «.md»)) {
      //
      // It’s a markdown file.
      // it on.
      //
      result = marked(fs.readFileSync(page + «.md»).toString());
 
      //
      // This undo marked’s URI encoding of quote marks.
      //
      result = result.replace(/\&quot\;/g,»\»»);
   }
 
   return (result);
}

Функция figurePage() получает полный путь к странице на сервере. Затем эта функция проверяет, является ли она страницей HTML, Markdown или Jade на основе расширения. Я все еще использую .amber для Jade, так как это была библиотека, которую я использовал с сервером goPress. Все содержимое Markdown и Jade переводятся в HTML перед передачей в вызывающую подпрограмму. Поскольку процессор уценки переводит все кавычки в &quot; Я перевожу их обратно, прежде чем передать обратно.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
//
// Function: fileExists
//
// Description: This function returns a boolean true if
// the file exists.
//
// Inputs:
// filePath Path to a file in a string.
//
function fileExists(filePath) {
   try {
      return fs.statSync(filePath).isFile();
   } catch (err) {
      return false;
   }
}

Функция fileExists() является заменой функции fs.exists() которая раньше была частью библиотеки fs Node.js. Он использует fs.statSync() чтобы попытаться получить статус файла. Если произошла ошибка, возвращается false . В противном случае он возвращает true .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
// Function: MergeRecursive
//
// Description: Recursively merge properties of two objects
//
// Inputs:
// obj1 The first object to merge
// obj2 The second object to merge
//
function MergeRecursive(obj1, obj2) {
 
   for (var p in obj2) {
      try {
         // Property in destination object set;
         if (obj2[p].constructor == Object) {
            obj1[p] = MergeRecursive(obj1[p], obj2[p]);
 
         } else {
            obj1[p] = obj2[p];
 
         }
 
      } catch (e) {
         // Property in destination object not set;
         obj1[p] = obj2[p];
 
      }
   }
 
   return obj1;
}

Последняя функция — это функция MergeRecursive() . Копирует второй проходной объект в первый пройденный объект. Я использую это, чтобы скопировать глобальную переменную основных parts в локальную копию перед добавлением отдельных частей страницы.

После сохранения файла вы можете запустить сервер с:

1
node nodePress.js

Кроме того, вы можете использовать скрипт npm который находится в файле package.json. Вы запускаете сценарии npm, как это:

1
npm start

Это запустит start скрипт, который находится внутри файла package.json.

Главная страница сервера nodePress
Главная страница сервера nodePress

Направьте ваш веб-браузер на http://localhost:8080 и вы увидите страницу выше. Вы могли заметить, что я добавил больше тестового кода на главную страницу. Все изменения на страницах находятся в загрузке для этого урока. В основном это всего лишь небольшие изменения для более полного тестирования функциональности и соответствия любым различиям при использовании разных библиотек. Самым заметным отличием является то, что библиотека Jade не использует $ для именования переменных, в то время как Amber использует.

Теперь у вас точно такая же плоская файловая система CMS в Go и Node.js. Это только царапает поверхность того, что вы можете построить с этой платформой. Экспериментируйте и пробуйте что-то новое. Это лучшая часть создания вашего собственного веб-сервера.