From 3bab0a1715255163b1ebe5374436d14c596fd565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20=E2=80=9CAd=C3=A6dra=E2=80=9D=20Hamel?= Date: Wed, 26 Jun 2024 23:42:33 +0200 Subject: [PATCH] De-gulpify: css, fonts, images --- lib/hash.ts | 58 +++++++++++++++++++++++++++++---------------- lib/tasks/css.ts | 29 +++++++++++++++++++---- lib/tasks/fonts.ts | 51 +++++++++++++++++++-------------------- lib/tasks/images.ts | 15 ++++++++---- 4 files changed, 98 insertions(+), 55 deletions(-) diff --git a/lib/hash.ts b/lib/hash.ts index f177171..9882957 100644 --- a/lib/hash.ts +++ b/lib/hash.ts @@ -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) => + (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): Observable => + new Observable((subscriber) => { + const mappings = new Map(); + 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); - }, - }); + }); } diff --git a/lib/tasks/css.ts b/lib/tasks/css.ts index 134ec72..aea7383 100644 --- a/lib/tasks/css.ts +++ b/lib/tasks/css.ts @@ -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")), + ), + ); diff --git a/lib/tasks/fonts.ts b/lib/tasks/fonts.ts index 203023b..d303206 100644 --- a/lib/tasks/fonts.ts +++ b/lib/tasks/fonts.ts @@ -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 { + 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")), + ); diff --git a/lib/tasks/images.ts b/lib/tasks/images.ts index 6b23933..ad24aa4 100644 --- a/lib/tasks/images.ts +++ b/lib/tasks/images.ts @@ -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")), + );