|
|
@@ -1,37 +1,145 @@
|
|
|
-import * as sass from "sass"
|
|
|
-import { firstFound } from "./util.js"
|
|
|
-import fs from "fs"
|
|
|
-import handlebars from "handlebars"
|
|
|
-import { marked } from "marked"
|
|
|
-import markedCodePreview from "marked-code-preview"
|
|
|
-import matter from "gray-matter"
|
|
|
+import * as sass from "sass";
|
|
|
+import { firstFound } from "./util.js";
|
|
|
+import fs from "fs";
|
|
|
+import handlebars from "handlebars";
|
|
|
+import { marked } from "marked";
|
|
|
+import markedCodePreview from "marked-code-preview";
|
|
|
+import matter from "gray-matter";
|
|
|
+import { optimize } from "svgo";
|
|
|
+import sharp from "sharp";
|
|
|
+import path from "path";
|
|
|
|
|
|
-const markedRenderer = marked.use({ gfm: true }).use(markedCodePreview)
|
|
|
+const imageSizes = [
|
|
|
+ "640w",
|
|
|
+ "768w",
|
|
|
+ "1024w",
|
|
|
+ "1366w",
|
|
|
+ "1600w",
|
|
|
+ "1920w",
|
|
|
+ "2560w"
|
|
|
+];
|
|
|
+
|
|
|
+function stripFileExtension(filePath) {
|
|
|
+ if (typeof filePath !== "string") return "";
|
|
|
+
|
|
|
+ const parts = filePath.split("/");
|
|
|
+ const fileName = parts.pop();
|
|
|
+
|
|
|
+ const nameWithoutExt = fileName.replace(/\.[^/.]+$/, "");
|
|
|
+
|
|
|
+ return [...parts, nameWithoutExt].join("/");
|
|
|
+}
|
|
|
+
|
|
|
+const renderer = meta => ({
|
|
|
+ image({ href, title, text }) {
|
|
|
+ const hrefWithoutFileExtension = stripFileExtension(href);
|
|
|
+ const attrs = [`alt="${text}"`];
|
|
|
+
|
|
|
+ const foundSrcSet = meta.resources.images.find(imageResource => {
|
|
|
+ return (
|
|
|
+ stripFileExtension(imageResource.path[0]) === hrefWithoutFileExtension
|
|
|
+ );
|
|
|
+ });
|
|
|
+ if (foundSrcSet) {
|
|
|
+ const srcSetString = foundSrcSet.path[1]
|
|
|
+ .map(src => src.join(" "))
|
|
|
+ .join(", ");
|
|
|
+ const defaultSrc = foundSrcSet.path[1][0][0];
|
|
|
+ attrs.push(`src="${defaultSrc}"`);
|
|
|
+ attrs.push(`srcset="${srcSetString}"`);
|
|
|
+ attrs.push(
|
|
|
+ `sizes="(min-width: 1600px) 25vw, (min-width: 800px) 50vw, 100vw"`
|
|
|
+ );
|
|
|
+ attrs.push(`style="aspect-ratio: ${foundSrcSet.path[2].aspectRatio}"`);
|
|
|
+ } else {
|
|
|
+ attrs.push(`src="${href}"`);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (title) {
|
|
|
+ attrs.push(`title="${title}"`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return `<img ${attrs.join(" ")} >`;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const markedRenderer = marked.use({ gfm: true }).use(markedCodePreview);
|
|
|
|
|
|
export function renderMarkdownWithTemplate(filePath, meta) {
|
|
|
- const content = fs.readFileSync(filePath, "utf8")
|
|
|
- const { data, content: markdown } = matter(content)
|
|
|
- const templateName = data.template || meta.opts.defaultTemplate
|
|
|
+ const content = fs.readFileSync(filePath, "utf8");
|
|
|
+ const { data, content: markdown } = matter(content);
|
|
|
+ const templateName = data.template || meta.opts.defaultTemplate;
|
|
|
|
|
|
const template = handlebars.compile(
|
|
|
fs.readFileSync(
|
|
|
firstFound(meta.opts.templateDirs, `${templateName}.hbs`),
|
|
|
- "utf8",
|
|
|
- ),
|
|
|
- )
|
|
|
+ "utf8"
|
|
|
+ )
|
|
|
+ );
|
|
|
const html = template({
|
|
|
...data,
|
|
|
...meta,
|
|
|
- content: markedRenderer(markdown),
|
|
|
- })
|
|
|
+ content: markedRenderer.use({ renderer: renderer(meta) })(markdown)
|
|
|
+ });
|
|
|
return {
|
|
|
detail: data,
|
|
|
- result: html,
|
|
|
- }
|
|
|
+ result: html
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
export function compileSass(filePath) {
|
|
|
return {
|
|
|
- result: sass.compile(filePath, { style: "compressed" }).css.toString(),
|
|
|
+ result: sass.compile(filePath, { style: "compressed" }).css.toString()
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+export function optimiseSvg(filePath) {
|
|
|
+ const svgString = fs.readFileSync(filePath, "utf8");
|
|
|
+ const result = optimize(svgString, {
|
|
|
+ plugins: ["preset-default"]
|
|
|
+ });
|
|
|
+ return { result: result.data };
|
|
|
+}
|
|
|
+
|
|
|
+function getCleanPath(path, meta) {
|
|
|
+ return path.replace(meta.opts.runDir, "").replace(meta.opts.baseDir, "/");
|
|
|
+}
|
|
|
+
|
|
|
+export async function optimiseImage(filePath, meta, fileOutputPath) {
|
|
|
+ const sourceExtension = path.extname(filePath);
|
|
|
+ const outputExtension = ".webp";
|
|
|
+ const base = path.basename(filePath).slice(0, -sourceExtension.length);
|
|
|
+ const metadata = await sharp(filePath).metadata();
|
|
|
+ const { width, height } = metadata;
|
|
|
+
|
|
|
+ if (!width || !height) {
|
|
|
+ throw new Error("Could not determine image dimensions");
|
|
|
}
|
|
|
+
|
|
|
+ const aspectRatio = width / height;
|
|
|
+
|
|
|
+ const srcset = await Promise.all(
|
|
|
+ imageSizes.map(async size => {
|
|
|
+ const sizeNum = parseInt(size.replace("w", ""), 10);
|
|
|
+ const outputFile = path.join(
|
|
|
+ fileOutputPath,
|
|
|
+ `${base}-${sizeNum}${outputExtension}`
|
|
|
+ );
|
|
|
+
|
|
|
+ await sharp(filePath)
|
|
|
+ .resize(sizeNum)
|
|
|
+ .webp({ quality: 80 })
|
|
|
+ .toFile(outputFile);
|
|
|
+
|
|
|
+ return [getCleanPath(outputFile, meta), size];
|
|
|
+ })
|
|
|
+ );
|
|
|
+ const imageRef = getCleanPath(
|
|
|
+ path.join(fileOutputPath, `${base}${outputExtension}`),
|
|
|
+ meta
|
|
|
+ );
|
|
|
+ return {
|
|
|
+ result: [imageRef, srcset, { aspectRatio }],
|
|
|
+ written: true
|
|
|
+ };
|
|
|
}
|