Maison >interface Web >js tutoriel >Créer un système de gestion de contenu : nodePress

Créer un système de gestion de contenu : nodePress

PHPz
PHPzoriginal
2023-09-03 13:53:051440parcourir

Vous avez créé avec succès un système de gestion de contenu (CMS) de système de fichiers plats à l'aide de Go. L'étape suivante consiste à reprendre la même idée et à créer un serveur Web à l'aide de Node.js. Je vais vous montrer comment charger la bibliothèque, créer le serveur et exécuter le serveur.

Ce CMS utilisera la structure des données du site introduite dans le premier tutoriel « Construire un CMS : structure et style ». Alors, téléchargez cette structure de base et installez-la dans un nouveau répertoire.

Obtenir des nœuds et des bibliothèques de nœuds

Le moyen le plus simple d'installer Node.js sur Mac est d'utiliser Homebrew. Si Homebrew n'est pas encore installé, le didacticiel Homebrew Revealed: The Ultimate Package Manager pour OS X vous montrera comment l'installer.

Pour installer Node.js à l'aide de Homebrew, entrez la commande suivante dans le terminal :

brew install node

Une fois terminées, les commandes Node et npm seront entièrement installées sur votre Mac. Pour toutes les autres plateformes, suivez les instructions sur le site Web Node.js.

Veuillez noter : de nombreux gestionnaires de packages installent actuellement la version 0.10 de Node.js. Ce didacticiel suppose que vous disposez de la version 5.3 ou supérieure. Vous pouvez vérifier votre version en tapant :

node --version
La commande

node 命令运行 JavaScript 解释器。 npm est le gestionnaire de packages pour Node.js et est utilisée pour installer de nouvelles bibliothèques, créer de nouveaux projets et exécuter des scripts de projet. Envato Tuts+ propose de nombreux didacticiels et cours intéressants sur Node.js et NPM.

Pour installer les bibliothèques du serveur web, vous devez exécuter la commande suivante dans le programme Terminal.app ou 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 est une plateforme de développement d'applications Web. Elle est similaire à la bibliothèque goWeb dans Go. Handles est un moteur de modèles pour créer des pages. Moment est une bibliothèque pour travailler avec des dates. Marked est un excellent convertisseur Markdown vers HTML en JavaScript. Jade est un langage HTML abrégé qui facilite la création de HTML. Morgan est une bibliothèque middleware pour Express qui génère des fichiers journaux standard Apache.

Une autre façon d'installer la bibliothèque consiste à télécharger les fichiers sources de ce didacticiel. Après téléchargement et décompression, entrez dans le répertoire principal :

npm --install

Cela installera tout le nécessaire pour créer ce projet.

nodePress.js

Vous pouvez maintenant commencer à créer le serveur. Dans le répertoire de niveau supérieur de votre projet, créez un fichier appelé nodePress.js, ouvrez-le dans l'éditeur de votre choix et commencez à ajouter le code suivant. Je vais vous expliquer le code que j'ai mis dans le fichier.

//
// 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/

Le code du serveur commence par initialiser toutes les bibliothèques utilisées pour créer le serveur. Les bibliothèques sans annotations avec URL sont des bibliothèques internes à 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;

Ensuite, j'ai configuré toutes les variables globales et la configuration de la bibliothèque. L'utilisation de variables globales n'est pas la meilleure pratique de conception de logiciels, mais elle fonctionne et facilite un développement rapide.

parts La variable est un tableau de hachages contenant toutes les parties de la page Web. Chaque page fait référence au contenu de cette variable. Cela commence par le contenu du fichier server.json en haut du répertoire du serveur.

J'ai ensuite utilisé les informations du fichier server.json pour créer le chemin complet vers le répertoire styleslayouts de ce site.

Puis définissez trois variables sur des valeurs vides : siteCSSsiteScriptsmainPage。这些全局变量将包含所有 CSS、JavaScript 和主索引页内容。这三个项目是任何 Web 服务器上请求最多的项目。因此,将它们保留在内存中可以节省时间。如果 server.json 文件中的 Cache Si la variable est fausse, les éléments seront relus à chaque requête.

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

Ce bloc de code est utilisé pour configurer la bibliothèque Marked pour générer du HTML à partir de Markdown. La plupart du temps, j'active la prise en charge des tables et des 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 Les variables chargent en outre les sections dans les répertoires styles et layout. Chaque fichier du répertoire

du répertoire site est également chargé dans la variable globale

. Le nom de fichier sans extension est le nom utilisé pour stocker le contenu du fichier. Ces noms sont développés dans la macro Guidon. savedatecdate

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

Le morceau de code suivant définit l'assistant guidon que j'ai défini pour une utilisation dans le serveur Web : 

. L'assistant de sauvegarde permet de créer des variables dans la page. Cette version prend en charge la version goPress où le nom et la valeur du paramètre sont séparés par "|". Vous pouvez également spécifier la sauvegarde à l'aide de deux paramètres. Par exemple :

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

Name is: {{name}}
newName is: {{newName}}
🎜Cela produira le même résultat. Je préfère la deuxième approche, mais la bibliothèque Handles de Go n'autorise pas plusieurs arguments. 🎜

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 脚本。

"Créer

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

结论

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn