diff --git a/doc/src/middlewares/docLatestRewrite.ts b/doc/src/middlewares/docLatestRewrite.ts index aa92f89..c71779d 100644 --- a/doc/src/middlewares/docLatestRewrite.ts +++ b/doc/src/middlewares/docLatestRewrite.ts @@ -3,7 +3,7 @@ import { Next } from "$dep/frugal/mod.ts"; import { getToc, latest } from "../pages/doc/toc.ts"; export async function docLatestRewrite(context: Context, next: Next) { - const matches = context.request.url.match(/\/doc@latest(:\/(.*))?$/); + const matches = context.request.url.match(/\/doc@latest(?:\/(.*))?$/); if (matches) { const toc = await getToc(context.resolve); const redirectUrl = context.request.url.replace("/doc@latest", `/doc@${latest(toc)}`); diff --git a/src/plugin/googleFonts.ts b/src/plugin/googleFonts.ts index 6fc7999..6fdeb42 100644 --- a/src/plugin/googleFonts.ts +++ b/src/plugin/googleFonts.ts @@ -15,8 +15,12 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { return { name: "frugal:googleFonts", setup(build) { - build.onResolve({ filter: /^\/\/fonts.googleapis.com\//, namespace: "https" }, async (args) => { - const name = (await xxhash.create()).update(args.path).digest("hex").toString(); + build.onResolve({ + filter: /^\/\/fonts.googleapis.com\//, + namespace: "https", + }, async (args) => { + const name = (await xxhash.create()).update(args.path).digest("hex") + .toString(); return { path: `/googlefonts-${name}.css`, namespace: "virtual", @@ -25,7 +29,10 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { }); if (type === "external") { - build.onLoad({ filter: /^\/googlefonts-.*\.css$/, namespace: "virtual" }, async (args) => { + build.onLoad({ + filter: /^\/googlefonts-.*\.css$/, + namespace: "virtual", + }, async (args) => { const url = args.pluginData.url; if (!url) { return; @@ -43,7 +50,10 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { return { contents: css, loader: "css" }; }); - build.onResolve({ filter: /^\/\/fonts.gstatic.com\//, namespace: "https" }, () => { + build.onResolve({ + filter: /^\/\/fonts.gstatic.com\//, + namespace: "https", + }, () => { return { external: true }; }); } @@ -53,7 +63,10 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { return { external: true }; }); - build.onLoad({ filter: /^\/googlefonts-.*\.css$/, namespace: "virtual" }, async (args) => { + build.onLoad({ + filter: /^\/googlefonts-.*\.css$/, + namespace: "virtual", + }, async (args) => { const url = args.pluginData.url; if (!url) { return; @@ -73,7 +86,8 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { for (const url of urls) { const matched = url.match(/src\s*:\s*url\((.*?)\)/); if (matched) { - const name = (await xxhash.create()).update(matched[1]).digest("hex") + const name = (await xxhash.create()).update(matched[1]) + .digest("hex") .toString(); const ext = path.extname(matched[1]); @@ -82,7 +96,10 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { try { await fs.ensureDir(path.dirname(path.fromFileUrl(fontUrl))); - const file = await Deno.open(fontUrl, { createNew: true, write: true }); + const file = await Deno.open(fontUrl, { + createNew: true, + write: true, + }); try { log(`Loading font ${++index} of ${urls.length}`, { scope: "frugal:googleFonts", @@ -91,7 +108,9 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { const response = await fetch(matched[1]); const readableStream = response.body?.getReader(); if (readableStream) { - const reader = streams.readerFromStreamReader(readableStream); + const reader = streams.readerFromStreamReader( + readableStream, + ); await streams.copy(reader, file); } @@ -104,9 +123,18 @@ export function googleFonts({ type = "local" }: Config = {}): Plugin { } } - const fontDest = new URL(`fonts/${name}${ext}`, frugal.config.publicdir); + const fontDest = new URL( + `fonts/${name}${ext}`, + frugal.config.publicdir, + ); await fs.ensureDir(path.dirname(path.fromFileUrl(fontDest))); - await fs.copy(fontUrl, fontDest); + try { + await fs.copy(fontUrl, fontDest); + } catch (error) { + if (!(error instanceof Deno.errors.AlreadyExists)) { + throw error; + } + } css = css.replace(matched[1], `/fonts/${name}${ext}`); } } diff --git a/src/plugin/svg.ts b/src/plugin/svg.ts index 602b14f..677fd4f 100644 --- a/src/plugin/svg.ts +++ b/src/plugin/svg.ts @@ -28,10 +28,14 @@ export function svg( build.onLoad({ filter }, async (args) => { const url = frugal.url(args); - const { meta: { spritesheet, id, viewBox } } = await svgBuilder.symbol(path.fromFileUrl(url)); + const { meta: { spritesheet, id, viewBox } } = await svgBuilder + .symbol(path.fromFileUrl(url)); return { - contents: JSON.stringify({ href: `/${outdir}${spritesheet}#${id}`, viewBox }), + contents: JSON.stringify({ + href: `/${outdir}${spritesheet}#${id}`, + viewBox, + }), loader: "json", }; }); @@ -58,8 +62,7 @@ export function svg( for (const [name, symbols] of Object.entries(spritesheets)) { const svg = svgBuilder.spritesheet(symbols, frugal.config); - const hash = (await xxhash.create()).update(svg).digest("hex").toString(); - const svgPath = path.join("svg", `${name}-${hash}.svg`); + const svgPath = path.join("svg", `${name}`); const svgUrl = new URL(svgPath, frugal.config.publicdir); const assetPath = `/${svgPath}`; @@ -76,7 +79,12 @@ export function svg( }; } -type MetaSymbol = { id: string; viewBox: string; spritesheet: string; path: string }; +type MetaSymbol = { + id: string; + viewBox: string; + spritesheet: string; + path: string; +}; type SvgSymbol = { attributes: Record; @@ -91,12 +99,27 @@ type Symbol = { }; class SvgBuilder { + #spritesheetName: Map; #symbolCache: Map; #spritesheetCache: Map; constructor() { this.#symbolCache = new Map(); this.#spritesheetCache = new Map(); + this.#spritesheetName = new Map(); + } + + async #getSpritesheetName(svgPath: string) { + const baseName = path.basename(path.dirname(svgPath)); + + const name = this.#spritesheetName.get(baseName); + if (name != undefined) { + return name; + } + + const hash = (await xxhash.create()).update(baseName).update(String(Date.now())) + .digest("hex").toString(); + return `${name}-${hash}.svg`; } async symbol(svgPath: string) { @@ -109,7 +132,10 @@ class SvgBuilder { return cached; } - log(`generating svg symbol from "${svgPath}"`, { level: "debug", scope: "frugal:svg" }); + log(`generating svg symbol from "${svgPath}"`, { + level: "debug", + scope: "frugal:svg", + }); const doc = new dom.DOMParser().parseFromString(svgString, "text/html")!; const svg = doc.querySelector("svg")!; @@ -118,7 +144,7 @@ class SvgBuilder { const height = svg.getAttribute("height"); const id = `${path.basename(svgPath, path.extname(svgPath))}-${svgHash}`; - const spritesheet = path.basename(path.dirname(svgPath)); + const spritesheet = await this.#getSpritesheetName(svgPath); const metaSymbol: MetaSymbol = { id, @@ -167,7 +193,10 @@ class SvgBuilder { return cached; } - log(`generating spritesheet "${symbols[0].meta.spritesheet}"`, { level: "debug", scope: "frugal:svg" }); + log(`generating spritesheet "${symbols[0].meta.spritesheet}"`, { + level: "debug", + scope: "frugal:svg", + }); const seenId: Record> = {}; const svgContent = [];