De-gulpify: css, fonts, images

This commit is contained in:
Thibault “Adædra” Hamel 2024-06-26 23:42:33 +02:00
parent dccb5f716c
commit 3bab0a1715
4 changed files with 98 additions and 55 deletions

View File

@ -1,7 +1,7 @@
import { Transform } from "node:stream";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import File from "vinyl"; import File from "vinyl";
import { PRODUCTION } from "./environment.js"; import { PRODUCTION } from "./environment.js";
import { Observable } from "rxjs";
function fileHash(buffer: Buffer) { function fileHash(buffer: Buffer) {
const hash = createHash("sha256"); const hash = createHash("sha256");
@ -9,28 +9,44 @@ function fileHash(buffer: Buffer) {
return hash.digest("hex"); return hash.digest("hex");
} }
const hashPath =
(mappings: Map<string, string>) =>
(file: File): File => {
const hash = PRODUCTION ? fileHash(file.contents as Buffer) : "00000000";
const newName = [
file.basename.substring(0, file.basename.length - file.extname.length),
hash.substring(0, 8),
file.extname.substring(1),
].join(".");
mappings.set(file.basename, newName);
file.basename = newName;
return file;
};
export default function (manifestName: string) { export default function (manifestName: string) {
const mappings: { [path: string]: string } = {}; return (observable: Observable<File>): Observable<File> =>
new Observable((subscriber) => {
const mappings = new Map<string, string>();
const hash = hashPath(mappings);
return new Transform({ observable.subscribe({
objectMode: true, next(file) {
transform(chunk: File, _, callback) { subscriber.next(hash(file));
const hash = PRODUCTION ? fileHash(chunk.contents as Buffer) : "00000000";
const newName = `${chunk.basename.substring(0, chunk.basename.length - chunk.extname.length)}.${hash.substring(0, 8)}${chunk.extname}`;
mappings[chunk.basename] = newName;
chunk.basename = newName;
callback(null, chunk);
}, },
final(callback) { error(e) {
subscriber.error(e);
},
complete() {
const mappingFile = new File({ const mappingFile = new File({
path: manifestName, path: manifestName,
contents: Buffer.from(JSON.stringify(mappings)), contents: Buffer.from(JSON.stringify(Object.fromEntries(mappings))),
}); });
this.push(mappingFile);
callback(null); subscriber.next(mappingFile);
subscriber.complete();
}, },
}); });
});
} }

View File

@ -1,6 +1,27 @@
import { src, dest } from "gulp"; import postcss, { Result } from "postcss";
import postcss from "gulp-postcss"; import config from "../../postcss.config.js";
import hashPath from "../hash.js"; import { src, then, synchronise, dest } from "../rx-utils.js";
import { map, lastValueFrom } from "rxjs";
import hashPaths from "../hash.js";
export const css = () => export const css = () =>
src("src/index.css").pipe(postcss()).pipe(hashPath("css.manifest")).pipe(dest("dist/_assets/")); lastValueFrom(
src("src/index.css").pipe(
then(
(file) =>
new Promise((resolve) =>
postcss(config.plugins)
.process(file.contents, { from: file.path })
.then((result: Result) => {
file.contents = Buffer.from(result.css);
file.path = "index.css";
resolve(file);
}),
),
),
synchronise(),
hashPaths("css.manifest"),
map(dest("dist/_assets")),
),
);

View File

@ -1,43 +1,42 @@
import { Transform } from "node:stream";
import File from "vinyl"; import File from "vinyl";
import tmp from "tmp"; import tmp from "tmp";
import { execFile } from "node:child_process"; import { execFile } from "node:child_process";
import { readFileSync, unlinkSync } from "node:fs"; import { readFileSync, unlinkSync } from "node:fs";
import { src, dest } from "gulp";
import hashPaths from "../hash.js"; import hashPaths from "../hash.js";
import { src, then, synchronise, dest } from "../rx-utils.js";
import { map } from "rxjs";
const FONT_PRESETS = { const FONT_PRESETS = {
mono: { ranges: ["20-7F", "2205", "2E22-2E25", "2713", "2717"] }, mono: { ranges: ["20-7F", "2205", "2E22-2E25", "2713", "2717"] },
text: { ranges: ["20-7F", "A0-FF", "2000-206F", "20AC"] }, text: { ranges: ["20-7F", "A0-FF", "2000-206F", "20AC"] },
}; };
function compileFont() { function compileFont(font: File): Promise<File> {
return new Transform({ const [, variant, weight] = /([A-Z][a-z]+)-(\w+)\.ttf$/.exec(font.basename);
objectMode: true,
async transform(chunk: File, _, callback) {
const [, variant, weight] = /([A-Z][a-z]+)-(\w+)\.ttf$/.exec(chunk.basename);
const tmpOutput = tmp.fileSync({ discardDescriptor: true }); const tmpOutput = tmp.fileSync({ discardDescriptor: true });
const unicodes = FONT_PRESETS[variant.toLowerCase()].ranges; const unicodes = FONT_PRESETS[variant.toLowerCase()].ranges;
return new Promise((resolve) => {
execFile("pyftsubset", [ execFile("pyftsubset", [
chunk.path, font.path,
`--unicodes=${unicodes.join(",")}`, `--unicodes=${unicodes.join(",")}`,
`--output-file=${tmpOutput.name}`, `--output-file=${tmpOutput.name}`,
"--flavor=woff2", "--flavor=woff2",
]).once("exit", () => { ]).once("exit", () => {
chunk.path = `iosevka-adaedra-${variant.toLowerCase()}-${weight.toLowerCase()}.woff2`; font.path = `iosevka-adaedra-${variant.toLowerCase()}-${weight.toLowerCase()}.woff2`;
chunk.contents = readFileSync(tmpOutput.name); font.contents = readFileSync(tmpOutput.name);
chunk.base = null; font.base = null;
unlinkSync(tmpOutput.name); unlinkSync(tmpOutput.name);
callback(null, chunk); resolve(font);
}); });
},
}); });
} }
export const fonts = () => export const fonts = () =>
src("vendor/*.ttf") src("vendor/*.ttf").pipe(
.pipe(compileFont()) then(compileFont),
.pipe(hashPaths("fonts.manifest")) synchronise(),
.pipe(dest("dist/_assets")); hashPaths("fonts.manifest"),
map(dest("dist/_assets")),
);

View File

@ -1,7 +1,14 @@
import { src, dest } from "gulp";
import hashPaths from "../hash.js"; import hashPaths from "../hash.js";
import { src, then, synchronise, dest } from "../rx-utils.js";
import { map } from "rxjs";
export const images = () => export const images = () =>
src("src/*.avif", { encoding: false }) src("src/*.avif").pipe(
.pipe(hashPaths("images.manifest")) then(async (file) => {
.pipe(dest("dist/_assets/")); file.path = file.path.substring(4);
return file;
}),
synchronise(),
hashPaths("images.manifest"),
map(dest("dist/_assets")),
);