Improve page speeds

* Defer loading of highlight css
* Cache /static for up to 16 days
This commit is contained in:
Chris Josten 2020-06-25 10:51:26 +02:00
parent ad1724b47f
commit 503d2d1ad0
2 changed files with 195 additions and 182 deletions

View file

@ -1,181 +1,194 @@
import std.experimental.logger; import std.experimental.logger;
import std.range; import std.range;
import std.string; import std.string;
import std.stdio; import std.stdio;
import std.typecons; import std.typecons;
import vibe.d; import vibe.d;
import article; import article;
import page; import page;
import project; import project;
import watcher; import watcher;
/** /**
* Internal list of articles, pages and projects by slug. * Internal list of articles, pages and projects by slug.
*/ */
Article[string] articles; Article[string] articles;
Page[string] pages; Page[string] pages;
Project[string] projects; Project[string] projects;
/** /**
* Default ordering and list with pointers to ordered articles. * Default ordering and list with pointers to ordered articles.
* (Note: this is code which will actually be compiled and passed on!) * (Note: this is code which will actually be compiled and passed on!)
*/ */
immutable string articleSortPred = "a.firstPublished > b.firstPublished"; immutable string articleSortPred = "a.firstPublished > b.firstPublished";
Article*[] articleList; Article*[] articleList;
immutable string pageSortPred = "a.title < b.title"; immutable string pageSortPred = "a.title < b.title";
Page*[] pageList; Page*[] pageList;
immutable string projectSortPred = "a.title < b.title"; immutable string projectSortPred = "a.title < b.title";
Project*[] projectList; Project*[] projectList;
/** /**
* Output types for the content. * Output types for the content.
*/ */
enum OutputType { enum OutputType {
HTML, HTML,
MARKDOWN MARKDOWN
} }
const string MIME_MARKDOWN = "text/markdown"; immutable string MIME_MARKDOWN = "text/markdown";
immutable Duration CACHE_TIME = days(16);
/** /**
* Get's the document type for the given slug based on extension * Get's the document type for the given slug based on extension
* and returns the slug without extension and the document type. Also removes the extension from the * and returns the slug without extension and the document type. Also removes the extension from the
* slug. * slug.
*/ */
private OutputType getOutputType(ref string slug) { private OutputType getOutputType(ref string slug) {
if (slug.endsWith(".md")) { if (slug.endsWith(".md")) {
slug = chomp(slug, ".md"); slug = chomp(slug, ".md");
return OutputType.MARKDOWN; return OutputType.MARKDOWN;
} else if (slug.endsWith(".html")){ } else if (slug.endsWith(".html")){
// If explicitly asking for HTML, we'll return HTML // If explicitly asking for HTML, we'll return HTML
slug = chomp(slug, ".html"); slug = chomp(slug, ".html");
return OutputType.HTML; return OutputType.HTML;
} else { } else {
// If in the future, for any reason, we no longer use HTML // If in the future, for any reason, we no longer use HTML
// this allows to us to keep the current urls with an option // this allows to us to keep the current urls with an option
// to change the output in the future. // to change the output in the future.
return OutputType.HTML; return OutputType.HTML;
} }
} }
/** void addCachingHeader(bool publicCache = true)(ref HTTPServerResponse res) {
* Template method for fetching a single page for a subclass of page. string header = "";
* Params: static if (publicCache) {
* T = the data structure/class to be passed as template parameter. Must have a slug parameter. header ~= "public";
* templ = The template to use when rendering. } else {
* array = An associative array where the keys are slugs for parameters and the values the template parameter. header ~= "private";
* req = The server request to consume. Assumes there is an slug parameter. }
* res = The server response to write to. header ~= ", max-age=" ~ to!string(CACHE_TIME.total!"seconds");
* res.headers["Cache-Control"] = header;
*/ }
void getSingle(T, string templ)(ref T[string] array, HTTPServerRequest req, HTTPServerResponse res) {
string slug = req.params["slug"]; /**
OutputType outputType = getOutputType(slug); * Template method for fetching a single page for a subclass of page.
* Params:
enforceHTTP(slug in array, HTTPStatus.notFound, "Page not found"); * T = the data structure/class to be passed as template parameter. Must have a slug parameter.
T content = array[slug]; * templ = The template to use when rendering.
res.headers["Cache-Control"] = "public"; * array = An associative array where the keys are slugs for parameters and the values the template parameter.
switch(outputType) with (OutputType) { * req = The server request to consume. Assumes there is an slug parameter.
case MARKDOWN: * res = The server response to write to.
res.writeBody(content.contentSource, MIME_MARKDOWN); *
break; */
default: void getSingle(T, string templ)(ref T[string] array, HTTPServerRequest req, HTTPServerResponse res) {
case HTML: string slug = req.params["slug"];
res.render!(templ, content); OutputType outputType = getOutputType(slug);
break;
} enforceHTTP(slug in array, HTTPStatus.notFound, "Page not found");
} T content = array[slug];
switch(outputType) with (OutputType) {
/** case MARKDOWN:
* Generates response for /posts/:slug and /palen/:slug. res.writeBody(content.contentSource, MIME_MARKDOWN);
*/ break;
void articleGetSingle(HTTPServerRequest req, HTTPServerResponse res) { default:
getSingle!(Article, "pages/article.dt")(articles, req, res); case HTML:
} res.render!(templ, content);
break;
/** }
* Generates response for /posts and /palen }
*/
void articleGetOverview(HTTPServerRequest req, HTTPServerResponse res) { /**
res.headers["Cache-Control"] = "public"; * Generates response for /posts/:slug and /palen/:slug.
render!("pages/article-list.dt", articleList)(res); */
} void articleGetSingle(HTTPServerRequest req, HTTPServerResponse res) {
getSingle!(Article, "pages/article.dt")(articles, req, res);
/** }
* Generates response for /projects and /projecten
*/ /**
void projectGetOverview(HTTPServerRequest req, HTTPServerResponse res) { * Generates response for /posts and /palen
res.headers["Cache-Control"] = "public"; */
render!("pages/project-list.dt", projectList)(res); void articleGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
} addCachingHeader(res);
render!("pages/article-list.dt", articleList)(res);
/** }
* Generate response for a page
*/ /**
void pageGet(HTTPServerRequest req, HTTPServerResponse res) { * Generates response for /projects and /projecten
// If no slug is supplied, it will be adjusted to "index" */
if (("slug" in req.params) is null) { void projectGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
req.params.addField("slug", "index"); addCachingHeader(res);
} render!("pages/project-list.dt", projectList)(res);
getSingle!(Page, "pages/page.dt")(pages, req, res); }
}
/**
/** * Generate response for a page
* Generate response for a project page */
*/ void pageGet(HTTPServerRequest req, HTTPServerResponse res) {
void projectGet(HTTPServerRequest req, HTTPServerResponse res) { addCachingHeader(res);
res.headers["Cache-Control"] = "public"; // If no slug is supplied, it will be adjusted to "index"
getSingle!(Project, "pages/project.dt")(projects, req, res); if (("slug" in req.params) is null) {
} req.params.addField("slug", "index");
}
/** getSingle!(Page, "pages/page.dt")(pages, req, res);
* Generates response whenever an error occurs. }
*/
@safe /**
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) { * Generate response for a project page
render!("pages/error.dt", error)(res); */
} void projectGet(HTTPServerRequest req, HTTPServerResponse res) {
res.headers["Cache-Control"] = "public";
void main() { getSingle!(Project, "pages/project.dt")(projects, req, res);
HTTPServerSettings settings = new HTTPServerSettings; }
settings.bindAddresses = ["0.0.0.0"];
settings.port = 3465; /**
settings.serverString = "zeg ik lekker niet"; * Generates response whenever an error occurs.
settings.errorPageHandler = toDelegate(&errorPage); */
settings.keepAliveTimeout = dur!"seconds"(60); @safe
debug { void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
settings.accessLogToConsole = true; render!("pages/error.dt", error)(res);
} }
URLRouter router = new URLRouter; void main() {
router.get("/posts/:slug", &articleGetSingle); HTTPServerSettings settings = new HTTPServerSettings;
router.get("/palen/:slug", &articleGetSingle); settings.bindAddresses = ["0.0.0.0"];
router.get("/posts/", &articleGetOverview); settings.port = 3465;
router.get("/palen/", &articleGetOverview); settings.serverString = "zeg ik lekker niet";
router.get("/projects/", &projectGetOverview); settings.errorPageHandler = toDelegate(&errorPage);
router.get("/projects/:slug", &projectGet); settings.keepAliveTimeout = dur!"seconds"(60);
router.get("/projecten/:slug", &projectGet); debug {
router.get("/static/*", serveStaticFiles("./public/")); settings.accessLogToConsole = true;
router.get("/:slug", &pageGet); }
router.get("/", &pageGet); HTTPFileServerSettings fSettings = new HTTPFileServerSettings;
fSettings.maxAge = days(16);
listenHTTP(settings, router);
URLRouter router = new URLRouter;
// Start indexing pages. router.get("/posts/:slug", &articleGetSingle);
runTask({ router.get("/palen/:slug", &articleGetSingle);
initPages!(Page, pageSortPred)(pages, pageList, "pages"); router.get("/posts/", &articleGetOverview);
}); router.get("/palen/", &articleGetOverview);
runTask({ router.get("/projects/", &projectGetOverview);
initPages!(Article, articleSortPred)(articles, articleList, "articles"); router.get("/projects/:slug", &projectGet);
}); router.get("/projecten/:slug", &projectGet);
runTask({ router.get("/static/*", serveStaticFiles("./public/", fSettings));
initPages!(Project, projectSortPred)(projects, projectList, "projects"); router.get("/:slug", &pageGet);
}); router.get("/", &pageGet);
runApplication();
} listenHTTP(settings, router);
// Start indexing pages.
runTask({
initPages!(Page, pageSortPred)(pages, pageList, "pages");
});
runTask({
initPages!(Article, articleSortPred)(articles, articleList, "articles");
});
runTask({
initPages!(Project, projectSortPred)(projects, projectList, "projects");
});
runApplication();
}

View file

@ -4,7 +4,7 @@ html(prefix="og: http://ogp.me/ns#")
head head
meta(name="viewport", content="width=device-width; initial-scale=1") meta(name="viewport", content="width=device-width; initial-scale=1")
link(rel="stylesheet", href="/static/style/base.css") link(rel="stylesheet", href="/static/style/base.css")
link(rel="stylesheet", href="/static/style/highlight.css") link(rel="stylesheet", href="/static/style/highlight.css", defer)
link(rel="alternate stylesheet", href="/static/style/old.css", title="1999") link(rel="alternate stylesheet", href="/static/style/old.css", title="1999")
link(rel="shortcut icon", href="/static/img/logo.png") link(rel="shortcut icon", href="/static/img/logo.png")
link(rel="apple-touch-icon", href="/static/img/logo.png") link(rel="apple-touch-icon", href="/static/img/logo.png")