Refining engine targeting

This commit is contained in:
Jocelyn Badgley (Twipped) 2020-03-07 18:04:37 -08:00
parent 4e5c14123f
commit 67b168dba1
7 changed files with 188 additions and 125 deletions

View File

@ -4,11 +4,11 @@ const path = require('path');
const fs = require('fs-extra');
const log = require('fancy-log');
const { minify } = require('html-minifier-terser');
const { resolve, readFile, ENGINE } = require('./resolve');
const { resolve, readFile, ENGINE, TYPE } = require('./resolve');
const handlebars = require('handlebars');
const Handlebars = require('handlebars');
const HandlebarsKit = require('hbs-kit');
HandlebarsKit.load(handlebars);
HandlebarsKit.load(Handlebars);
const slugs = require('slugify');
const slugify = (s) => slugs(s, { remove: /[*+~.,()'"!?:@/\\]/g }).toLowerCase();
@ -44,7 +44,7 @@ const markdownEngines = {
function markdown (mode, input, env) {
input = input.replace(/\{!\{([\s\S]*?)\}!\}/mg, (match, contents) => {
try {
const result = handlebars.compile(contents)(env);
const result = Handlebars.compile(contents)(env);
return 'æææ' + result + 'æææ';
} catch (e) {
log.error(e);
@ -66,6 +66,11 @@ function markdown (mode, input, env) {
return input ? markdownEngines[mode].render(input, env) : '';
}
function handlebars (input, env) {
const template = Handlebars.compile(input);
return template(env);
}
function stripIndent (input) {
const match = input.match(/^[^\S\n]*(?=\S)/gm);
const indent = match && Math.min(...match.map((el) => el.length));
@ -88,47 +93,45 @@ const MINIFY_CONFIG = {
const HANDLEBARS_PARTIALS = {
layout: 'templates/layout.hbs',
page: 'templates/page.hbs',
post: 'templates/post.hbs',
};
module.exports = exports = async function (prod) {
const templates = {};
for (const [ name, file ] of Object.entries(HANDLEBARS_PARTIALS)) {
try {
const contents = await readFile(file);
const template = handlebars.compile(contents.toString('utf8'));
handlebars.registerPartial(name, template);
const template = Handlebars.compile(contents.toString('utf8'));
templates[name] = template;
Handlebars.registerPartial(name, template);
} catch (e) {
log.error('Could not execute load partial ' + file, e);
}
}
const pageTemplateRaw = await readFile('templates/page.hbs');
if (!pageTemplateRaw) throw new Error('Post template was empty?');
try {
var pageTemplate = handlebars.compile(pageTemplateRaw.toString('utf8'));
} catch (e) {
log.error('Crash while loading page template', e);
}
const revManifest = prod && await fs.readJson(resolve('rev-manifest.json')).catch(() => {}).then((r) => r || {});
const helpers = new Injectables(prod, revManifest);
handlebars.registerHelper('import', helpers.import());
handlebars.registerHelper('markdown', helpers.markdown());
handlebars.registerHelper('icon', helpers.icon());
handlebars.registerHelper('prod', helpers.production());
handlebars.registerHelper('rev', helpers.rev());
Handlebars.registerHelper('import', helpers.import());
Handlebars.registerHelper('markdown', helpers.markdown());
Handlebars.registerHelper('icon', helpers.icon());
Handlebars.registerHelper('prod', helpers.production());
Handlebars.registerHelper('rev', helpers.rev());
const shrink = (input) => (prod ? minify(input, MINIFY_CONFIG) : input);
const result = {
[ENGINE.HANDLEBARS]: (source, env) => {
const template = handlebars.compile(source);
return shrink(template(env));
},
[ENGINE.MARKDOWN]: (source, env) => shrink(pageTemplate({ ...env, contents: markdown('full', source, env) })),
[ENGINE.OTHER]: (source) => shrink(source),
MARKDOWN_CONTENT: (source, env) => markdown('full', source, env),
MARKDOWN_PREVIEW: (source, env) => markdown('preview', source, env),
[TYPE.HANDLEBARS]: handlebars,
[TYPE.MARKDOWN]: (source, env) => markdown('full', source, env),
[TYPE.OTHER]: (source) => source,
[ENGINE.PAGE]: (source, env) => shrink(templates.page({ ...env, contents: markdown('full', source, env) })),
[ENGINE.POST]: (source, env) => shrink(templates.post({ ...env, contents: markdown('full', source, env) })),
[ENGINE.HTML]: (source) => shrink(source),
[ENGINE.OTHER]: (source) => source,
preview: (source, env) => markdown('preview', source, env),
};
return result;
@ -206,7 +209,7 @@ class Injectables {
contents = markdown('full', contents, data);
return new handlebars.SafeString(contents);
return new Handlebars.SafeString(contents);
};
}
@ -215,14 +218,14 @@ class Injectables {
return function (tpath, ...args) {
const { hash, data } = args.pop();
const value = args.shift();
const context = handlebars.createFrame(value || data.root);
const context = Handlebars.createFrame(value || data.root);
Object.assign(context, hash || {});
tpath = self._parsePath(tpath, data.root.local, 'hbs');
try {
const contents = self._template(tpath, handlebars.compile)(context);
return new handlebars.SafeString(contents);
const contents = self._template(tpath, Handlebars.compile)(context);
return new Handlebars.SafeString(contents);
} catch (e) {
log.error('Could not execute import template ' + tpath, e);
return '';
@ -238,10 +241,10 @@ class Injectables {
try {
const contents = self._template(tpath, (s) =>
handlebars.compile(`<span class="svg-icon" {{#if size}}style="width:{{size}}px;height:{{size}}px"{{/if}}>${s}</span>`),
Handlebars.compile(`<span class="svg-icon" {{#if size}}style="width:{{size}}px;height:{{size}}px"{{/if}}>${s}</span>`),
)({ size: hash && hash.size });
return new handlebars.SafeString(contents);
return new Handlebars.SafeString(contents);
} catch (e) {
log.error('Could not execute import template ' + tpath, e);
return '';

View File

@ -19,22 +19,16 @@ module.exports = exports = class File {
}
const file = path.parse(filepath);
let { base: basename, name } = file;
this.preprocessed = false;
if (name[0] === '_') {
this.preprocessed = true;
file.name = name = name.slice(1);
file.basename = basename = basename.slice(1);
}
this._basename();
this.kind = kind(filepath);
this.type = type(filepath);
this.input = filepath; // public/file.ext
this.cwd = file.dir;
this.ext = this.preprocessed ? file.ext : normalizedExt(file.ext);
this.name = name; // index, fileA, fileB
this.basename = basename; // index.ext, fileA.ext, fileB.ext
this.name = file.name; // index, fileA, fileB
this.basename = file.basename; // index.ext, fileA.ext, fileB.ext
const dir = this._dir(file.dir);
if (dir) {
@ -60,6 +54,15 @@ module.exports = exports = class File {
];
}
_basename (file) {
this.preprocessed = false;
if (file.name[0] === '_') {
this.preprocessed = true;
file.name = file.name.slice(1);
file.basename = file.basename.slice(1);
}
}
_dir (dir) {
dir = dir.split('/');
if (dir[0] === 'public') dir.shift();

View File

@ -8,6 +8,7 @@ const Promise = require('bluebird');
const fs = require('fs-extra');
const { sortBy } = require('lodash');
const getEngines = require('./engines');
const primeTweets = require('./page-tweets');
const pageWriter = require('./page-writer');
const evaluate = require('./evaluate');
@ -27,9 +28,10 @@ exports.everything = function (prod = false) {
async function fn () {
// load a directory scan of the public and post folders
const [ PublicFiles, PostFiles ] = await Promise.all([
const [ PublicFiles, PostFiles, engines ] = await Promise.all([
loadPublicFiles(),
loadPostFiles(),
getEngines(prod),
]);
// load data for all the files in that folder
@ -71,8 +73,8 @@ exports.everything = function (prod = false) {
await evaluate(tasks.flat(), cache);
await cache.save();
posts = await pageWriter(pages, posts, prod);
await writeIndex('dist/tweets/index.json', posts.filter(Boolean), true);
const postIndex = await pageWriter(engines, pages, posts, prod);
await fs.writeFile(resolve('dist/tweets/index.json'), prod ? JSON.stringify(postIndex) : JSON.stringify(postIndex, null, 2));
}
fn.displayName = prod ? 'buildForProd' : 'build';

View File

@ -1,22 +1,62 @@
const path = require('path');
const Promise = require('bluebird');
const fs = require('fs-extra');
const getEngines = require('./engines');
const { resolve, ROOT, ENGINE } = require('./resolve');
const { map, uniq } = require('lodash');
const { resolve, ROOT } = require('./resolve');
const { siteInfo } = require(resolve('package.json'));
module.exports = exports = async function writePageContent (pages, posts, prod) {
const engines = await getEngines(prod);
const postIndex = await processPages(engines, posts, null, prod);
await processPages(engines, pages, posts, prod);
module.exports = exports = async function writePageContent (engines, pages, posts, prod) {
const postIndex = index(posts, engines);
await processPages(engines, [ ...posts, ...pages ], postIndex, prod);
return postIndex;
};
function processPages (engines, pages, posts, prod) {
return Promise.map(pages, async (page) => {
// page = new Page(page);
function index (posts, engines) {
posts = posts.filter((p) => !p.draft);
var data = {
siblings(posts);
// fill in post content
posts.forEach((p) => { p.content = engines[p.type](p.source, pageState(p)); });
const reducedPosts = posts.map(pageJSON);
const authors = uniq(map(reducedPosts, 'author').flat()).sort((a, b) => (a.toUpperCase() > b.toUpperCase() ? 1 : -1));
const tagMap = reducedPosts.reduce((o, p) => Object.assign(o, p.tags), {});
const tags = Object.keys(tagMap).sort().reduce((result, tagslug) => {
result[tagslug] = tagMap[tagslug];
return result;
}, {});
return {
posts: reducedPosts,
authors,
tags,
latest: posts[0],
};
}
function siblings (posts) {
let first, prev, next, last;
for (let i = 0; i < posts.length; i++) {
const post = posts[i];
first = i > 0 && posts[0];
prev = posts[i - 1] || false;
next = posts[i + 1] || false;
last = i < posts.length - 1 && posts[posts.length - 1];
post.siblings = {
first: first && first.url,
prev: prev && prev.url,
next: next && next.url,
last: last && last.url,
};
}
}
function pageState (page, posts) {
return {
...page,
meta: { ...page.meta, ...page },
page: {
@ -34,25 +74,32 @@ function processPages (engines, pages, posts, prod) {
},
posts,
};
}
const json = {
url: page.url,
fullurl: page.fullurl,
title: page.meta.title,
subtitle: page.meta.subtitle,
description: page.meta.description,
date: page.dateCreated,
titlecard: page.titlecard,
tags: page.meta.tags,
author: page.meta.author,
function pageJSON (post) {
return {
url: post.url,
fullurl: post.fullurl,
json: '/' + post.json,
title: post.meta.title,
subtitle: post.meta.subtitle,
description: post.meta.description,
date: post.dateCreated,
titlecard: post.titlecard,
tags: post.meta.tags,
author: post.meta.author,
siblings: post.sibling,
};
}
function processPages (engines, pages, posts, prod) {
return Promise.map(pages, async (page) => {
const state = pageState(page, posts);
const json = pageJSON(page);
const html = String(engines[page.engine](page.source, state));
const html = String(engines[page.engine](data.source, data));
if (page.engine === ENGINE.MARKDOWN) {
json.preview = String(engines.MARKDOWN_PREVIEW(data.source, data));
page.content = String(engines.MARKDOWN_CONTENT(data.source, data));
json.content = page.content;
}
const output = resolve('dist', page.out);
await fs.ensureDir(path.dirname(output));
@ -62,16 +109,5 @@ function processPages (engines, pages, posts, prod) {
prod ? JSON.stringify(json) : JSON.stringify(json, null, 2),
)),
]);
return !page.draft && {
url: page.url,
json: page.json,
title: page.meta.title,
subtitle: page.meta.subtitle,
description: page.meta.description,
date: page.dateCreated,
tags: page.meta.tags,
author: page.meta.author,
};
});
}

View File

@ -6,7 +6,7 @@ const log = require('fancy-log');
const File = require('./file');
const actions = require('./actions');
const { URL } = require('url');
const { resolve, readFile, isCleanUrl, ENGINE } = require('./resolve');
const { resolve, readFile, isCleanUrl, TYPE, ENGINE } = require('./resolve');
const { isObject, isString } = require('./lib/util');
const pkg = require(resolve('package.json'));
@ -29,9 +29,21 @@ module.exports = exports = class Page extends File {
'dateModified',
'classes',
'flags',
'siblings',
);
this.engine = ENGINE[this.type] || ENGINE.COPY;
this.engine = this._engine();
}
_engine () {
switch (this.type) {
case TYPE.HANDLEBARS:
return TYPE.HANDLEBARS;
case TYPE.MARKDOWN:
return ENGINE.PAGE;
default:
return ENGINE.OTHER;
}
}
_out () {

View File

@ -1,7 +1,7 @@
const path = require('path');
const { without } = require('lodash');
const { resolve, isCleanUrl } = require('./resolve');
const { resolve, isCleanUrl, TYPE, ENGINE } = require('./resolve');
const Page = require('./page');
const slugs = require('slugify');
const slugify = (s) => slugs(s, { remove: /[*+~.,()'"!?:@/\\]/g }).toLowerCase();
@ -17,6 +17,17 @@ function arrayify (input) {
module.exports = exports = class Post extends Page {
_engine () {
switch (this.type) {
case TYPE.HANDLEBARS:
return TYPE.HANDLEBARS;
case TYPE.MARKDOWN:
return ENGINE.POST;
default:
return ENGINE.OTHER;
}
}
_dir (dir) {
// if the file name matches the postmatch pattern, then this needs to be /p/ file
const match = this.name.match(postmatch);
@ -61,6 +72,8 @@ module.exports = exports = class Post extends Page {
_parse (...args) {
super._parse(...args);
if (!this.titlecard) this.titlecard = '/tweets/titlecard.png';
this.meta.tags = (this.meta.tags || []).reduce((result, tag) => {
result[slugify(tag)] = tag;
return result;

View File

@ -78,13 +78,13 @@ exports.isCleanUrl = is(HBS, MD);
const TYPE = exports.TYPE = {
IMAGE: 'IMAGE',
VIDEO: 'VIDEO',
HANDLEBARS: 'HANDLEBARS',
MARKDOWN: 'MARKDOWN',
SCRIPT: 'SCRIPT',
STYLE: 'STYLE',
OTHER: 'OTHER',
IMAGE: 'TYPE_IMAGE',
VIDEO: 'TYPE_VIDEO',
HANDLEBARS: 'TYPE_HANDLEBARS',
MARKDOWN: 'TYPE_MARKDOWN',
SCRIPT: 'TYPE_SCRIPT',
STYLE: 'TYPE_STYLE',
OTHER: 'TYPE_OTHER',
};
exports.type = dictMatch({
@ -99,11 +99,11 @@ exports.type = dictMatch({
const KIND = exports.KIND = {
PAGE: 'PAGE',
POST: 'POST',
ASSET: 'ASSET',
ARTIFACT: 'ARTIFACT',
OTHER: 'OTHER',
PAGE: 'KIND_PAGE',
POST: 'KIND_POST',
ASSET: 'KIND_ASSET',
ARTIFACT: 'KIND_ARTIFACT',
OTHER: 'KIND_OTHER',
};
exports.kind = dictMatch({
@ -114,19 +114,13 @@ exports.kind = dictMatch({
const ENGINE = exports.ENGINE = {
HANDLEBARS: 'HANDLEBARS',
MARKDOWN: 'MARKDOWN',
COPY: 'COPY',
exports.ENGINE = {
HTML: 'ENGINE_HTML',
PAGE: 'ENGINE_PAGE',
POST: 'ENGINE_POST',
OTHER: 'ENGINE_OTHER',
};
exports.engine = dictMatch({
[ENGINE.HANDLEBARS]: is(XML, HBS, HTML),
[ENGINE.MARKDOWN]: is(MD),
}, ENGINE.COPY);
exports.readFile = function readFile (fpath) {
fpath = exports.resolve(fpath);
return fs.readFile(fpath).catch((err) => {