diff --git a/deno.jsonc b/deno.jsonc index a66f8a5..65c82e5 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -19,9 +19,10 @@ "rehype-stringify": "npm:rehype-stringify@^10.0.0", "rxjs": "npm:rxjs@^7.8.1", "tmp": "npm:tmp@^0.2.3", + "to-vfile": "https://esm.sh/to-vfile@^8.0.0", "unified": "npm:unified@^11.0.4", "unist-util-visit": "npm:unist-util-visit@^5.0.0", - "vinyl": "npm:vinyl@^3.0.0" + "vfile": "https://esm.sh/vfile@^6.0.1" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/deno.lock b/deno.lock index d86e3e6..64cd81a 100644 --- a/deno.lock +++ b/deno.lock @@ -20,8 +20,7 @@ "npm:rxjs@^7.8.1": "npm:rxjs@7.8.1", "npm:tmp@^0.2.3": "npm:tmp@0.2.3", "npm:unified@^11.0.4": "npm:unified@11.0.5", - "npm:unist-util-visit@^5.0.0": "npm:unist-util-visit@5.0.0", - "npm:vinyl@^3.0.0": "npm:vinyl@3.0.0" + "npm:unist-util-visit@^5.0.0": "npm:unist-util-visit@5.0.0" }, "npm": { "@csstools/selector-resolve-nested@1.1.0_postcss-selector-parser@6.1.0": { @@ -122,10 +121,6 @@ "picomatch": "picomatch@2.3.1" } }, - "b4a@1.6.6": { - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", - "dependencies": {} - }, "bail@2.0.2": { "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "dependencies": {} @@ -134,10 +129,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dependencies": {} }, - "bare-events@2.4.2": { - "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", - "dependencies": {} - }, "bcp-47-match@2.0.3": { "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", "dependencies": {} @@ -242,14 +233,6 @@ "source-map": "source-map@0.6.1" } }, - "clone-stats@1.0.0": { - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "dependencies": {} - }, - "clone@2.1.2": { - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dependencies": {} - }, "collapse-white-space@2.1.0": { "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", "dependencies": {} @@ -454,10 +437,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dependencies": {} }, - "fast-fifo@1.3.2": { - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dependencies": {} - }, "fill-range@7.1.1": { "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { @@ -1204,10 +1183,6 @@ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", "dependencies": {} }, - "queue-tick@1.0.1": { - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dependencies": {} - }, "read-cache@1.0.0": { "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "dependencies": { @@ -1486,14 +1461,6 @@ "unified": "unified@11.0.5" } }, - "remove-trailing-separator@1.1.0": { - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dependencies": {} - }, - "replace-ext@2.0.0": { - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dependencies": {} - }, "resolve@1.22.8": { "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { @@ -1538,15 +1505,6 @@ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "dependencies": {} }, - "streamx@2.18.0": { - "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", - "dependencies": { - "bare-events": "bare-events@2.4.2", - "fast-fifo": "fast-fifo@1.3.2", - "queue-tick": "queue-tick@1.0.1", - "text-decoder": "text-decoder@1.1.0" - } - }, "string-width@4.2.3": { "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { @@ -1606,18 +1564,6 @@ "picocolors": "picocolors@1.0.1" } }, - "teex@1.0.1": { - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dependencies": { - "streamx": "streamx@2.18.0" - } - }, - "text-decoder@1.1.0": { - "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", - "dependencies": { - "b4a": "b4a@1.6.6" - } - }, "tmp@0.2.3": { "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dependencies": {} @@ -1742,16 +1688,6 @@ "vfile-message": "vfile-message@4.0.2" } }, - "vinyl@3.0.0": { - "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", - "dependencies": { - "clone": "clone@2.1.2", - "clone-stats": "clone-stats@1.0.0", - "remove-trailing-separator": "remove-trailing-separator@1.1.0", - "replace-ext": "replace-ext@2.0.0", - "teex": "teex@1.0.1" - } - }, "web-namespaces@2.0.1": { "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", "dependencies": {} @@ -1789,12 +1725,23 @@ } }, "redirects": { - "https://esm.sh/asciidoctor@^3.0.4": "https://esm.sh/asciidoctor@3.0.4" + "https://esm.sh/asciidoctor@^3.0.4": "https://esm.sh/asciidoctor@3.0.4", + "https://esm.sh/to-vfile@^8.0.0": "https://esm.sh/to-vfile@8.0.0", + "https://esm.sh/vfile@^6.0.1": "https://esm.sh/vfile@6.0.1" }, "remote": { "https://esm.sh/asciidoctor@3.0.4": "23f6b6ab844b5295074b6dd139b0153f4b6424df7f41aaa2fcec5004359bb094", + "https://esm.sh/to-vfile@8.0.0": "05aa989433514d267833c5057b53935c837c1373824ed8b60ed38cae67655af5", "https://esm.sh/v135/@asciidoctor/core@3.0.4/denonext/core.mjs": "fbb624c9375ac40a70e586d84f6986ff7e8c1ff572e006284a08709e5118bbf7", - "https://esm.sh/v135/asciidoctor@3.0.4/denonext/asciidoctor.mjs": "00c21f42422684a4bc5e35647d9495a6631632fdc4c97ddb045a1a3b46d58dba" + "https://esm.sh/v135/asciidoctor@3.0.4/denonext/asciidoctor.mjs": "00c21f42422684a4bc5e35647d9495a6631632fdc4c97ddb045a1a3b46d58dba", + "https://esm.sh/v135/to-vfile@8.0.0/denonext/to-vfile.mjs": "e9e39c7791eb0ea7055105ffd3eb6ccf3d36a1a4ddf6c5a3824bc3838544ad40", + "https://esm.sh/v135/unist-util-stringify-position@4.0.0/denonext/unist-util-stringify-position.mjs": "dabd32cb2b590bbb077fc6f6591a2e065cffd6c55646ba383455926a27ea64d7", + "https://esm.sh/v135/vfile-message@4.0.2/denonext/vfile-message.mjs": "efc85b18bedda337fb1c20cdc452fac3addac32ee55948cebf2845396ae641ac", + "https://esm.sh/v135/vfile@6.0.1/denonext/do-not-use-conditional-minpath.js": "9a7ca0443aa0dff4d6a74822ba54cd1a5077a75d2f740d4d99cd8897a7f62a3c", + "https://esm.sh/v135/vfile@6.0.1/denonext/do-not-use-conditional-minproc.js": "34e683b50c7d1e17a90a522afafcdb032ceaeff2e0eb5dbca28f9b4783de73ba", + "https://esm.sh/v135/vfile@6.0.1/denonext/do-not-use-conditional-minurl.js": "79e90ed8915870371874d7ca17f8cbb3a8cab5b4e054bbcff85f7c91df32d0ba", + "https://esm.sh/v135/vfile@6.0.1/denonext/vfile.mjs": "31065c960cf79824afc0bc6890688c540f09d067eb5415effb7fafac6ef33c13", + "https://esm.sh/vfile@6.0.1": "a28180cc6d7bfad90237eddd9dbc8c840af0bc50e1220bb9b8dc670e96fe49aa" }, "workspace": { "dependencies": [ @@ -1817,8 +1764,7 @@ "npm:rxjs@^7.8.1", "npm:tmp@^0.2.3", "npm:unified@^11.0.4", - "npm:unist-util-visit@^5.0.0", - "npm:vinyl@^3.0.0" + "npm:unist-util-visit@^5.0.0" ] } } diff --git a/lib/hash.ts b/lib/hash.ts index 1f2acae..a5ac72f 100644 --- a/lib/hash.ts +++ b/lib/hash.ts @@ -1,8 +1,8 @@ import { createHash } from "node:crypto"; -import File from "vinyl"; import { PRODUCTION } from "./environment.ts"; import { Observable } from "rxjs"; import { Buffer } from "node:buffer"; +import { VFile } from "vfile"; function fileHash(buffer: Buffer) { const hash = createHash("sha256"); @@ -10,22 +10,22 @@ 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 hashPath = (mappings: Map) => (file: VFile): VFile => { + const hash = PRODUCTION ? fileHash(file.value) : "00000000"; const newName = [ - file.basename.substring(0, file.basename.length - file.extname.length), + file.stem!, hash.substring(0, 8), - file.extname.substring(1), + file.extname!.substring(1), ].join("."); - mappings.set(file.basename, newName); + mappings.set(file.basename!, newName); file.basename = newName; return file; }; export default function (manifestName: string) { - return (observable: Observable): Observable => + return (observable: Observable): Observable => new Observable((subscriber) => { const mappings = new Map(); const hash = hashPath(mappings); @@ -38,9 +38,9 @@ export default function (manifestName: string) { subscriber.error(e); }, complete() { - const mappingFile = new File({ + const mappingFile = new VFile({ path: manifestName, - contents: Buffer.from(JSON.stringify(Object.fromEntries(mappings))), + value: JSON.stringify(Object.fromEntries(mappings)), }); subscriber.next(mappingFile); diff --git a/lib/rx-utils.ts b/lib/rx-utils.ts index f09d538..858cf02 100644 --- a/lib/rx-utils.ts +++ b/lib/rx-utils.ts @@ -1,18 +1,18 @@ import { from, mergeMap, Observable, Subscriber } from "rxjs"; -import File from "vinyl"; -import { readFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { existsSync } from "node:fs"; import { mkdir, writeFile } from "node:fs/promises"; import { Glob } from "glob"; +import { VFile } from "vfile"; +import { read } from "to-vfile"; export function dest(prefix: string) { - return async (file: File) => { + return async (file: VFile) => { const actualPath = join(prefix, file.path); if (!existsSync(dirname(actualPath))) { await mkdir(dirname(actualPath), { recursive: true }); } - await writeFile(actualPath, file.contents as Buffer); + await writeFile(actualPath, file.value); console.log("[-] Written", actualPath); return file; @@ -37,7 +37,11 @@ export function onComplete(f: (sink: Subscriber) => Promise) { ); } -const loadFile = async (path: string): Promise => new File({ path, contents: await readFile(path) }); +const loadFile = (path: string): Promise => + read(join(Deno.cwd(), path)).then((vfile) => { + vfile.path = path; + return vfile; + }); -export const fromGlob = (paths: string | string[]): Observable => +export const fromGlob = (paths: string | string[]): Observable => from(new Glob(paths, {})).pipe(mergeMap(loadFile)); diff --git a/lib/tasks/articles.ts b/lib/tasks/articles.ts index 086cd44..4f2b628 100644 --- a/lib/tasks/articles.ts +++ b/lib/tasks/articles.ts @@ -1,7 +1,5 @@ -import { readFileSync } from "node:fs"; import { basename, dirname, join } from "node:path"; import asciidoctor from "asciidoctor"; -import File from "vinyl"; import { DateTime } from "luxon"; import { unified } from "unified"; import rehypeParse from "rehype-parse"; @@ -20,7 +18,11 @@ import { JSX } from "preact/jsx-runtime"; import { reloadAssets } from "../assets.ts"; import { lastValueFrom, mergeMap, Observable, Subscriber } from "rxjs"; import { dest, fromGlob, onComplete } from "../rx-utils.ts"; -import { Buffer } from "node:buffer"; +import { VFile } from "vfile"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); +const DOCTYPE = encoder.encode(""); const Asciidoctor = asciidoctor(); const EXTENSION_REGISTRY = Asciidoctor.Extensions.create(); @@ -34,7 +36,7 @@ EXTENSION_REGISTRY.inlineMacro("abbr", function () { }); }); -function extractImages(sink: (image: File) => void, articlePath: string) { +function extractImages(sink: Subscriber, articlePath: string) { return function () { return function (tree: any) { visit(tree, "element", function (node: any) { @@ -43,24 +45,30 @@ function extractImages(sink: (image: File) => void, articlePath: string) { } const imagePath = join(dirname(articlePath)); - const image = new File({ + const image = new VFile({ path: join(basename(articlePath, ".asciidoc"), node.properties.src), - contents: readFileSync(imagePath), + value: Deno.readFileSync(imagePath), }); - sink(image); + sink.next(image); }); }; }; } -function renderDocument(root: JSX.Element): Buffer { - return Buffer.concat([Buffer.from(""), Buffer.from(renderToStaticMarkup(root))]); +function renderDocument(root: JSX.Element): Uint8Array { + const result = encoder.encode(renderToStaticMarkup(root)); + const dest = new Uint8Array(DOCTYPE.length + result.length); + + dest.set(DOCTYPE); + dest.set(result, DOCTYPE.length); + + return dest; } -const transformArticle = (sink: (file: File) => void, articles: Article[]) => async (file: File) => { +const transformArticle = (sink: Subscriber, articles: Article[]) => async (file: VFile) => { const slug = basename(file.path, ".asciidoc"); - const document = Asciidoctor.load(file.contents!.toString(), { + const document = Asciidoctor.load(decoder.decode(file.value as Uint8Array), { extension_registry: EXTENSION_REGISTRY, }); const date = DateTime.fromISO(document.getAttribute("docdate", { zone: "UTC" })); @@ -94,13 +102,13 @@ const transformArticle = (sink: (file: File) => void, articles: Article[]) => as }); file.path = join(slug, "index.html"); - file.contents = renderDocument(content); + file.value = renderDocument(content); articles.push(article); - sink(file); + sink.next(file); }; -const finalizeArticles = (articles: Article[]) => (sink: Subscriber) => { +const finalizeArticles = (articles: Article[]) => (sink: Subscriber) => { articles.sort(({ date: a }, { date: b }) => b.diff(a).toMillis()); const contents = renderLayout({ meta: { @@ -112,7 +120,7 @@ const finalizeArticles = (articles: Article[]) => (sink: Subscriber) => { Content: () => renderIndex({ articles }), }); - sink.next(new File({ path: "index.html", contents: renderDocument(contents) })); + sink.next(new VFile({ path: "index.html", value: renderDocument(contents) })); return Promise.resolve(); }; @@ -124,9 +132,9 @@ export const articles = async () => { fromGlob("articles/**/*.asciidoc").pipe( mergeMap( (file) => - new Observable((subscriber) => { + new Observable((subscriber) => { transformArticle( - (f) => subscriber.next(f), + subscriber, articles, )(file).then(() => subscriber.complete()); }), diff --git a/lib/tasks/css.ts b/lib/tasks/css.ts index 163d050..7b47adf 100644 --- a/lib/tasks/css.ts +++ b/lib/tasks/css.ts @@ -3,19 +3,20 @@ import config from "../../postcss.config.ts"; import { dest, fromGlob } from "../rx-utils.ts"; import { lastValueFrom, mergeMap } from "rxjs"; import hashPaths from "../hash.ts"; -import File from "vinyl"; -import { Buffer } from "node:buffer"; +import { VFile } from "vfile"; export const css = async () => { await lastValueFrom( fromGlob("src/index.css").pipe( mergeMap( (file) => - new Promise((resolve) => + new Promise((resolve) => postcss(config.plugins) - .process(file.contents, { from: file.path }) + .process(file.value, { from: file.path }) .then((result: Result) => { - file.contents = Buffer.from(result.css); + const encoder = new TextEncoder(); + + file.value = encoder.encode(result.css); file.path = "index.css"; resolve(file); diff --git a/lib/tasks/fonts.ts b/lib/tasks/fonts.ts index d0a75bb..27f0351 100644 --- a/lib/tasks/fonts.ts +++ b/lib/tasks/fonts.ts @@ -1,17 +1,16 @@ -import File from "vinyl"; import tmp from "tmp"; import hashPaths from "../hash.ts"; import { dest, fromGlob } from "../rx-utils.ts"; import { lastValueFrom, mergeMap } from "rxjs"; -import { Buffer } from "node:buffer"; +import { VFile } from "vfile"; const FONT_PRESETS: { [variant: string]: { ranges: string[] } } = { mono: { ranges: ["20-7F", "2205", "2E22-2E25", "2713", "2717"] }, text: { ranges: ["20-7F", "A0-FF", "2000-206F", "20AC"] }, }; -async function compileFont(font: File): Promise { - const [, variant, weight] = /([A-Z][a-z]+)-(\w+)\.ttf$/.exec(font.basename) as string[]; +async function compileFont(font: VFile): Promise { + const [, variant, weight] = /([A-Z][a-z]+)-(\w+)\.ttf$/.exec(font.basename!) as string[]; const tmpOutput = tmp.fileSync({ discardDescriptor: true }); const unicodes = FONT_PRESETS[variant.toLowerCase()].ranges; @@ -20,8 +19,7 @@ async function compileFont(font: File): Promise { }).output(); font.path = `iosevka-adaedra-${variant.toLowerCase()}-${weight.toLowerCase()}.woff2`; - font.contents = Buffer.from(await Deno.readFile(tmpOutput.name)); - font.base = null; + font.value = await Deno.readFile(tmpOutput.name); await Deno.remove(tmpOutput.name); return font; diff --git a/lib/tasks/images.ts b/lib/tasks/images.ts index 03b3fcf..808f682 100644 --- a/lib/tasks/images.ts +++ b/lib/tasks/images.ts @@ -5,7 +5,7 @@ import { tap } from "rxjs"; export const images = () => fromGlob("src/*.avif") .pipe( - tap((f) => (f.path = f.path.substring(4))), + tap((f) => (f.path = f.basename!)), hashPaths("images.manifest"), ) .forEach(dest("dist/_assets")); diff --git a/lib/tasks/svg.ts b/lib/tasks/svg.ts index c6e8ae8..a3eb190 100644 --- a/lib/tasks/svg.ts +++ b/lib/tasks/svg.ts @@ -4,5 +4,5 @@ import { dest, fromGlob } from "../rx-utils.ts"; // SVG `use` has no way of allowing cross-origin, so we need to keep them with the HTML files. export const svg = () => fromGlob("src/*.svg") - .pipe(tap((f) => (f.path = f.path.substring(4)))) + .pipe(tap((f) => (f.path = f.basename!))) .forEach(dest("dist"));