Browse Source

feat(scripts): generate optimised image formats

Signed-off-by: Muthu Kumar <muthukumar@thefeathers.in>
pull/1/head
Muthu Kumar 2 years ago
parent
commit
f5c849e5b9
Signed by: mkrhere GPG Key ID: 3FD688398897097E
  1. 4
      .gitignore
  2. 2
      package.json
  3. 64
      pnpm-lock.yaml
  4. 52
      scripts/blog.js
  5. 30
      src/pages/blog/components/BlogContent.tsx
  6. 6
      src/util/index.ts

4
.gitignore

@ -10,6 +10,9 @@
# production
/build
/dist
/public/blog/assets/featured
/public/blog/assets/gen
# misc
.DS_Store
@ -21,4 +24,3 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
dist

2
package.json

@ -19,6 +19,7 @@
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"framer-motion": "^6.3.15",
"gm": "^1.23.1",
"imagen": "github:MKRhere/imagen",
"marked": "^4.0.17",
"react": "^18.2.0",
@ -27,6 +28,7 @@
},
"devDependencies": {
"@svgr/rollup": "^6.2.1",
"@types/gm": "^1.18.12",
"@types/marked": "^4.0.3",
"@types/node": "^18.0.0",
"@types/react": "^18.0.14",

64
pnpm-lock.yaml

@ -3,6 +3,7 @@ lockfileVersion: 5.4
specifiers:
'@emotion/css': ^11.9.0
'@svgr/rollup': ^6.2.1
'@types/gm': ^1.18.12
'@types/marked': ^4.0.3
'@types/node': ^18.0.0
'@types/react': ^18.0.14
@ -12,6 +13,7 @@ specifiers:
classnames: ^2.3.1
date-fns: ^2.28.0
framer-motion: ^6.3.15
gm: ^1.23.1
imagen: github:MKRhere/imagen
marked: ^4.0.17
react: ^18.2.0
@ -26,6 +28,7 @@ dependencies:
classnames: 2.3.1
date-fns: 2.28.0
framer-motion: 6.3.15_biqbaboplfbrettd7655fr4n2y
gm: 1.23.1
imagen: github.com/MKRhere/imagen/c9988aeddaf8e03a1b30293842b8a6e3e2065e62
marked: 4.0.17
react: 18.2.0
@ -34,6 +37,7 @@ dependencies:
devDependencies:
'@svgr/rollup': 6.2.1
'@types/gm': 1.18.12
'@types/marked': 4.0.3
'@types/node': 18.0.0
'@types/react': 18.0.14
@ -1501,7 +1505,7 @@ packages:
'@babel/helper-split-export-declaration': 7.16.7
'@babel/parser': 7.18.5
'@babel/types': 7.18.4
debug: 4.3.2
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@ -2616,6 +2620,12 @@ packages:
'@types/serve-static': 1.13.10
dev: true
/@types/gm/1.18.12:
resolution: {integrity: sha512-k3Gd15Ywy75FiFQUsrqpKD42rhMdHaETyw2zjK9u3xbrH1c7nS+npSb19yjPCqR8KxeL7vw2zJF4Kl2bAca0aQ==}
dependencies:
'@types/node': 18.0.0
dev: true
/@types/graceful-fs/4.1.5:
resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==}
dependencies:
@ -3298,6 +3308,14 @@ packages:
is-string: 1.0.7
dev: true
/array-parallel/0.1.3:
resolution: {integrity: sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==}
dev: false
/array-series/0.1.5:
resolution: {integrity: sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==}
dev: false
/array-union/2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'}
@ -4014,6 +4032,13 @@ packages:
yaml: 1.10.2
dev: true
/cross-spawn/4.0.2:
resolution: {integrity: sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==}
dependencies:
lru-cache: 4.1.5
which: 1.3.1
dev: false
/cross-spawn/7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@ -4072,7 +4097,7 @@ packages:
postcss-modules-scope: 3.0.0_postcss@8.4.14
postcss-modules-values: 4.0.0_postcss@8.4.14
postcss-value-parser: 4.2.0
semver: 7.3.5
semver: 7.3.7
webpack: 5.73.0
dev: true
@ -4295,7 +4320,6 @@ packages:
optional: true
dependencies:
ms: 2.1.3
dev: true
/debug/4.3.2:
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
@ -5184,7 +5208,7 @@ packages:
ajv: 6.12.6
chalk: 4.1.1
cross-spawn: 7.0.3
debug: 4.3.2
debug: 4.3.4
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.1.1
@ -5801,6 +5825,18 @@ packages:
slash: 3.0.0
dev: true
/gm/1.23.1:
resolution: {integrity: sha512-wYGVAa8/sh9ggF5qWoOs6eArcAgwEPkDNvf637jHRHkMUznvs7m/Q2vrc0KLN6B8px3nnRJqJcXK4mTK6lLFmg==}
engines: {node: '>= 0.10.0'}
dependencies:
array-parallel: 0.1.3
array-series: 0.1.5
cross-spawn: 4.0.2
debug: 3.2.7
transitivePeerDependencies:
- supports-color
dev: false
/graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
dev: true
@ -6295,7 +6331,6 @@ packages:
/isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/istanbul-lib-coverage/3.2.0:
resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
@ -7171,6 +7206,13 @@ packages:
tslib: 2.3.0
dev: true
/lru-cache/4.1.5:
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
dependencies:
pseudomap: 1.0.2
yallist: 2.1.2
dev: false
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@ -7359,7 +7401,6 @@ packages:
/ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/multicast-dns/7.2.5:
resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
@ -8120,7 +8161,7 @@ packages:
cosmiconfig: 7.0.1
klona: 2.0.5
postcss: 8.4.14
semver: 7.3.5
semver: 7.3.7
webpack: 5.73.0
dev: true
@ -8651,6 +8692,10 @@ packages:
ipaddr.js: 1.9.1
dev: true
/pseudomap/1.0.2:
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
dev: false
/psl/1.8.0:
resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==}
dev: true
@ -10440,7 +10485,6 @@ packages:
hasBin: true
dependencies:
isexe: 2.0.0
dev: true
/which/2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
@ -10689,6 +10733,10 @@ packages:
engines: {node: '>=10'}
dev: true
/yallist/2.1.2:
resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
dev: false
/yallist/4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}

52
scripts/blog.js

@ -1,7 +1,14 @@
// @ts-check
import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
import { readdir, readFile, writeFile, mkdir, stat } from "node:fs/promises";
import { renderPreviewImg } from "imagen";
import GM from "gm";
function rewriteExtn(filename, extn) {
const split = filename.split(".");
split[split.length - 1] = extn;
return split.join(".");
}
const toplevel = (await readdir("public/blog")).filter(x => x !== "assets");
@ -55,17 +62,15 @@ const data = Object.fromEntries(
),
);
function rewriteExtn(filename, extn) {
const split = filename.split(".");
split[split.length - 1] = extn;
return split.join(".");
}
writeFile("src/blog.json", JSON.stringify(data, null, "\t"), "utf-8").then(() =>
console.log("Done"),
);
/**
*
* @param {import("./blog.types").Data} data
*/
export async function generateFeaturedImages(data) {
async function generateFeaturedImages(data) {
for (const year in data) {
const yearData = data[year];
for (const slug in yearData) {
@ -82,10 +87,35 @@ export async function generateFeaturedImages(data) {
}
}
writeFile("src/blog.json", JSON.stringify(data, null, "\t"), "utf-8").then(() =>
console.log("Done"),
);
mkdir("public/blog/assets/featured", { recursive: true }).then(() =>
generateFeaturedImages(data),
);
const convert = (gm, img, size, fmt) =>
new Promise((resolve, reject) =>
gm("public/blog/assets/" + img)
.resize(size)
.noProfile()
.write(
"public/blog/assets/gen/" + rewriteExtn(img, size + "." + fmt),
err => {
if (err) return reject(err);
else resolve(true);
},
),
);
async function generateOptimisedImages() {
await mkdir("public/blog/assets/gen/", { recursive: true });
const imgs = await readdir("public/blog/assets");
for (const img of imgs) {
if (!(await stat("public/blog/assets/" + img)).isFile()) continue;
const gm = GM.subClass({ imageMagick: true });
for (const size of [480, 800])
for (const fmt of ["jpg", "webp", "avif"])
await convert(gm, img, size, fmt);
}
}
generateOptimisedImages();

30
src/pages/blog/components/BlogContent.tsx

@ -7,7 +7,7 @@ import { ArticleSubHeader } from "./ArticleSubHeader";
import { css, cx } from "@emotion/css";
import { ReactComponent as Arrow } from "../../../assets/arrow-thin.svg";
import { ReactComponent as Close } from "../../../assets/close.svg";
import { ellipses, useNav } from "../../../util";
import { ellipses, rewriteExtn, useNav } from "../../../util";
const Markdown: React.FC<{ content: string }> = ({ content }) => {
return (
@ -152,6 +152,11 @@ export const BlogPost: React.FC = () => {
if (!article || error) return <div>{error || "Unknown error occurred"}</div>;
const featured = rewriteExtn(
"/blog/assets/gen/" + article["featured-img"],
"",
);
return (
<>
<Close
@ -169,15 +174,34 @@ export const BlogPost: React.FC = () => {
width: 100%;
max-height: 25rem;
`}>
<picture>
{[
[480, "avif", 600],
[800, "avif"],
[480, "webp", 600],
[800, "webp"],
[480, "jpg", 600],
[800, "jpg"],
].map(([size, format, query]) => {
return (
<source
key={`${size}-${format}-${query || ""}`}
srcSet={`${featured}${size}.${format} ${size}w`}
type={`image/${format}`}
{...(query && { media: `(max-width: ${query}px)` })}
/>
);
})}
<img
className={css`
max-width: 100%;
height: 100%;
border-radius: 0.5rem;
`}
src={"/blog/assets/" + article["featured-img"]}
alt="Featured"
src={featured + "800.jpg"}
alt="featured"
/>
</picture>
</div>
<h1
className={css`

6
src/util/index.ts

@ -28,3 +28,9 @@ export const useNav = () => {
navigate(link);
};
};
export function rewriteExtn(filename: string, extn: string) {
const split = filename.split(".");
split[split.length - 1] = extn;
return split.join(".");
}

Loading…
Cancel
Save