@taprootio/rollup-plugin-taproot
Version:
Simple static site generation
298 lines (289 loc) • 13.8 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var readdir = require('recursive-readdir');
var fs = require('fs');
var path = require('path');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var readdir__default = /*#__PURE__*/_interopDefaultLegacy(readdir);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
class cloneable {
static deepCopy(source) {
return Array.isArray(source)
? source.map((item) => this.deepCopy(item))
: source instanceof Date
? new Date(source.getTime())
: source && typeof source === "object"
? Object.getOwnPropertyNames(source).reduce((o, prop) => {
Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
o[prop] = this.deepCopy(source[prop]);
return o;
}, Object.create(Object.getPrototypeOf(source)))
: source;
}
}
// NOTE: Flash of unstyled content can happen when resources,
// such as fonts and other styles, are loaded after
// initial render. This waits for things to load before
// showing the page. Template body needs an initial
// class of "loading" for it to work.
const FUCFix = [
`<style>
body {
transition: opacity ease-in 500ms;
}
body.loading {
opacity: 0;
}
body.loading.loaded {
opacity: 100;
}
</style>`,
`<script type="text/javascript">
window.onload = () => {
document.body.classList.add("loaded")
}
</script>`,
];
const BuildHead = (data) => {
var _a, _b, _c, _d, _e;
if (!data.Canonical) {
throw "No canonical specified";
}
// Clone it so any changes made here do not bubble up.
data = cloneable.deepCopy(data);
console.log(JSON.stringify(data));
data.Canonical = (_a = data.Canonical) === null || _a === void 0 ? void 0 : _a.replace("index.html", "");
data.CharSet = (_b = data.CharSet) !== null && _b !== void 0 ? _b : "UTF-8";
data.Locale = (_c = data.Locale) !== null && _c !== void 0 ? _c : "en_US";
data.PageType = (_e = (_d = data.PageType) === null || _d === void 0 ? void 0 : _d.toLowerCase()) !== null && _e !== void 0 ? _e : "article";
if (!data.Title) {
throw `No title for ${data.Canonical}`;
}
if (!data.Description) {
throw `No description for ${data.Canonical}`;
}
const validPageTypes = ["article", "website"];
if (validPageTypes.indexOf(data.PageType) < 0) {
throw `Invalid page type for ${data.Canonical}`;
}
if (data.PageType == "article" && !data.Author) {
throw `Article must have an author for ${data.Canonical}`;
}
const tags = [
`<meta charset="${data.CharSet}" />`,
`<meta name="viewport" content="width=device-width, initial-scale=1" />`,
];
if (!data.NoRobots) {
tags.push(`<meta name="robots" content="index, follow, max-image-preview:large" />`);
}
tags.push(...FUCFix);
tags.push(`<title>${data.Title} → ${data.Publisher.Name}</title>`, `<meta name="description" content="${data.Description}" />`, `<link rel="canonical" href="${data.Canonical}" />`, `<meta property="og:locale" content="${data.Locale}" />`, `<meta property="og:type" content="${data.PageType}" />`, `<meta property="og:title" content="${data.Title}" />`, `<meta property="og:description" content="${data.Description}" />`, `<meta property="og:url" content="${data.Canonical}" />`, `<meta property="og:site_name" content="${data.Publisher.Name}" />`, `<meta property="article:publisher" content="${data.Publisher.FacebookPage}" />`, `<meta property="og:image" content="${data.SocialImage.Url}" />`, `<meta property="og:image:width" content="${data.SocialImage.Width}" />`, `<meta property="og:image:height" content="${data.SocialImage.Height}" />`, `<meta property="og:image:type" content="${data.SocialImage.MediaType}" />`, // e.g. "image/png"
`<meta name="twitter:card" content="summary_large_image" />`, `<meta name="twitter:site" content="${data.Publisher.TwitterHandle}" />`);
if (data.DatePublished) {
tags.push(`<meta property="article:published_time" content="${data.DatePublished}" />`);
}
if (data.DateModified) {
tags.push(`<meta property="article:modified_time" content="${data.DateModified}" />`);
}
if (data.Author) {
tags.push(`<meta property="article:author" content="${data.Author.FacebookPage}" />`, `<meta name="author" content="${data.Author.Name}" />`, `<meta name="twitter:creator" content="${data.Author.TwitterHandle}" />`, `<meta name="twitter:label1" content="Written by" />`, `<meta name="twitter:data1" content="${data.Author.Name}" />`);
}
return tags.join("\n");
};
const determinePath = (fileName, canonical) => {
let emitPath;
if (canonical) {
emitPath = canonical;
}
else {
emitPath = fileName.split(".")[0];
}
if (emitPath.endsWith("index")) {
emitPath = emitPath.substring(0, emitPath.length - 5);
}
if (emitPath.endsWith("/")) {
emitPath = emitPath.substring(0, emitPath.length - 1);
}
return emitPath;
};
const buildTagPath = (tagRoot, tag) => `${tagRoot !== null && tagRoot !== void 0 ? tagRoot : "/tag/"}${encodeURI(tag.toLowerCase().replace(" ", "-"))}`;
const getElements = (files) => {
// https://github.com/rollup/plugins/blob/master/packages/html/src/index.ts
// TODO: Set these from options...
const publicPath = "/";
// TODO: Allow specifying attributes
const attrs = "";
const scripts = (files.js || [])
.map(({ fileName }) => {
// const attrs = makeHtmlAttributes(attributes.script);
return `<script src="${publicPath}${fileName}"${attrs} type="module"></script>`;
})
.join("\n");
const links = (files.css || [])
.map(({ fileName }) => {
// const attrs = makeHtmlAttributes(attributes.link);
return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
})
.join("\n");
return {
scripts,
links,
};
};
const getFiles = (bundle) => {
const files = Object.values(bundle).filter((file) => file.type === "chunk" || file.type === "asset");
const result = {};
for (const file of files) {
const { fileName } = file;
const extension = path__default["default"].extname(fileName).substring(1);
result[extension] = (result[extension] || []).concat(file);
}
return result;
};
const Taproot = (options) => {
const templates = new Map();
let pagesToRender;
return {
name: "rollup-plugin-taproot",
async buildStart(_options) {
var _a, _b, _c, _d;
pagesToRender = new Map();
const templateFiles = await readdir__default["default"](options.TemplatesPath);
for (const templateFile of templateFiles) {
for (const templateParser of options.TemplateParsers) {
if (!templateParser.FileMatcher.test(templateFile)) {
continue;
}
const source = fs__default["default"].readFileSync(templateFile).toString();
const templateKey = path__default["default"].basename(templateFile).split(".")[0];
const compiled = templateParser.CompileTemplate(source);
templates.set(templateKey, compiled);
}
}
console.log("Reading pages...");
const pageFiles = await readdir__default["default"](options.PagesPath);
for (const pageFile of pageFiles) {
for (const pageRenderer of options.PageRenderers) {
if (!pageRenderer.FileMatcher.test(pageFile)) {
continue;
}
const source = fs__default["default"].readFileSync(pageFile).toString();
const fileName = pageFile.substring(pageFile.indexOf(`${options.PagesPath.substring(1)}/`) +
options.PagesPath.length);
// Clone it so any changes made here do not affect cached
// data in the renderer.
const rendered = cloneable.deepCopy(pageRenderer.Render(source));
rendered.Data.Canonical = determinePath(fileName, (_a = rendered.Data.Canonical) !== null && _a !== void 0 ? _a : "");
pagesToRender.set(fileName, rendered);
}
}
const tags = new Map();
for (const page of pagesToRender.values()) {
for (const tag of (_b = page.Data.Tags) !== null && _b !== void 0 ? _b : []) {
if (!tags.has(tag)) {
tags.set(tag, []);
}
(_c = tags.get(tag)) === null || _c === void 0 ? void 0 : _c.push(page);
}
}
for (const tag of tags.keys()) {
const pages = (_d = tags.get(tag)) !== null && _d !== void 0 ? _d : [];
const contents = `
<ul class="articles">
${pages
.map((page) => `
<li>
<article>
<h2 slot="page-title">${page.Data.Title}</h2>
${page.Data.Description
? `<p slot="page-description">${page.Data.Description}</p>`
: ""}
<a href="/${page.Data.Canonical}">Read More</a>
</article>
</li>`)
.join("\n")}
</ul>`;
const page = {
Contents: contents,
Data: {
Title: tag,
Description: `Pages tagged with '${tag}'`,
Canonical: determinePath(`${buildTagPath(options.TagRoot, tag).substring(1)}`, ""),
PageType: "website",
},
};
pagesToRender.set(buildTagPath(options.TagRoot, tag), page);
}
},
generateBundle(_options, bundle) {
var _a, _b, _c, _d, _e, _f;
const assets = getFiles(bundle);
const elements = getElements(assets);
const currentYear = new Date().getFullYear().toString();
for (const fileName of pagesToRender.keys()) {
const taprootPage = pagesToRender.get(fileName);
console.log(JSON.stringify(taprootPage.Data));
const outPath = (_a = taprootPage.Data.Canonical) !== null && _a !== void 0 ? _a : "";
const author = taprootPage.Data.Author
? options.Authors.get(taprootPage.Data.Author)
: undefined;
const head = [
BuildHead({
Author: author,
DateModified: taprootPage.Data.DateModified,
DatePublished: taprootPage.Data.DatePublished,
Publisher: options.Publisher,
SocialImage: {
Height: 100,
Width: 100,
MediaType: "image/png",
Url: `${options.SiteRootUrl}${outPath ? `${outPath}/` : ""}social_image.png}`,
},
Title: taprootPage.Data.Title,
Canonical: `${options.SiteRootUrl}${outPath}`,
CharSet: (_b = options.CharSet) !== null && _b !== void 0 ? _b : "UTF-8",
Description: taprootPage.Data.Description,
PageType: taprootPage.Data.PageType,
}),
elements.links,
elements.scripts,
].join("\n");
const template = templates.has((_c = taprootPage.Data.Template) !== null && _c !== void 0 ? _c : "")
? taprootPage.Data.Template
: "default";
const rendered = (_d = templates.get(template)) === null || _d === void 0 ? void 0 : _d.Render({
Author: author,
Canonical: taprootPage.Data.Canonical,
Contents: taprootPage.Contents,
CSSVars: taprootPage.Data.CSSVars,
CurrentYear: currentYear,
DateModified: taprootPage.Data.DateModified,
DatePublished: taprootPage.Data.DatePublished,
Description: taprootPage.Data.Description,
Head: head,
HidePageHead: taprootPage.Data.HidePageHead,
PageType: taprootPage.Data.PageType,
SiteName: options.Publisher.Name,
Tags: (_f = (_e = taprootPage.Data.Tags) === null || _e === void 0 ? void 0 : _e.map((tag) => {
return {
Tag: tag,
Url: buildTagPath(options.TagRoot, tag),
};
})) !== null && _f !== void 0 ? _f : [],
Title: taprootPage.Data.Title,
});
const htmlPath = `${outPath ? `${outPath}/` : ""}index.html`;
console.log(`Emitting file ${htmlPath}`);
this.emitFile({
type: "asset",
fileName: htmlPath,
source: rendered,
});
}
},
};
};
exports.Taproot = Taproot;
//# sourceMappingURL=index.js.map