Bouw een CMS nodePress

U hebt met behulp van Go een plat bestandssysteem Content Management System (CMS) gemaakt. De volgende stap is om hetzelfde ideaal te nemen en een webserver te maken met Node.js. Ik zal je laten zien hoe je de bibliotheken laadt, de server maakt en de server uitvoert.

Deze CMS gebruikt de sitegegevensstructuur zoals uiteengezet in de eerste zelfstudie, Building a CMS: Structure and Styling. Download en installeer deze basisstructuur daarom in een nieuwe map.

Node en de knooppuntbibliotheken verkrijgen

De eenvoudigste manier om Node.js op een Mac te installeren, is met Homebrew. Als je Homebrew nog niet hebt geïnstalleerd, dan is de zelfstudie Homebrew Demystified: OS X's Ultimate Package Manager laat je zien hoe.

Als u Node.js met Homebrew wilt installeren, typt u deze instructie in een terminal:

brouw installatie knooppunt

Als je klaar bent, zul je de node en npm commando's volledig geïnstalleerd hebben op je Mac. Volg voor alle andere platforms de instructies op de Node.js-website.

Let op: veel pakketbeheerders installeren momenteel Node.js versie 0.10. Deze tutorial gaat ervan uit dat je versie 5.3 of nieuwer hebt. U kunt uw versie controleren door het volgende te typen:

knooppunt - versie

De knooppunt opdracht voert de JavaScript-interpreter uit. De NPM command is een pakketbeheerder voor Node.js om nieuwe bibliotheken te installeren, nieuwe projecten aan te maken en scripts voor een project uit te voeren. Er zijn veel geweldige tutorials en cursussen over Node.js en NPM bij Envato Tuts+.

Om de bibliotheken voor de webserver te installeren, moet u deze opdrachten uitvoeren in het programma Terminal.app of iTerm.app:

npm install express --save npm install handle --save npm install moment - save npm install gemarkeerd --save npm install jade --save npm install morgan --save

Express is een webapplicatie-ontwikkelplatform. Het lijkt op de goWeb-bibliotheek in Go. Stuur is de sjablonengine voor het maken van de pagina's. Moment is een bibliotheek voor het werken met datums. Gemarkeerd is een geweldige Markdown to HTML-converter in JavaScript. Jade is een HTML-steno-taal voor het eenvoudig maken van HTML. Morgan is een middleware-bibliotheek voor Express die de Apache Standard Log Files genereert.

Een alternatieve manier om de bibliotheken te installeren, is door de bronbestanden voor deze zelfstudie te downloaden. Eenmaal gedownload en uitgepakt, typ dit in de hoofddirectory:

npm - installeren

Dat zal alles installeren wat nodig is om dit project te maken.

nodePress.js

Nu kunt u aan de slag gaan met het maken van de server. Maak in de bovenste map van het project een bestand met de naam nodePress.js, open het in de gewenste editor en begin met het toevoegen van de volgende code. Ik ga de code uitleggen zoals deze in het bestand wordt geplaatst.

// // Laad de gebruikte bibliotheken. // var fs = require ('fs'); var path = require ("path"); var child_process = require ('child_process'); var process = require ('proces'); 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/

De servercode begint met de initialisatie van alle bibliotheken die worden gebruikt om de server te maken. Bibliotheken die geen commentaar met een webadres hebben, zijn interne Node.js-bibliotheken.

// // Globale variabelen instellen. // 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;

Vervolgens heb ik alle globale variabelen en bibliotheekconfiguraties ingesteld. Het gebruik van globale variabelen is niet de beste manier om software te ontwerpen, maar het werkt wel en zorgt voor een snelle ontwikkeling.

De onderdelen variabele is een hash-array die alle delen van een webpagina bevat. Elke pagina verwijst naar de inhoud van deze variabele. Het begint met de inhoud van het server.json-bestand dat zich boven aan de serverdirectory bevindt.

Ik gebruik vervolgens de informatie uit het bestand server.json om de volledige paden naar de te maken stijlen en lay-outs mappen die voor deze site worden gebruikt.

Drie variabelen worden vervolgens ingesteld op nulwaarden: siteCSS, siteScripts, en hoofdpagina. Deze globale variabelen bevatten alle inhoud van de CSS-, JavaScript- en hoofdindexpagina's. Deze drie items zijn de meest gevraagde items op elke webserver. Daarom bespaart u tijd door ze in het geheugen te bewaren. Als het Cache variabele in het bestand server.json is false, deze items worden bij elk verzoek opnieuw gelezen.

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

Dit codeblok is voor het configureren van de Gemarkeerde bibliotheek voor het genereren van HTML vanuit Markdown. Meestal zet ik de tafel aan en SmartLists ondersteunen.

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'); // // Lees in de pagina-onderdelen. // var partFiles = fs.readdirSync (parts ['Sitebase'] + "parts /"); partFiles.forEach (functie (ele, index, array) parts [path.basename (ele, path.extname (ele))] = figurePage (parts ['Sitebase'] + "parts /" + path.basename (ele, path.extname (ele))););

De onderdelen variabele wordt verder geladen met de delen van de stijlen en lay-out directories. Elk bestand in de onderdelen map binnen de plaats map wordt ook geladen in de onderdelen globale variabele. De naam van het bestand zonder de extensie is de naam die wordt gebruikt om de inhoud van het bestand op te slaan. Deze namen worden uitgebreid in de macro Handlebar.

// // Stel Helpers van stuur in. // // // HandleBars Helper: opslaan // // Beschrijving: deze helper verwacht een // """"waar de naam // wordt opgeslagen met de waarde voor toekomstige // -uitbreidingen en ook de // -waarde direct. // Handlebars.registerHelper (" opslaan ", functie (naam, tekst) // // Lokale variabelen. // var newName = "", newText = ""; // // Kijk of de naam en tekst in het eerste argument // is met. | Als dat het geval is, extraheer ze dan goed. Anders, // gebruik de naam en tekst argumenten als gegeven. // if (naam.indexOf ("|")> 0) var parts = name.split ("|"); newName = parts [0]; newText = parts [1]; else newName = naam; newText = tekst; // // Registreer de nieuwe helper // Handlebars.registerHelper (newName, function () return newText;); // // Retourneer de tekst. // return newText;) ; // // HandleBars Helper: date // // Beschrijving: deze helper retourneert de datum // op basis van het opgegeven formaat // Handlebars.registerHelper ("date", function (dFormat) return moment (). Format ( dFormat);); // // HandleBars Helper: cdate // // Beschrijving: deze helper retourneert de gegeven datum // in naar een indeling op basis van het formaat // gegeven. // Handlebars.registerHelper ("cdate", functie (cTime, dFormat) return moment (cTime) .format (dFormat););

Het volgende gedeelte van de code definieert de Handlebar-helpers die ik heb gedefinieerd voor gebruik in de webserver: opslaan, datum, en CDate. Met de bewaarhelper kunnen variabelen binnen een pagina worden gemaakt. Deze versie ondersteunt de goPress-versie waarbij de naam en de waarde samen van elkaar zijn gescheiden door een "|". U kunt ook een opslag specificeren met behulp van twee parameters. Bijvoorbeeld:

save "name | Richard Guay" save "newName" "Richard Guay" Naam is: name newName is: newName

Dit levert dezelfde resultaten op. Ik geef de voorkeur aan de tweede benadering, maar de Handlebars-bibliotheek in Go biedt niet meer dan één parameter.

De datum en CDate helpers formatteren de huidige datum (datum) of een bepaalde datum (CDate) volgens de moment.js bibliotheek opmaakregels. De CDate helper verwacht dat de datum de eerste parameter is en het ISO 8601-formaat heeft.

// // Maak en configureer de server. // var nodePress = express (); // // Configureer middleware. // nodePress.use (morgan ('combined'))

Nu maakt de code een Express-instantie voor het configureren van de eigenlijke serverengine. De nodePress.use () functie stelt de middleware-software in. Middleware is elke code die wordt geserveerd bij elke aanroep naar de server. Hier heb ik de Morgan.js-bibliotheek ingesteld om de juiste serverlogboekuitvoer te maken.

// // Bepaal de routes. // nodePress.get ('/', functie (request, response) setBasicHeader (response); if ((parts ["Cache"] == true) && (mainPage! = null)) response.send (mainPage) ; else mainPage = page ("main"); response.send (mainPage);); nodePress.get ('/ favicon.ico', functie (request, response) var options = root: parts ['Sitebase'] + 'images /', dotfiles: 'deny', headers: 'x-timestamp' : Date.now (), 'x-send': true; response.set ("Content-type", "image / ico"); setBasicHeader (response); response.sendFile ('favicon.ico', opties , functie (err) if (err) console.log (err); response.status (err.status) .end (); else console.log ('Favicon is verzonden:', 'favicon.ico' ););); nodePress.get ('/ stylesheets.css', functie (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', functie (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', functie (request, response) var options = root: parts ['Sitebase'] + 'images /', dotfiles: 'deny', headers: 'x-timestamp ': Date.now (),' x-send ': 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 ('Afbeelding is verzonden:', request.params.image););); nodePress.get ('/ posts / blogs /: blog', functie (request, response) setBasicHeader (response); response.send (post ("blogs", request.params.blog, "index"));) ; nodePress.get ('/ posts / blogs /: blog /: post', functie (request, response) setBasicHeader (response); response.send (post ("blogs", request.params.blog, request.params.post ));); nodePress.get ('/ posts / news /: news', functie (request, response) setBasicHeader (response); response.send (post ("nieuws", request.params.news, "index"));) ; nodePress.get ('/ posts / news /: news /: post', function (request, response) setBasicHeader (response); response.send (post ("nieuws", request.params.news, request.params.post ));); nodePress.get ('/: pagina', functie (request, response) setBasicHeader (response); response.send (page (request.params.page)););

Dit gedeelte van de code definieert alle routes die nodig zijn om de webserver te implementeren. Alle routes uitvoeren de setBasicHeader () functie om de juiste header-waarden in te stellen. Alle verzoeken om een ​​paginatype zullen de pagina() functie, terwijl alle verzoeken om een ​​posttypepagina de berichten () functie.

De standaard voor Content-Type is HTML. Daarom is voor CSS, JavaScript en afbeeldingen de Content-Type is expliciet ingesteld op de juiste waarde.

U kunt ook routes definiëren met de leggen, verwijderen, en post REST-werkwoorden. Deze eenvoudige server maakt alleen gebruik van de krijgen werkwoord.

// // Start de 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 luistert naar http: / /% s:% s ', host, poort););

Het laatste wat u moet doen voordat u de verschillende functies definieert, is om de server te starten. Het bestand server.json bevat de DNS-naam (hier is het localhost) en de poort voor de server. Eenmaal geparseerd, de server luister() functie gebruikt het poortnummer om de server te starten. Zodra de serverpoort open is, registreert het script het adres en de poort voor de server.

// // Functie: setBasicHeader // // Beschrijving: met deze functie wordt de algemene headerinformatie // nodig. // // Inputs: // response Het antwoordobject // function setBasicHeader (response) response.append ("Cache-Control", "max-age = 2592000, cache"); response.append ("Server", "nodePress - een CMS geschreven in een knooppunt van Custom Computer Tools: http://customct.com."); 

De eerste gedefinieerde functie is de setBasicHeader () functie. Met deze functie wordt de antwoordheader ingesteld om de browser opdracht te geven de pagina gedurende een maand in de cache op te slaan. Het vertelt de browser ook dat de server een nodePress-server is. Als er andere standaard header-waarden zijn die u zoekt, voegt u deze hier toe met de response.append () functie.

// // Functie: pagina // // Beschrijving: deze functie verwerkt een paginavraag // // Ingangen: // pagina De opgevraagde pagina // functiepagina (pagina) // // Verwerk de gegeven pagina volgens de standaard lay-out. // return (processPage (parts ["layout"], parts ['Sitebase'] + "pages /" + page)); 

De pagina() function stuurt de indelingssjabloon voor een pagina en de locatie van de pagina op de server naar de processPage () functie.

// // Functie: post // // Beschrijving: deze functie verwerkt een postverzoek // // Invoer: // type Het type bericht. // cat De categorie van het bericht. // post Het gevraagde bericht // functiebericht (type, kat, bericht) // // Verwerk de post op basis van het type en de postnaam. // return (processPage (parts ["layout"], parts ['Sitebase'] + "posts /" + type + "/" + cat + "/" + post)); 

De post() functie is net als de pagina() functie, behalve dat berichten meer items hebben om elk bericht te definiëren. In deze reeks servers bevat een bericht een type, categorie, en de werkelijke post. Het type is ook blogs of nieuws. De categorie is flatcms. Omdat deze directorynamen vertegenwoordigen, kunt u ze maken wat u maar wilt. Pas de naamgeving aan met wat zich in uw bestandssysteem bevindt.

// // Functie: procespagina // // Beschrijving: deze functie verwerkt een pagina voor de CMS. // // Inputs: // layout De lay-out die moet worden gebruikt voor de pagina. // page Pad naar de pagina om te renderen. // function processPage (layout, pagina) // // Download de inhoud van de pagina's en voeg deze toe aan de lay-out. // var context = ; context = MergeRecursive (context, onderdelen); context ['inhoud'] = figuurpagina (pagina); context ['PageName'] = path.basename (pagina, path.extname (pagina)); // // Paginagegevens laden. // if (fileExists (page + ".json")) // // Laad het gegevensbestand van de pagina en voeg het toe aan de gegevensstructuur. // context = MergeRecursive (context, JSON.parse (fs.readFileSync (pagina + '.json', 'utf8')));  // // Stuurcodes verwerken. // var template = Handlebars.compile (layout); var html = sjabloon (context); // // Verwerk alle shortcodes. // html = processShortCodes (html); // // Doorloop het stuur opnieuw. // template = Handlebars.compile (html); html = sjabloon (context); // // Retourresultaten. // return (html); 

De processPage () functie krijgt de lay-out en het pad naar de pagina-inhoud te renderen. De functie begint met het maken van een lokale kopie van de onderdelen globale variabele en het toevoegen van de hashtag "inhoud" met de resultaten van bellen figurePage () functie. Vervolgens wordt het Paginanaam hash-waarde voor de naam van de pagina.

Deze functie compileert vervolgens de inhoud van de pagina naar de lay-outsjabloon met behulp van Handlebars. Daarna, de processShortCodes () functie breidt alle op de pagina gedefinieerde shortcodes uit. Vervolgens gaat de stuurwielstuurknop Handlebars opnieuw over de code. De browser ontvangt vervolgens de resultaten.

// // Functie: processShortCodes // // Beschrijving: deze functie neemt een tekenreeks en // verwerkt alle shortcodes in // de tekenreeks. // // Inputs: // content Te verwerken string // function processShortCodes (content) // // Maak de resultatenvariabele. // var results = ""; // // Zoek de eerste match. // var scregFind = / \ - \ [([^ \]] *) \] \ - / i; var match = scregFind.exec (inhoud); if (match! = null) results + = content.substr (0, match.index); var scregNameArg = /(\w+)(.*)*/i; var parts = scregNameArg.exec (match [1]); if (parts! = null) // // Zoek de afsluitende tag. // var scregClose = new RegExp ("\\ - \\ [\\ /" + parts [1] + "\\] \\ -"); var left = content.substr (match.index + 4 + parts [1] .length); var match2 = scregClose.exec (links); if (match2! = null) // // Verwerk de ingesloten shortcodetekst. // var ingesloten = processShortCodes (content.substr (match.index + 4 + parts [1] .length, match2.index)); // // Zoek uit of er argumenten zijn. // var args = ""; if (parts.length == 2) args = parts [2];  // // Voer de shortcode uit. // resultaten + = shortcodes [parts [1]] (args, ingesloten); // // Verwerk de rest van de code voor shortcodes. // resultaten + = processShortCodes (left.substr (match2.index + 5 + parts [1] .length));  else // // ongeldige shortcode. Geef de volledige reeks terug. // resultaten = inhoud;  else // // Ongeldige shortcode. Geef de volledige reeks terug. // resultaten = inhoud;  else // // Geen shortcodes gevonden. Stuur de string terug. // resultaten = inhoud;  terug (resultaten); 

De processShortCodes () functie neemt de inhoud van de webpagina als een tekenreeks en zoekt naar alle shortcodes. Een shortcode is een codeblok dat lijkt op HTML-tags. Een voorbeeld zou zijn:

-[doos]- 

Dit zit in een doos

-[/doos]-

Deze code heeft een shortcode voor doos rond een HTML-paragraaf. Waar HTML gebruikt < en >, gebruik van shortcodes -[ en ]-. Na de naam kan een string met argumenten voor de shortcode wel of niet aanwezig zijn.

De processShortCodes () functie vindt een shortcode, krijgt zijn naam en argumenten, vindt het einde om de inhoud te krijgen, verwerkt de inhoud voor shortcodes, voert de shortcode uit met de argumenten en inhoud, voegt de resultaten toe aan de voltooide pagina en zoekt naar de volgende shortcode in de rest van de pagina. Het lussen wordt uitgevoerd door de functie recursief aan te roepen.

// // Definieer de functiearray met shortcodes. // var shortcodes = 'box': function (args, inside) return ("
"+ inside +"
");, 'Column1': function (args, inside) return ("
"+ inside +"
");, 'Column2': function (args, inside) return ("
"+ inside +"
");, 'Column1of3': function (args, inside) return ("
"+ inside +"
");, 'Column2of3': function (args, inside) return ("
"+ inside +"
");, 'Column3of3': function (args, inside) return ("
"+ inside +"
");, 'php': function (args, inside) return ("
"+ inside +"
");, 'js': function (args, inside) return ("
"+ inside +"
");, 'html': function (args, inside) return ("
"+ inside +"
");, 'css': function (args, inside) return ("
"+ inside +"
");;

Dit volgende gedeelte definieert de shortcodes json-structuur die de naam definieert van een shortcode die aan zijn functie is gekoppeld. Alle shortcode-functies accepteren twee parameters: args en binnen. De args is alles achter de naam en spatie en vóór het sluiten van de tag. De binnen is alles wat de open en sluitende shortcode-tags bevatten. Deze functies zijn eenvoudig, maar u kunt een shortcode maken om alles uit te voeren wat u maar kunt bedenken in JavaScript.

// // Functie: figurePage // // Beschrijving: deze functie geeft het paginatype weer // en laadt de inhoud op de juiste manier // de HTML-inhoud voor de pagina retourneren. // // Inputs: // page De pagina om inhoud te laden. // function figurePage (pagina) var result = ""; if (fileExists (page + ".html")) // // Het is een HTML-bestand. Lees het in en stuur het op. // result = fs.readFileSync (pagina + ".html");  else if (fileExists (page + ".amber")) // // Het is een jade-bestand. Converteren naar HTML en verzenden. I // gebruik de amber-extensie nog steeds voor compatibiliteit // to goPress. // var jadeFun = jade.compileFile (pagina + ".amber", ); // Render de functie var result = jadeFun ();  else if (fileExists (page + ".md")) // // Het is een markdown-bestand. Converteer naar HTML en stuur // it aan. // result = gemarkeerd (fs.readFileSync (pagina + ".md"). toString ()); // // De URI-codering van aanhalingstekens door deze markering ongedaan maken. // result = result.replace (/ \ & quot \; / g, "\" "); return (result);

De figurePage () functie ontvangt het volledige pad naar een pagina op de server. Deze functie test vervolgens of het een HTML-, Markdown- of Jade-pagina is op basis van de extensie. Ik gebruik nog steeds .amber voor Jade omdat dat de bibliotheek was die ik gebruikte met de goPress-server. Alle Markdown- en Jade-inhoud worden vertaald in HTML voordat deze wordt doorgegeven aan de aanroeproutine. Omdat de Markdown-processor alle aanhalingstekens vertaalt naar ", Ik vertaal ze terug voordat ik het terug geef.

// // Functie: fileExists // // Beschrijving: deze functie retourneert een booleaanse waar als // het bestand bestaat. Anders, false. // // Inputs: // filePath Path naar een bestand in een string. // function fileExists (filePath) try return fs.statSync (filePath) .isFile ();  catch (err) return false; 

De Bestand bestaat() functie is een vervanging voor de fs.exists () functie die vroeger deel uitmaakte van de fs bibliotheek van Node.js. Het gebruikt de fs.statSync () functie om te proberen de status van het bestand te krijgen. Als er een fout optreedt, vals wordt teruggestuurd. Anders keert het terug waar.

// // Functie: MergeRecursive // ​​// Beschrijving: recursief eigenschappen van twee objecten samenvoegen // // Inputs: // obj1 Het eerste samen te voegen object // obj2 Het tweede samen te voegen object // functie MergeRecursive (obj1, obj2) for (var p in obj2) try // Eigenschap in bestemming-objectset; update zijn waarde. if (obj2 [p] .constructor == Object) obj1 [p] = MergeRecursive (obj1 [p], obj2 [p]);  else obj1 [p] = obj2 [p];  catch (e) // Object in doelobject niet ingesteld; maak het aan en stel de waarde in. obj1 [p] = obj2 [p];  return obj1; 

De laatste functie is de MergeRecursive () functie. Het kopieert het tweede doorloopobject naar het eerste doorgegeven object. Ik maak hier gebruik van om de hoofdtekst te kopiëren onderdelen globale variabele in een lokale kopie voordat u paginaspecifieke onderdelen toevoegt.

Lokaal draaien

Na het opslaan van het bestand, kunt u de server uitvoeren met:

node nodePress.js

Als alternatief kunt u de NPM script dat zich in het package.json-bestand bevindt. U voert npm scripts als volgt uit:

npm start

Hiermee wordt het begin script dat zich in het bestand package.json bevindt.

nodePress Server Hoofdpagina

Richt uw webbrowser op http: // localhost: 8080 en je ziet de bovenstaande pagina. U hebt misschien gemerkt dat ik meer testcode aan de hoofdpagina heb toegevoegd. Alle wijzigingen aan de pagina's staan ​​in de download voor deze zelfstudie. Het zijn meestal slechts een paar kleine aanpassingen om de functionaliteit vollediger te testen en om eventuele verschillen aan te passen aan het gebruik van verschillende bibliotheken. Het meest opvallende verschil is dat de Jade-bibliotheek niet gebruikt $ om variabelen een naam te geven, terwijl Amber dat doet.

Conclusie

Nu hebt u precies hetzelfde platte bestandssysteem-CMS in Go en Node.js. Dit krast alleen het oppervlak van wat je met dit platform kunt bouwen. Experimenteer en probeer iets nieuws. Dat is het beste deel van het maken van uw eigen webserver.