feat(perf:fast-rebuilds): Stop mutating resources param in ComponentResources emitter (#977)

* Stop mutating resources param in ComponentResources emitter

* Add done rebuilding log for fast rebuilds

* Move google font loading to Head component

* Simplify code and fix comment
This commit is contained in:
kabirgh 2024-03-10 00:42:23 +00:00 committed by GitHub
parent 6e0c102970
commit b4236e5142
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 72 additions and 98 deletions

View file

@ -19,6 +19,7 @@ const config: QuartzConfig = {
ignorePatterns: ["private", "templates", ".obsidian"], ignorePatterns: ["private", "templates", ".obsidian"],
defaultDateType: "created", defaultDateType: "created",
theme: { theme: {
fontOrigin: "googleFonts",
cdnCaching: true, cdnCaching: true,
typography: { typography: {
header: "Schibsted Grotesk", header: "Schibsted Grotesk",
@ -72,7 +73,7 @@ const config: QuartzConfig = {
filters: [Plugin.RemoveDrafts()], filters: [Plugin.RemoveDrafts()],
emitters: [ emitters: [
Plugin.AliasRedirects(), Plugin.AliasRedirects(),
Plugin.ComponentResources({ fontOrigin: "googleFonts" }), Plugin.ComponentResources(),
Plugin.ContentPage(), Plugin.ContentPage(),
Plugin.FolderPage(), Plugin.FolderPage(),
Plugin.TagPage(), Plugin.TagPage(),

View file

@ -309,6 +309,8 @@ async function partialRebuildFromEntrypoint(
} }
await rimraf([...destinationsToDelete]) await rimraf([...destinationsToDelete])
console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
toRemove.clear() toRemove.clear()
release() release()
clientRefresh() clientRefresh()

View file

@ -1,6 +1,7 @@
import { i18n } from "../i18n" import { i18n } from "../i18n"
import { FullSlug, joinSegments, pathToRoot } from "../util/path" import { FullSlug, joinSegments, pathToRoot } from "../util/path"
import { JSResourceToScriptElement } from "../util/resources" import { JSResourceToScriptElement } from "../util/resources"
import { googleFontHref } from "../util/theme"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default (() => { export default (() => {
@ -21,10 +22,11 @@ export default (() => {
<head> <head>
<title>{title}</title> <title>{title}</title>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
{cfg.theme.cdnCaching && ( {cfg.theme.cdnCaching && cfg.theme.fontOrigin === "googleFonts" && (
<> <>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" />
<link rel="stylesheet" href={googleFontHref(cfg.theme)} />
</> </>
)} )}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />

View file

@ -8,7 +8,6 @@ import popoverScript from "../../components/scripts/popover.inline"
import styles from "../../styles/custom.scss" import styles from "../../styles/custom.scss"
import popoverStyle from "../../components/styles/popover.scss" import popoverStyle from "../../components/styles/popover.scss"
import { BuildCtx } from "../../util/ctx" import { BuildCtx } from "../../util/ctx"
import { StaticResources } from "../../util/resources"
import { QuartzComponent } from "../../components/types" import { QuartzComponent } from "../../components/types"
import { googleFontHref, joinStyles } from "../../util/theme" import { googleFontHref, joinStyles } from "../../util/theme"
import { Features, transform } from "lightningcss" import { Features, transform } from "lightningcss"
@ -69,13 +68,8 @@ async function joinScripts(scripts: string[]): Promise<string> {
return res.code return res.code
} }
function addGlobalPageResources( function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentResources) {
ctx: BuildCtx,
staticResources: StaticResources,
componentResources: ComponentResources,
) {
const cfg = ctx.cfg.configuration const cfg = ctx.cfg.configuration
const reloadScript = ctx.argv.serve
// popovers // popovers
if (cfg.enablePopovers) { if (cfg.enablePopovers) {
@ -85,12 +79,12 @@ function addGlobalPageResources(
if (cfg.analytics?.provider === "google") { if (cfg.analytics?.provider === "google") {
const tagId = cfg.analytics.tagId const tagId = cfg.analytics.tagId
staticResources.js.push({
src: `https://www.googletagmanager.com/gtag/js?id=${tagId}`,
contentType: "external",
loadTime: "afterDOMReady",
})
componentResources.afterDOMLoaded.push(` componentResources.afterDOMLoaded.push(`
const gtagScript = document.createElement("script")
gtagScript.src = "https://www.googletagmanager.com/gtag/js?id=${tagId}"
gtagScript.async = true
document.head.appendChild(gtagScript)
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); } function gtag() { dataLayer.push(arguments); }
gtag("js", new Date()); gtag("js", new Date());
@ -147,115 +141,72 @@ function addGlobalPageResources(
document.dispatchEvent(event) document.dispatchEvent(event)
`) `)
} }
let wsUrl = `ws://localhost:${ctx.argv.wsPort}`
if (ctx.argv.remoteDevHost) {
wsUrl = `wss://${ctx.argv.remoteDevHost}:${ctx.argv.wsPort}`
}
if (reloadScript) {
staticResources.js.push({
loadTime: "afterDOMReady",
contentType: "inline",
script: `
const socket = new WebSocket('${wsUrl}')
// reload(true) ensures resources like images and scripts are fetched again in firefox
socket.addEventListener('message', () => document.location.reload(true))
`,
})
}
} }
interface Options { // This emitter should not update the `resources` parameter. If it does, partial
fontOrigin: "googleFonts" | "local" // rebuilds may not work as expected.
} export const ComponentResources: QuartzEmitterPlugin = () => {
const defaultOptions: Options = {
fontOrigin: "googleFonts",
}
export const ComponentResources: QuartzEmitterPlugin<Options> = (opts?: Partial<Options>) => {
const { fontOrigin } = { ...defaultOptions, ...opts }
return { return {
name: "ComponentResources", name: "ComponentResources",
getQuartzComponents() { getQuartzComponents() {
return [] return []
}, },
async getDependencyGraph(ctx, content, _resources) { async getDependencyGraph(_ctx, _content, _resources) {
// This emitter adds static resources to the `resources` parameter. One return new DepGraph<FilePath>()
// important resource this emitter adds is the code to start a websocket
// connection and listen to rebuild messages, which triggers a page reload.
// The resources parameter with the reload logic is later used by the
// ContentPage emitter while creating the final html page. In order for
// the reload logic to be included, and so for partial rebuilds to work,
// we need to run this emitter for all markdown files.
const graph = new DepGraph<FilePath>()
for (const [_tree, file] of content) {
const sourcePath = file.data.filePath!
const slug = file.data.slug!
graph.addEdge(sourcePath, joinSegments(ctx.argv.output, slug + ".html") as FilePath)
}
return graph
}, },
async emit(ctx, _content, resources): Promise<FilePath[]> { async emit(ctx, _content, _resources): Promise<FilePath[]> {
const promises: Promise<FilePath>[] = [] const promises: Promise<FilePath>[] = []
const cfg = ctx.cfg.configuration const cfg = ctx.cfg.configuration
// component specific scripts and styles // component specific scripts and styles
const componentResources = getComponentResources(ctx) const componentResources = getComponentResources(ctx)
let googleFontsStyleSheet = "" let googleFontsStyleSheet = ""
if (fontOrigin === "local") { if (cfg.theme.fontOrigin === "local") {
// let the user do it themselves in css // let the user do it themselves in css
} else if (fontOrigin === "googleFonts") { } else if (cfg.theme.fontOrigin === "googleFonts" && !cfg.theme.cdnCaching) {
if (cfg.theme.cdnCaching) { // when cdnCaching is true, we link to google fonts in Head.tsx
resources.css.push(googleFontHref(cfg.theme)) let match
} else {
let match
const fontSourceRegex = /url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g const fontSourceRegex = /url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g
googleFontsStyleSheet = await ( googleFontsStyleSheet = await (
await fetch(googleFontHref(ctx.cfg.configuration.theme)) await fetch(googleFontHref(ctx.cfg.configuration.theme))
).text() ).text()
while ((match = fontSourceRegex.exec(googleFontsStyleSheet)) !== null) { while ((match = fontSourceRegex.exec(googleFontsStyleSheet)) !== null) {
// match[0] is the `url(path)`, match[1] is the `path` // match[0] is the `url(path)`, match[1] is the `path`
const url = match[1] const url = match[1]
// the static name of this file. // the static name of this file.
const [filename, ext] = url.split("/").pop()!.split(".") const [filename, ext] = url.split("/").pop()!.split(".")
googleFontsStyleSheet = googleFontsStyleSheet.replace( googleFontsStyleSheet = googleFontsStyleSheet.replace(
url, url,
`https://${cfg.baseUrl}/static/fonts/${filename}.ttf`, `https://${cfg.baseUrl}/static/fonts/${filename}.ttf`,
) )
promises.push( promises.push(
fetch(url) fetch(url)
.then((res) => { .then((res) => {
if (!res.ok) { if (!res.ok) {
throw new Error(`Failed to fetch font`) throw new Error(`Failed to fetch font`)
} }
return res.arrayBuffer() return res.arrayBuffer()
}) })
.then((buf) => .then((buf) =>
write({ write({
ctx, ctx,
slug: joinSegments("static", "fonts", filename) as FullSlug, slug: joinSegments("static", "fonts", filename) as FullSlug,
ext: `.${ext}`, ext: `.${ext}`,
content: Buffer.from(buf), content: Buffer.from(buf),
}), }),
), ),
) )
}
} }
} }
// important that this goes *after* component scripts // important that this goes *after* component scripts
// as the "nav" event gets triggered here and we should make sure // as the "nav" event gets triggered here and we should make sure
// that everyone else had the chance to register a listener for it // that everyone else had the chance to register a listener for it
addGlobalPageResources(ctx, resources, componentResources) addGlobalPageResources(ctx, componentResources)
const stylesheet = joinStyles( const stylesheet = joinStyles(
ctx.cfg.configuration.theme, ctx.cfg.configuration.theme,

View file

@ -18,6 +18,23 @@ export function getStaticResourcesFromPlugins(ctx: BuildCtx) {
} }
} }
// if serving locally, listen for rebuilds and reload the page
if (ctx.argv.serve) {
const wsUrl = ctx.argv.remoteDevHost
? `wss://${ctx.argv.remoteDevHost}:${ctx.argv.wsPort}`
: `ws://localhost:${ctx.argv.wsPort}`
staticResources.js.push({
loadTime: "afterDOMReady",
contentType: "inline",
script: `
const socket = new WebSocket('${wsUrl}')
// reload(true) ensures resources like images and scripts are fetched again in firefox
socket.addEventListener('message', () => document.location.reload(true))
`,
})
}
return staticResources return staticResources
} }

View file

@ -22,6 +22,7 @@ export interface Theme {
} }
cdnCaching: boolean cdnCaching: boolean
colors: Colors colors: Colors
fontOrigin: "googleFonts" | "local"
} }
export type ThemeKey = keyof Colors export type ThemeKey = keyof Colors