From f619cd54db9def2f560c25608346b974e4ca6192 Mon Sep 17 00:00:00 2001 From: MapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Sat, 8 Jan 2022 02:36:04 -0600 Subject: [PATCH] use components for non-blocking builds --- .../docs/src/asset-builder/asset-builder.ts | 44 +++--------- packages/docs/src/asset-builder/asset.tsx | 70 +++++++++++++++++++ packages/docs/src/helpers/filesystem.ts | 11 +++ packages/docs/src/html.tsx | 27 ++++--- 4 files changed, 102 insertions(+), 50 deletions(-) create mode 100644 packages/docs/src/asset-builder/asset.tsx create mode 100644 packages/docs/src/helpers/filesystem.ts diff --git a/packages/docs/src/asset-builder/asset-builder.ts b/packages/docs/src/asset-builder/asset-builder.ts index 31018d2..c8524e6 100644 --- a/packages/docs/src/asset-builder/asset-builder.ts +++ b/packages/docs/src/asset-builder/asset-builder.ts @@ -1,10 +1,12 @@ import type { RequestHandler } from "express" import express from "express" import { createHash } from "node:crypto" -import { mkdir, rm, writeFile } from "node:fs/promises" -import { dirname, join, parse } from "node:path" +import { mkdir, rm } from "node:fs/promises" +import { join, parse } from "node:path" +import { Promisable } from "type-fest" +import { ensureWrite, normalizeAsFilePath } from "../helpers/filesystem.js" -type Asset = { +export type Asset = { inputFile: string outputFile: string url: string @@ -20,8 +22,6 @@ export type AssetTransformResult = { } export class AssetBuilder { - private library = new Map() - private constructor( private cacheFolder: string, private transformers: AssetTransformer[], @@ -29,19 +29,14 @@ export class AssetBuilder { static async create(cacheFolder: string, transformers: AssetTransformer[]) { if (process.env.NODE_ENV !== "production") { - await rm(cacheFolder, { recursive: true }).catch() + await rm(cacheFolder, { recursive: true }).catch(() => {}) } await mkdir(cacheFolder, { recursive: true }) return new AssetBuilder(cacheFolder, transformers) } - async build(inputFile: string | URL, name?: string): Promise { - inputFile = normalizeAsFilePath(inputFile) - - const existing = this.library.get(inputFile) - if (existing) { - return existing.url - } + async build(input: Promisable, name?: string): Promise { + const inputFile = normalizeAsFilePath(await input) const transformResult = await this.transform(inputFile) @@ -55,20 +50,8 @@ export class AssetBuilder { const outputFile = join(this.cacheFolder, url) await ensureWrite(outputFile, transformResult.content) - this.library.set(inputFile, { inputFile, outputFile, url }) - return url - } - - local(inputFile: string | URL, name?: string): string { - inputFile = normalizeAsFilePath(inputFile) - - const asset = this.library.get(inputFile) - if (asset) { - return asset.url - } - - throw this.build(inputFile, name) + return { inputFile, outputFile, url } } middleware(): RequestHandler { @@ -86,12 +69,3 @@ export class AssetBuilder { throw new Error(`No transformers found for ${inputFile}`) } } - -async function ensureWrite(file: string, content: string) { - await mkdir(dirname(file), { recursive: true }) - await writeFile(file, content) -} - -function normalizeAsFilePath(file: string | URL) { - return new URL(file, "file:").pathname -} diff --git a/packages/docs/src/asset-builder/asset.tsx b/packages/docs/src/asset-builder/asset.tsx new file mode 100644 index 0000000..d069591 --- /dev/null +++ b/packages/docs/src/asset-builder/asset.tsx @@ -0,0 +1,70 @@ +import React, { ReactNode } from "react" +import { normalizeAsFilePath } from "../helpers/filesystem.js" +import { useAssetBuilder } from "./asset-builder-context.js" +import { Asset, AssetBuilder } from "./asset-builder.js" + +type AssetState = + | { status: "building"; promise: Promise } + | { status: "built"; asset: Asset } + +const cache = new Map() + +function useAssetBuild( + cacheKey: string, + build: (builder: AssetBuilder) => Promise, +) { + const builder = useAssetBuilder() + + const state = cache.get(cacheKey) + if (!state) { + const promise = build(builder).then((asset) => { + cache.set(cacheKey, { status: "built", asset }) + }) + + cache.set(cacheKey, { status: "building", promise }) + throw promise + } + + if (state.status === "building") { + throw state.promise + } + + return state.asset.url +} + +export function LocalFileAsset({ + from, + as: name, + children, +}: { + from: string | URL + as?: string + children: (url: string) => ReactNode +}) { + const inputFile = normalizeAsFilePath(from) + + const url = useAssetBuild(inputFile, (builder) => { + return builder.build(inputFile, name) + }) + + return <>{children(url)} +} + +export function ModuleAsset({ + from, + as: name, + children, +}: { + from: string + as?: string + children: (url: string) => ReactNode +}) { + const cacheKey = `node:${from}` + + const url = useAssetBuild(cacheKey, async (builder) => { + const inputFile = await import.meta.resolve!(from) + return await builder.build(inputFile, name) + }) + + return <>{children(url)} +} diff --git a/packages/docs/src/helpers/filesystem.ts b/packages/docs/src/helpers/filesystem.ts new file mode 100644 index 0000000..ec8fb2c --- /dev/null +++ b/packages/docs/src/helpers/filesystem.ts @@ -0,0 +1,11 @@ +import { mkdir, writeFile } from "node:fs/promises" +import { dirname } from "node:path" + +export async function ensureWrite(file: string, content: string) { + await mkdir(dirname(file), { recursive: true }) + await writeFile(file, content) +} + +export function normalizeAsFilePath(file: string | URL) { + return new URL(file, "file:").pathname +} diff --git a/packages/docs/src/html.tsx b/packages/docs/src/html.tsx index 5063bc8..ddcbe44 100644 --- a/packages/docs/src/html.tsx +++ b/packages/docs/src/html.tsx @@ -1,14 +1,7 @@ import packageJson from "reacord/package.json" import type { ReactNode } from "react" import React from "react" -import { useAssetBuilder } from "./asset-builder/asset-builder-context.js" - -const tailwindCssPath = new URL( - await import.meta.resolve!("tailwindcss/tailwind.css"), -).pathname - -const alpineJs = new URL(await import.meta.resolve!("alpinejs/dist/cdn.js")) - .pathname +import { LocalFileAsset, ModuleAsset } from "./asset-builder/asset.js" export function Html({ title = "Reacord", @@ -19,7 +12,6 @@ export function Html({ description?: string children: ReactNode }) { - const assets = useAssetBuilder() return ( @@ -38,15 +30,20 @@ export function Html({ as="style" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@500&family=Rubik:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap" /> - - + + + {(url) => } + + + + {(url) => } + {title} -