Setting up our server

With our templating system working, let's go ahead and hook all of this up to our server. Instead of now responding with a simple message of A okay!, we will respond with our template all put together. We can do this easily by running the following code:

stream.respond({
'content-type': 'text/html',
':status': 200
});
const file = fs.createReadStream('./template/main.html');
const tStream = new LoopingStream({
dir: templateDirectory,
vars : { //removed for readability }
},
loopAmount : 2
})
file.pipe(tStream).pipe(stream);
});

This should look almost exactly like our test harness. If we now head to https://localhost:50000, we should see a very basic HTML page, but we have our templated file created! If we now head into the development tools and look at the sources, we will see something odd. The CSS states that we loaded in our main.css file, but the contents of the file look exactly like our HTML file!

Our server is responding to every request with our HTML file! What we need to do is some extra work so our server can respond to the requests correctly. We will do this by mapping the URL of the request to the files that we have. For simplicity's sake, we will only respond to HTML and CSS requests (we will not be sending any JavaScript across), but this system can easily be added upon to add in return types for images, and even files. We will add all of this by doing the following:

  1. We will set up a lookup table for our file endings, like this:
const FILE_TYPES = new Map([
['.css', path.join('.', templateDirectory, 'css')],
['.html', path.join('.', templateDirectory, 'html')]
]);
  1. Next, we will use this map to pull the files based off of headers of the request, like this:

const p = headers[':path'];
for(const [fileType, loc] of FILE_TYPES) {
if( p.endsWith(fileType) ) {
stream.respondWithFile(
path.join(loc, path.posix.basename(p)),
{
'content-type': `text/${fileType.slice(1)}`,
':status': 200
}
);
return;
}
}

The basic idea is to loop through our supported file types to see if we have them. If we do, then we will respond with the file and also tell the browser whether it is an HTML or CSS file through the content-type header.

  1. Now, we need a way to tell if a request is bad or not. Currently, we can go to any URL and we will just get the same response over and over again. We will utilize a publishedDirectory environment variable for this. Based on the name of the files in there, those will be our endpoints. For every sub-URL pattern, we will look for subdirectories that follow the same pattern. This is illustrated as follows:
https:localhost:50000/articles/1 maps to <publishedDirectory>/articles/1.md

The .md extension means that it is a Markdown file. This is how we will write out pages.

  1. For now, let's get this mapping working. To do this, we will put the following code below our for loop:
try {
const f = fs.statSync(path.join('.', publishedDirectory, p));
stream.respond({
'content-type': 'text/html',
':status': 200
});
const file = fs.createReadStream('./template/main.html');
const tStream = new LoopingStream({
dir: templateDirectory,
vars : { },
loopAmount : 2
})
file.pipe(tStream).pipe(stream);
} catch(e) {
stream.respond({
'content-type': 'text/html',
':status' : 404
});
stream.end('File Not Found! Turn Back!');
console.warn('following file requested and not found! ', p);
}

We wrap our method for finding the file (fs.statSync) inside of a try/catch block. With this, if we error out, this will usually mean that we did not find the file, and we will send a 404 message to the user. Otherwise, we will just send what we have been sending: our example template. If we now run our server, we will be greeted with the following message: File Not Found! Turn Back!. We have nothing in that directory!

Let's go ahead and create the directory, and add a file called first.md. If we add this directory and the file and rerun our server, we will still get the error message if we head to https://localhost:50000/first! We are getting this because we did not tack on the Markdown file extension when checking for the file! Let's go ahead and add this to the fs.statSync check, as follows:

const f = fs.statSync(path.join('.', publishedDirectory, `${p}.md`));

Now, when we rerun our server, we will see the normal template that we had before. If we add content to the first.md file, we will not get that file. We now need to add this addition to our templating system.

Remember at the start of the chapter we added the npm package remarkable? We will now add the Markdown renderer, remarkable, and a new keyword that our templating language will look for to render the Markdown, as follows:

  1. Let's add Remarkable as an import to our template.js file, like this:
import Remarkable from 'remarkable'
  1. We will look for the following directive to include that Markdown file into <% file <filename> %> template, like this:
const processPattern = function(pattern, templateDir, publishDir, vars=null) {
const process = pattern.toString('utf8').trim();
const LOOP = "loop";
const FIND = "from";
const FILE = "file";
const breakdown = process.split(' ');
switch(breakdown[0]) {
// previous case statements removed for readability
case FILE: {
const file = breakdown[1];
return fs.readFileSync(path.join(publishDir, file));
}
default:
return new Error("Process directory not found! " +
breakdown[0]);
}
}
  1. We will now need to add the publishDir variable to our Transform stream's possible options in the constructor, as follows:
export default class TemplateBuilder extends Transform {
#pattern = []
#publish = null
constructor(opts={}) {
super(opts);
if( opts.publishDirectory ) {
this.#publish = opts.publishDirectory;
}
}
_transform(chunk, encoding, cb) {
let location = 0;
do {
if(!this.#pattern.length && this.#pair.start === -1 ) {
// code from before
} else {
if( this.#pair.start !== -1 ) {
this.push(processPattern(chunk.slice(this.#pair.start,
this.#pair.end), this.#template, this.#publish, this.#vars)); //add publish to our processPattern function
}
}
} while( location !== -1 );
}
}
Remember: Quite a bit of code has been removed from these examples to make them easier to read. For the full examples, head on over to the book's code repository.
  1. Create a LoopingStream class that will loop and run the TemplateBuilder:
export class LoopingStream extends Transform {
#publish = null
constructor(opts={}) {
super(opts);
if( opts.publish ) {
this.#publish = opts.publish;
}
}
async _flush(cb) {
for(let i = 0; i < this.#numberOfRolls; i++) {
const passThrough = new PassThrough();
const templateBuilder = new TemplateBuilder({
templateDirectory : this.#dir,
templateVariables : this.#vars,
publishDirectory :this.#publish
});
}
cb();
}
}
  1. We will need to update our template with the following templated line:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/main.css" />
</head>
<body>
<% from html header %>
<% from html sidebar %>
<% file first.md %>
<% from html footer %>
</body>
</html>
  1. Finally, we need to pass in the publish directory to our stream from the server. We can do this with the following code addition:
const tStream = new LoopingStream({
dir: templateDirectory,
publish: publishedDirectory,
vars : {
}});

With all of this, we should get something back from the server that is not just our base template. If we added some Markdown to the file, we should just see that Markdown with our template. We now need to make sure that this Markdown gets processed. Let's head back to our transformation method and call the Remarkable method so that it processes the Markdown and gives us back HTML, as shown in the following code block:

const MarkdownRenderer = new Remarkable.Remarkable();
const processPattern = function(…) {
switch(breakdown[0]) {
case FILE: {
const file = breakdown[1];
const html =
MarkdownRenderer.render(fs.readfileSync(path.join(publishDir, file)
).toString('utf8'));
return Buffer.from(html);
}
}
}

With this change, we now have a generic Markdown parser that enables us to take our template files and send them back with our main.html file. The final change we will need to make in order to have a functioning templating system and static server is to make sure that instead of the main.html file having the exact template, it has the directive state that we want in order to put a file there and have our templating system put the file that is declared in our stream constructor. We can do this easily with the following changes:

  1. In our template.js file, we will utilize a unique variable called fileToProcess. We get this the same way we get the variables that we want to process for the sidebar.html file, through the vars that we pass through. We will utilize the file we have in the second part of the template directive if we do not have the file from the fileToProcess variable, as shown in the following code block:
case FILE: {
const file = breakdown[1];
const html =
MarkdownRenderer.render(fs.readFileSync(path.join(publishDir,
vars.fileToProcess || file)).toString('utf8'));
return Buffer.from(html);
}
  1. We will need to pass this variable from our server to the stream, like this:
const p = headers[':path'];
const tStream = new LoopingStream({
dir: templateDirectory,
publish: publishedDirectory,
vars : {
articles : [ ],
fileToProcess : `${p}.md`
},
loopAmount : 2
});
  1. The final change we will make is to change the html file, to have a new base Markdown file for pages that we do not have. This could allow us to have a base page for the root URL. We will not be implementing this, but this is a way for us to do that:
<body>
<% from html header %>
<% from html sidebar %>
<% file base.md %>
<% from html footer %>
</body>

With this change, if we now run our server, we have a fully functioning templating system with Markdown support! This is an amazing achievement! However, we will need to add two features to our server so that it will be able to handle more requests and process the same requests quickly. These features are caching and clustering.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.156.251