Building Templates

In notebook:
FrontEndMasters Organising Javascript functionality
Created at:
2016-10-10
Updated:
2016-10-10
Tags:
libraries React JavaScript
He has another utility called build templates (build-templates.js).

It will process through all the templates that are in the /templates directory. He uses Grips templates. He uses this instead of Gulp or Grunt. So you don't need to learn another tool. 
Loops through the files in the directory and compiles them so that they are runnable JS files that can be run in the server or browser. 

Then explains tmpls-wrapper.js file. First the UMD wrapper to share modules. 
The UMD boilerplate is not very nice, but you can hide that with the code editor (code folding).
This replaces the need for browserify. With UMD you can run your file in the browser, node, or require and it doesn't need to be transformed.  He just copies the UMD boilerplate code in each file. 

The compiled templates (executable code) get put into this file (where the ​/*TEMPLATES*/​ comments are). 

build-templates.js sets up a watch (like in Grunt or Gulp). Every time a templates files is change, it will rebuild the templates. 
  loadTemplates(path.join(__dirname,"web","js","Tmpls.js"));

// NOTE: here he waatches for the Tmpls.js file to change
// watch for updated templates to reload
watch.createMonitor(
	/*root=*/path.join(__dirname,"web","js"),
	/*options=*/{
		ignoreDirectoryPattern: true,
		filter: function filter(file) {
		  // NOTE: don't reload the server
		  // when the templates change
		  // hot reloading the templates files
			// only monitor the template-bundle "Tmpls.js"
			return /Tmpls\.js$/.test(file);
		}
	},
Illustrates how this whole templates watching and reloading works. 
starts up $ ./build-templates.js​ --watch​​ 
Then modifies index_grips.html. 
Then everything is updates (server, compiled templates files). 

Shows the repo for GRIPS templating engine. He has a playground site to see how it's compiled.
One of the points of Grips is that you get much better error handling than with other templating engines. You get much richer error messages, with stack traces, run-time error checking etc.  

Grips is a partials based templating engine. Files can extend other files. You can do templates inheritance. Many pages that inherit from master but override partials. 

Grips has a shorthand form for all the syntax. 

The point is that you can use any tempting engine. The same concept his presenting here can be run with any templating engine not just Grips. 
You need a watch and load into the server.    

Look at the ​loadPage​ route (more down in server.js:
​View.getPageHTML(url)​ 
it invokes grips and loads the result. ​loadPage​ could work just as well with any other templating engine. 
  routes.push(
	// a recognized full-page request?
	function loadPage(req,res) {
		var url;

		if (
			req.method === "GET" &&
			(url = Pages.recognize(req.url))
		) {
			return ASQ(function ASQ(done){
					View.getPageHTML(url)
					.val(function pageHTML(url,html) {
						res.writeHead(200,{ "Content-type": "text/html; charset=UTF-8" });
						res.end(html);
						done(true);
					})
					.or(done.fail);
				});
		}
	}
);
Code continues here:
  	/*handler=*/function handler(monitor) {
		monitor.on("created",loadTemplates);
		monitor.on("changed",loadTemplates);
	}
);

// setup HTTP routes
routes.push(
	// always set server name
	function serverName(req,res) {
		res.setHeader("Server",secret.SERVER_NAME);
	}
);


routes.push(
	// ensure security headers for all responses
	function securityHeaders(req,res) {
		// From: https://developer.mozilla.org/en-US/docs/Security/CSP/Introducing_Content_Security_Policy
		res.setHeader("Content-Security-Policy","default-src 'self'; script-src 'self' 'unsafe-eval' localhost:8050 ajax.googleapis.com ssl.google-analytics.com; connect-src 'self' www.random.org; style-src 'self' 'unsafe-inline' localhost:8050");

		// From: https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
		res.setHeader("Strict-Transport-Security","max-age=" + 1E9 + "; includeSubdomains");
	}
);

routes.push(
	// await full request
	function fullRequest(req,res) {
		req.body = "";
		return ASQ.react(function listener(next){
			req.addListener("data",function(chunk){
				req.body += chunk;
			});
			req.addListener("end",next);
			req.resume();
		});
	}
);

routes.push(
	// favicon
	function favicon(req,res) {
		try {
			if (req.method === "GET" && req.url === "/favicon.ico") {
				fs.statSync(path.join(__dirname,"web","favicon.ico"));
			}
			return;
		}
		catch (err) {}

		// empty favicon.ico response
		res.writeHead(204,{
			"Content-Type": "image/x-icon",
			"Cache-Control": "public, max-age: 604800"
		});
		res.end();
		return true;
	}
)

routes.push(
	// static file request?
	function staticResources(req,res) {
		if (req.method === "GET" &&
			/^\/(?:js\/(?=.+)|css\/(?=.+)|images\/(?=.+)|robots\.txt\b|humans\.txt\b|favicon\.ico\b)/
			.test(req.url)
		) {
			req.url = "/web" + req.url;
			static_files.serve(req,res);
			return true;
		}
	}
);

routes.push(
	// a recognized full-page request?
	function loadPage(req,res) {
		var url;

		if (
			req.method === "GET" &&
			(url = Pages.recognize(req.url))
		) {
			return ASQ(function ASQ(done){
					View.getPageHTML(url)
					.val(function pageHTML(url,html) {
						res.writeHead(200,{ "Content-type": "text/html; charset=UTF-8" });
						res.end(html);
						done(true);
					})
					.or(done.fail);
				});
		}
	}
);

routes.push(
	// generate-phrase POST fallback?
	function generatePhraseFallback(req,res) {
		if (req.method === "POST" && req.url == "/") {
			var data = url_parser.parse("/?" + req.body,true).query;

			data.wordCount = data.level;
			data.localRandom = data.localized;

			return ASQ(function ASQ(done){
					API.generate(data)
					.seq(function result(phrase) {
						return View.getPageHTML("/",{
								phrase_results: [ phrase ]
							});
					})
					.val(function pageHTML(url,html){
						res.writeHead(200,{
							"Content-type": "text/html; charset=UTF-8",
							"Cache-Control": "no-store, no-cache, must-revalidate, post-check=0, pre-check=0",
							"Pragma": "no-cache",
							"Expires": "Thu, 01 Dec 1994 16:00:00 GMT"
						});
						res.end(html);
						done(true);
					})
					.or(done.fail);
				});
		}
	}
);

routes.push(
	// api request?
	function api(req,res) {
		if (
			req.method === "GET" &&
			/^\/api\/generate/.test(req.url)
		) {
			var data = url_parser.parse(req.url,true).query;

			return ASQ(function ASQ(done){
					API.generate(data)
					.val(function result(phrase) {
						res.writeHead(200,{
							"Content-type": "application/json; charset=UTF-8",
							"Cache-Control": "no-store, no-cache, must-revalidate, post-check=0, pre-check=0",
							"Pragma": "no-cache",
							"Expires": "Thu, 01 Dec 1994 16:00:00 GMT"
						});
						res.end(JSON.stringify({
							phrase: phrase
						}));
						done(true);
					})
					.or(done.fail);
				});
		}
	}
);

routes.push(
	// default route
	function defaultRoute(req,res) {
		res.writeHead(404);
		res.end();
	}
);


// server request handling
ASQ.react(function listen(trigger){
	httpserv.on("request",trigger);
})
.runner(router)
.or(responseError);

// start server
httpserv.listen(secret.SERVER_PORT,secret.SERVER_ADDR);


// *****************************

function *router(token) {
	var req = token.messages[0], res = token.messages[1], route, error;

	for (route of routes) {
		try {
			route = route(req,res);
			if (ASQ.isSequence(route)) {
				// wait to resolve the route
				route = yield route;
			}
			if (route === true) {
				break;
			}
		}
		catch (err) {
			error = err;
			break;
		}
	}

	// response error?
	if (error) {
		throw {
			req: req,
			res: res,
			reason: error
		};
	}
}

function logMessage(msg,returnVal) {
	var d = new Date();
	msg = "[" + d.toLocaleString() + "] " + msg;
	if (!!returnVal) {
		return msg;
	}
	else {
		console.log(msg);
	}
}

function NOTICE(location,msg,returnVal) {
	return logMessage("NOTICE(" + location + "): " + msg,!!returnVal);
}

function ERROR(location,msg,returnVal) {
	return logMessage("ERROR(" + location + "): " + msg,!!returnVal);
}

function responseError(respErr) {
	try {
		if (respErr.req && respErr.res) {
			if (respErr.req.headers &&
				respErr.req.headers["accept"] === "application/json"
			) {
				respErr.reason = JSON.stringify({
					error: respErr.reason
				});
			}
			respErr.res.writeHead(500);
			respErr.res.end(respErr.reason.toString());
			return true;
		}
	} catch(e) {}

	ERROR("responseError",
		respErr ? ((respErr.stack + "") || respErr.toString()) : "Unknown response error"
	);
}

function loadTemplates(file) {
	var cache_entry;

	if (/Tmpls\.js$/.test(file)) {
		cache_entry = require.resolve(file);

		// templates already loaded into cache?
		if (require.cache[cache_entry]) {
			NOTICE("templates","Reloaded.");

			// clear the templates-module from the require cache
			delete require.cache[cache_entry];

			// clear out the grips collection cache
			Object.keys(grips.collections).forEach(function forEach(key){
				delete grips.collections[key];
			});
		}
		else {
			NOTICE("templates","Loaded.");
		}

		// load the templates-module and initialize it
		global.Tmpls = Tmpls = require(file);
		Events.emit("Tmpls");
	}
}