您已使用 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 的文件,在您選擇的編輯器中開啟它,然後開始新增以下程式碼。我將解釋放入文件中的程式碼。
// // 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 檔案中的資訊來建立用於此網站的 styles
和 layouts
目錄的完整路徑。
接著將三個變數設定為空值:siteCSS
、siteScripts
和 mainPage
。這些全域變數將包含所有 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
變數進一步載入 styles
和 layout
目錄中的部分。 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 幫助程式:save
、date
和 cdate
。儲存助手允許在頁面內建立變數。此版本支援 goPress 版本,其中參數的名稱和值一起以「|」分隔。您也可以使用兩個參數指定保存。例如:
{{save "name|Richard Guay"}} {{save "newName" "Richard Guay"}} Name is: {{name}} newName is: {{newName}}
這將產生相同的結果。我更喜歡第二種方法,但 Go 中的 Handlebars 庫不允許使用多個參數。
date
和 cdate
帮助程序格式化当前日期 (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
显式设置为其适当的值。
您还可以使用 put
、delete
和 post
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
。类型为 blogs
或 news
。类别是 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 + "
" + inside + "
" + inside + "
下一节定义 shortcodes
json 结构,该结构定义与其函数关联的短代码的名称。所有短代码函数都接受两个参数:args
和 inside
。 args
是名称和空格之后、标签结束之前的所有内容。 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(/\"\;/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
脚本。
将您的网络浏览器指向 http://localhost:8080
,您将看到上面的页面。您可能已经注意到我在主页上添加了更多测试代码。对页面的所有更改都包含在本教程的下载中。它们大多只是一些小的调整,以更全面地测试功能并适应使用不同库的任何差异。最显着的区别是 Jade 库不使用 $
来命名变量,而 Amber 则使用。
现在,您在 Go 和 Node.js 中拥有完全相同的平面文件系统 CMS。这只是您可以使用此平台构建的内容的表面。尝试并尝试新事物。这是创建您自己的网络服务器的最佳部分。
以上是建立內容管理系統:nodePress的詳細內容。更多資訊請關注PHP中文網其他相關文章!