首頁 >web前端 >js教程 >建立內容管理系統:nodePress

建立內容管理系統:nodePress

PHPz
PHPz原創
2023-09-03 13:53:051464瀏覽

您已使用 Go 成功建立了平面檔案系統內容管理系統 (CMS)。下一步是採用同樣的理念,使用 Node.js 製作一個 Web 伺服器。我將向您展示如何載入庫、建立伺服器和運行伺服器。

此 CMS 將使用第一個教學課程「建立 CMS:結構和樣式」中介紹的網站資料結構。因此,請下載此基本結構並將其安裝在新目錄中。

取得節點和節點庫

在 Mac 上安裝 Node.js 最簡單的方法是使用 Homebrew。如果您尚未安裝 Homebrew,教學 Homebrew 揭秘:OS X 的終極套件管理器將向您展示如何安裝。

要使用 Homebrew 安裝 Node.js,請在終端機中輸入以下指令:

brew install node

完成後,您的 Mac 上將完全安裝了 Node 和 npm 命令。對於所有其他平台,請按照 Node.js 網站上的說明進行操作。

請注意:許多套件管理器目前正在安裝 Node.js 版本 0.10。本教學假設您擁有 5.3 或更高版本。您可以鍵入以下內容來檢查您的版本:

node --version

node 指令執行 JavaScript 解譯器。 npm 指令是 Node.js 的套件管理器,用於安裝新程式庫、建立新專案以及執行專案腳本。 Envato Tuts 上有許多關於 Node.js 和 NPM 的精彩教學和課程。

要安裝 Web 伺服器的庫,您必須在 Terminal.app 或 iTerm.app 程式中執行以下命令:

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 是一個 Web 應用程式開發平台。它類似Go中的goWeb函式庫。 Handlebars 是用於建立頁面的模板引擎。 Moment 是一個用於處理日期的函式庫。 Marked 是一個很棒的 JavaScript 中 Markdown 到 HTML 轉換器。 Jade 是一種 HTML 速記語言,可輕鬆建立 HTML。 Morgan 是 Express 的中間件庫,可產生 Apache 標準日誌檔案。

安裝庫的另一種方法是下載本教學的來源檔案。下載並解壓縮後,在主目錄中輸入:

npm --install

這將安裝創建該專案所需的一切。

nodePress.js

現在您可以開始建立伺服器了。在專案的頂層目錄中,建立一個名為 nodePress.js 的文件,在您選擇的編輯器中開啟它,然後開始新增以下程式碼。我將解釋放入文件中的程式碼。

//
// 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'); // http://expressjs.com/en/
var morgan = require('morgan'); // https://github.com/expressjs/morgan
var Handlebars = require("handlebars"); // http://handlebarsjs.com/
var moment = require("moment"); // http://momentjs.com/
var marked = require('marked'); // https://github.com/chjj/marked
var jade = require('jade'); // http://jade-lang.com/

伺服器程式碼從初始化用於建立伺服器的所有程式庫開始。沒有帶有網址的註解的函式庫是內部 Node.js 函式庫。

//
// 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 檔案中的資訊來建立用於此網站的 styleslayouts 目錄的完整路徑。

接著將三個變數設定為空值:siteCSSsiteScriptsmainPage。這些全域變數將包含所有 CSS、JavaScript 和主索引頁內容。這三個項目是任何 Web 伺服器上要求最多的項目。因此,將它們保留在記憶體中可以節省時間。如果 server.json 檔案中的 Cache 變數為 false,則每個請求都會重新讀取這些項目。

marked.setOptions({
  renderer: new marked.Renderer(),
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: true,
  smartypants: false
});

此程式碼區塊用於配置 Marked 庫以從 Markdown 產生 HTML。大多數情況下,我會打開表格和 smartLists 支援。

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 變數進一步載入 styleslayout 目錄中的部分。 site 目錄內的 parts 目錄中的每個檔案也被載入到 parts 全域變數中。不含副檔名的檔案名稱是用來儲存檔案內容的名稱。這些名稱在 Handlebars 巨集中會擴展。

//
// 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. It also returns the
// 						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 |. If so, extract them properly. Otherwise,
	// 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);
});

下一段程式碼定義了我定義的在 Web 伺服器中使用的 Handlebars 幫助程式:savedatecdate。儲存助手允許在頁面內建立變數。此版本支援 goPress 版本,其中參數的名稱和值一起以「|」分隔。您也可以使用兩個參數指定保存。例如:

{{save "name|Richard Guay"}}
{{save "newName" "Richard Guay"}}

Name is: {{name}}
newName is: {{newName}}

這將產生相同的結果。我更喜歡第二種方法,但 Go 中的 Handlebars 庫不允許使用多個參數。

datecdate 帮助程序格式化当前日期 (date) 或给定日期 (cdate)根据 moment.js 库格式化规则。 cdate 帮助程序期望渲染的日期是第一个参数并且具有 ISO 8601 格式。

//
// Create and configure the server.
//
var nodePress = express();

//
// Configure middleware.
//
nodePress.use(morgan('combined'))

现在,代码创建一个 Express 实例来配置实际的服务器引擎。 nodePress.use() 函数设置中间件软件。中间件是在每次调用服务器时提供服务的任何代码。在这里,我设置了 Morgan.js 库来创建正确的服务器日志输出。

//
// 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));
});

这部分代码定义了实现 Web 服务器所需的所有路由。所有路由都运行 setBasicHeader() 函数来设置正确的标头值。所有针对页面类型的请求都会调用 page() 函数,而所有针对 post 类型页面的请求都会调用 posts() 函数。

Content-Type 的默认值为 HTML。因此,对于 CSS、JavaScript 和图像,Content-Type 显式设置为其适当的值。

您还可以使用 putdeletepost REST 动词定义路由。这个简单的服务器仅使用 get 动词。

//
// 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() 函数使用端口号来启动服务器。服务器端口打开后,脚本会记录服务器的地址和端口。

//
// 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() 函数在此处添加它们。

//
// 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() 函数。

//
// 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() 函数,不同之处在于帖子有更多项目来定义每个帖子。在这个系列的服务器中,一个post包含一个type、category,以及实际的post。类型为 blogsnews。类别是 flatcms。由于这些代表目录名称,因此您可以将它们设为您想要的任何名称。只需将命名与文件系统中的名称相匹配即可。

//
// 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 全局变量的本地副本,并添加“contents”主题标签以及调用 figurePage() 函数的结果。然后,它将 PageName 哈希值设置为页面名称。

然后,该函数使用 Handlebars 将页面内容编译到布局模板。之后, processShortCodes() 函数将展开页面上定义的所有短代码。然后,Handlebars 模板引擎再次检查代码。然后浏览器接收结果。

//
// 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. Return full string.
            //
            results = content;
         }
      } else {
         //
         // Invalid shortcode. Return full string.
         //
         results = content;
      }
   } else {
      //
      // No shortcodes found. Return the string.
      //
      results = content;
   }
   return (results);
}

processShortCodes() 函数将网页内容作为字符串并搜索所有短代码。短代码是类似于 HTML 标签的代码块。一个例子是:

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

此代码在 HTML 段落周围有一个 box 的简码。其中 HTML 使用 >>,短代码使用 -[ 和 >]-。在名称后面,可以包含或不可以包含包含短代码参数的字符串。

processShortCodes() 函数查找短代码,获取其名称和参数,找到末尾以获取内容,处理短代码的内容,使用参数和内容执行短代码,将结果添加到完成中页面,并在页面的其余部分搜索下一个短代码。循环是通过递归调用函数来执行的。

//
// 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 + "
"); }, 'js': function(args, inside) { return ("
" + inside + "
"); }, 'html': function(args, inside) { return ("
" + inside + "
"); }, 'css': function(args, inside) { return ("
" + inside + "
"); } };

下一节定义 shortcodes json 结构,该结构定义与其函数关联的短代码的名称。所有短代码函数都接受两个参数:argsinsideargs 是名称和空格之后、标签结束之前的所有内容。 inside 是开始和结束短代码标记包含的所有内容。这些功能是基本功能,但您可以创建一个短代码来执行您能在 JavaScript 中想到的任何功能。

//
// 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. Read it in and send it on.
      //
      result = fs.readFileSync(page + ".html");
   } else if (fileExists(page + ".amber")) {
      //
      // It's a jade file. Convert to HTML and send it on. I
      // 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. Convert to HTML and send
      // 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 页面。我仍然在 Jade 中使用 .amber,因为那是我在 goPress 服务器上使用的库。所有 Markdown 和 Jade 内容都会先转换为 HTML,然后再传递给调用例程。由于 Markdown 处理器将所有引号翻译为 ",因此我在传回之前将它们翻译回来。

//
// Function:     	fileExists
//
// Description: 	This function returns a boolean true if 
// 					the file exists. Otherwise, false.
//
// 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() 函数的替代品,该函数曾经是 Node.js 的 fs 库的一部分。它使用 fs.statSync() 函数来尝试获取文件的状态。如果发生错误,则会返回 false。否则,返回 true

//
//  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; update its value.
         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; create it and set its value.
         obj1[p] = obj2[p];

      }
   }

   return obj1;
}

最后一个函数是 MergeRecursive() 函数。它将第二个传递对象复制到第一个传递对象中。在添加特定于页面的部分之前,我利用它将主 parts 全局变量复制到本地副本中。

本地运行

保存文件后,您可以使用以下命令运行服务器:

node nodePress.js

或者,您可以使用 package.json 文件中的 npm 脚本。您可以像这样运行 npm 脚本:

npm start

这将运行 package.json 文件内的 start 脚本。

"建立內容管理系統:nodePress"

将您的网络浏览器指向 http://localhost:8080,您将看到上面的页面。您可能已经注意到我在主页上添加了更多测试代码。对页面的所有更改都包含在本教程的下载中。它们大多只是一些小的调整,以更全面地测试功能并适应使用不同库的任何差异。最显着的区别是 Jade 库不使用 $ 来命名变量,而 Amber 则使用。

结论

现在,您在 Go 和 Node.js 中拥有完全相同的平面文件系统 CMS。这只是您可以使用此平台构建的内容的表面。尝试并尝试新事物。这是创建您自己的网络服务器的最佳部分。

以上是建立內容管理系統:nodePress的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn