| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import * as sass from "sass";
- import {
- firstFound,
- stripFileExtension,
- getCleanPath,
- getHref
- } from "./util.js";
- import fs from "fs/promises";
- 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";
- import { minify } from "html-minifier-terser";
- import favicons from "favicons";
- const imageSizes = [
- "640w",
- "768w",
- "1024w",
- "1366w",
- "1600w",
- "1920w",
- "2560w"
- ];
- const templateCache = new Map();
- function createMarkdownRenderer(meta) {
- return marked
- .use({ gfm: true })
- .use(markedCodePreview)
- .use({
- renderer: {
- image({ href, title, text }) {
- const hrefWithoutExt = stripFileExtension(href);
- const attrs = [`alt="${text}"`];
- const foundSrcSet = meta.resources.images.find(({ detail }) => {
- return detail.imageRef === href;
- });
- if (foundSrcSet) {
- const srcSetString = foundSrcSet.detail.srcSet
- .map(src => src.join(" "))
- .join(", ");
- const defaultSrc = foundSrcSet.detail.srcSet[0][0];
- attrs.push(`src="${defaultSrc}"`);
- attrs.push(`srcset="${srcSetString}"`);
- attrs.push(`sizes="(min-width: 800px) 40vw, 100vw"`);
- attrs.push(
- `style="aspect-ratio: ${foundSrcSet.detail.aspectRatio}"`
- );
- } else {
- attrs.push(`src="${href}"`);
- }
- if (title) {
- attrs.push(`title="${title}"`);
- }
- return `<img ${attrs.join(" ")} >`;
- }
- }
- });
- }
- export async function renderMarkdownWithTemplate(
- filePath,
- meta,
- fileOutputDir,
- fileOutputPath
- ) {
- const content = await fs.readFile(filePath, "utf8");
- const { data, content: markdown } = matter(content);
- const templateName = data.template || meta.opts.defaultTemplate;
- const href = getHref(fileOutputPath, meta);
- if (!templateCache.has(templateName)) {
- const templatePath = firstFound(
- meta.opts.templateDirs,
- `${templateName}.hbs`
- );
- if (!templatePath) throw new Error(`Template not found: ${templateName}`);
- const templateContent = await fs.readFile(templatePath, "utf8");
- templateCache.set(templateName, handlebars.compile(templateContent));
- }
- const template = templateCache.get(templateName);
- const renderer = createMarkdownRenderer(meta);
- const html = template({
- ...data,
- ...meta,
- href,
- content: renderer(markdown)
- });
- console.log("====>", data, meta, href);
- const minifiedHtml = await minify(html, {
- collapseWhitespace: true,
- removeComments: true,
- removeRedundantAttributes: true,
- removeEmptyAttributes: true,
- minifyCSS: true,
- minifyJS: true
- });
- return {
- detail: { ...data, href },
- result: minifiedHtml
- };
- }
- export async function compileSass(filePath) {
- const result = await sass.compileAsync(filePath, { style: "compressed" });
- return { result: result.css.toString() };
- }
- export async function optimiseSvg(filePath) {
- const svgString = await fs.readFile(filePath, "utf8");
- const result = optimize(svgString, {
- plugins: ["preset-default"]
- });
- return { result: result.data };
- }
- export async function copy(filePath) {
- const fileContent = await fs.readFile(filePath, "utf8");
- return { result: fileContent };
- }
- export async function optimiseImage(filePath, meta, fileOutputDir) {
- const sourceExtension = path.extname(filePath);
- const outputExtension = ".webp";
- const base = path.basename(filePath, sourceExtension);
- const original = sharp(filePath);
- const metadata = await original.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(
- fileOutputDir,
- `${base}-${sizeNum}${outputExtension}`
- );
- await original
- .clone()
- .resize(sizeNum)
- .webp({ quality: 80 })
- .toFile(outputFile);
- return [getCleanPath(outputFile, meta), size];
- })
- );
- const imageRef = getCleanPath(path.join(filePath), meta);
- return {
- result: srcSet.map(src => src[0]),
- detail: { imageRef, srcSet, aspectRatio },
- written: true
- };
- }
- export async function generateFavicons(filePath, meta, fileOutputDir) {
- // Configuration for favicons package
- const configuration = {
- path: getCleanPath(fileOutputDir, meta), // Path for overriding default icons path
- appName: meta.opts.site?.name || "Website",
- appShortName: meta.opts.site?.shortName || "Site",
- appDescription: meta.opts.site?.description || "",
- developerName: meta.opts.site?.author || "",
- developerURL: meta.opts.site?.url || "",
- dir: "auto",
- lang: meta.opts.site?.language | "en-US",
- background: meta.opts.site?.backgroundColor || "#ffffff",
- theme_color: meta.opts.site?.themeColor || "#ffffff",
- appleStatusBarStyle: "black-translucent",
- display: "standalone",
- orientation: "any",
- scope: "/",
- start_url: "/",
- version: "1.0",
- logging: false,
- pixel_art: false,
- loadManifestWithCredentials: false,
- manifestMaskable: false,
- icons: {
- android: true,
- appleIcon: true,
- appleStartup: true,
- favicons: true,
- windows: true,
- yandex: true
- }
- };
- try {
- const response = await favicons(filePath, configuration);
- // Write all generated images to disk
- await Promise.all(
- response.images.map(async image => {
- const outputPath = path.join(fileOutputDir, image.name);
- await fs.writeFile(outputPath, image.contents);
- })
- );
- // Write all generated files (manifests, etc.) to disk
- await Promise.all(
- response.files.map(async file => {
- const outputPath = path.join(fileOutputDir, file.name);
- await fs.writeFile(outputPath, file.contents);
- })
- );
- // Combine HTML meta tags
- const htmlMeta = response.html.join("\n ");
- return {
- detail: { htmlMeta },
- result: [
- ...response.images.map(img =>
- getCleanPath(path.join(fileOutputDir, img.name), meta)
- ),
- ...response.files.map(file =>
- getCleanPath(path.join(fileOutputDir, file.name), meta)
- )
- ],
- written: true
- };
- } catch (error) {
- throw new Error(`Failed to generate favicons: ${error.message}`);
- }
- }
|