From 330e322e48ba8890b11f4de0a84f3cffaad22096 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Thu, 8 Feb 2024 02:52:55 -0500 Subject: [PATCH] feat(fonts): fetch before build (#817) * feat: fetch google fonts before build Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * Update quartz/plugins/emitters/componentResources.ts * fix: fetching wolff2 Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: remove request stylesheet Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix: race condition Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: remove preconnect for static fonts since we are already downloading fonts into public folder Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: remove deadcode Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: add options to gate for cdn caching Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Jacky Zhao * chore: apply jacky's suggestion Co-authored-by: Jacky Zhao * chore: add docs and only use one promise Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * fix: fmt Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> * chore: remove deadcode * chore: final touches Co-authored-by: Jacky Zhao * revert: changes in theme.ts * fix: styles and remove deadcode Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --------- Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> Co-authored-by: Jacky Zhao --- docs/configuration.md | 1 + quartz.config.ts | 1 + quartz/components/Head.tsx | 8 ++- quartz/plugins/emitters/componentResources.ts | 62 ++++++++++++++++--- quartz/plugins/emitters/helpers.ts | 2 +- quartz/util/theme.ts | 1 + 6 files changed, 64 insertions(+), 11 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 33d5a574..b0c85062 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -34,6 +34,7 @@ This part of the configuration concerns anything that can affect the whole site. - `ignorePatterns`: a list of [glob]() patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. See [[private pages]] for more details. - `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings. - `theme`: configure how the site looks. + - `cdnCaching`: Whether to use Google CDN to cache the fonts (generally will be faster). Disable this if you want Quartz to be self-contained. Default to `true` - `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here. - `header`: Font to use for headers - `code`: Font for inline and block quotes. diff --git a/quartz.config.ts b/quartz.config.ts index 4921a118..4e36e940 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -14,6 +14,7 @@ const config: QuartzConfig = { ignorePatterns: ["private", "templates", ".obsidian"], defaultDateType: "created", theme: { + cdnCaching: true, typography: { header: "Schibsted Grotesk", body: "Source Sans Pro", diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index b94909cc..dae81c73 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -30,8 +30,12 @@ export default (() => { - - + {cfg.theme.cdnCaching && ( + <> + + + + )} {css.map((href) => ( ))} diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index 5eb9718a..666c1d26 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -1,4 +1,4 @@ -import { FilePath, FullSlug } from "../../util/path" +import { FilePath, FullSlug, joinSegments } from "../../util/path" import { QuartzEmitterPlugin } from "../types" // @ts-ignore @@ -172,27 +172,72 @@ export const ComponentResources: QuartzEmitterPlugin = (opts?: Partial< return [] }, async emit(ctx, _content, resources): Promise { + const promises: Promise[] = [] + const cfg = ctx.cfg.configuration // component specific scripts and styles const componentResources = getComponentResources(ctx) // important that this goes *after* component scripts // as the "nav" event gets triggered here and we should make sure // that everyone else had the chance to register a listener for it - if (fontOrigin === "googleFonts") { - resources.css.push(googleFontHref(ctx.cfg.configuration.theme)) - } else if (fontOrigin === "local") { + let googleFontsStyleSheet = "" + if (fontOrigin === "local") { // let the user do it themselves in css + } else if (fontOrigin === "googleFonts") { + if (cfg.theme.cdnCaching) { + resources.css.push(googleFontHref(cfg.theme)) + } else { + let match + + const fontSourceRegex = /url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g + + googleFontsStyleSheet = await ( + await fetch(googleFontHref(ctx.cfg.configuration.theme)) + ).text() + + while ((match = fontSourceRegex.exec(googleFontsStyleSheet)) !== null) { + // match[0] is the `url(path)`, match[1] is the `path` + const url = match[1] + // the static name of this file. + const [filename, ext] = url.split("/").pop()!.split(".") + + googleFontsStyleSheet = googleFontsStyleSheet.replace(url, `/fonts/${filename}.ttf`) + + promises.push( + fetch(url) + .then((res) => { + if (!res.ok) { + throw new Error(`Failed to fetch font`) + } + return res.arrayBuffer() + }) + .then((buf) => + write({ + ctx, + slug: joinSegments("fonts", filename) as FullSlug, + ext: `.${ext}`, + content: Buffer.from(buf), + }), + ), + ) + } + } } addGlobalPageResources(ctx, resources, componentResources) - const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles) + const stylesheet = joinStyles( + ctx.cfg.configuration.theme, + ...componentResources.css, + googleFontsStyleSheet, + styles, + ) const [prescript, postscript] = await Promise.all([ joinScripts(componentResources.beforeDOMLoaded), joinScripts(componentResources.afterDOMLoaded), ]) - const fps = await Promise.all([ + promises.push( write({ ctx, slug: "index" as FullSlug, @@ -223,8 +268,9 @@ export const ComponentResources: QuartzEmitterPlugin = (opts?: Partial< ext: ".js", content: postscript, }), - ]) - return fps + ) + + return await Promise.all(promises) }, } } diff --git a/quartz/plugins/emitters/helpers.ts b/quartz/plugins/emitters/helpers.ts index ef1d1c35..523151c2 100644 --- a/quartz/plugins/emitters/helpers.ts +++ b/quartz/plugins/emitters/helpers.ts @@ -7,7 +7,7 @@ type WriteOptions = { ctx: BuildCtx slug: FullSlug ext: `.${string}` | "" - content: string + content: string | Buffer } export const write = async ({ ctx, slug, ext, content }: WriteOptions): Promise => { diff --git a/quartz/util/theme.ts b/quartz/util/theme.ts index 47951c4f..bd0da5fb 100644 --- a/quartz/util/theme.ts +++ b/quartz/util/theme.ts @@ -15,6 +15,7 @@ export interface Theme { body: string code: string } + cdnCaching: boolean colors: { lightMode: ColorScheme darkMode: ColorScheme