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 File from "vinyl";
import { PRODUCTION } from "./environment.js";
import { Observable } from "rxjs";
function fileHash(buffer: Buffer) {
const hash = createHash("sha256");
@ -9,28 +9,44 @@ function fileHash(buffer: Buffer) {
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) {
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({
objectMode: true,
transform(chunk: File, _, callback) {
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}`;
observable.subscribe({
next(file) {
subscriber.next(hash(file));
},
error(e) {
subscriber.error(e);
},
complete() {
const mappingFile = new File({
path: manifestName,
contents: Buffer.from(JSON.stringify(Object.fromEntries(mappings))),
});
mappings[chunk.basename] = newName;
chunk.basename = newName;
callback(null, chunk);
},
final(callback) {
const mappingFile = new File({
path: manifestName,
contents: Buffer.from(JSON.stringify(mappings)),
subscriber.next(mappingFile);
subscriber.complete();
},
});
this.push(mappingFile);
callback(null);
},
});
});
}

View File

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

View File

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