From 4811500b1b9c169aac82254d771677cd6dc2a86c Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Mon, 24 Jul 2023 01:20:43 +0000
Subject: [PATCH] make component resources a proper emitter

---
 quartz/plugins/emitters/componentResources.ts |  154 +++++++++++++++++++++++++
 quartz/plugins/index.ts                       |   77 ------------
 content/features/upcoming features.md         |    1 
 quartz/processors/emit.ts                     |   98 ----------------
 quartz/plugins/emitters/index.ts              |    1 
 quartz/bootstrap-cli.mjs                      |    8 
 quartz.config.ts                              |    1 
 7 files changed, 163 insertions(+), 177 deletions(-)

diff --git a/content/features/upcoming features.md b/content/features/upcoming features.md
index 8e7711e..5c57dba 100644
--- a/content/features/upcoming features.md
+++ b/content/features/upcoming features.md
@@ -4,7 +4,6 @@
 
 ## high priority
 
-- component resources should be emitted by an emitter
 - https://help.obsidian.md/Editing+and+formatting/Tags#Nested+tags nested tags??
 - watch mode for config/source code
 - https://help.obsidian.md/Editing+and+formatting/Basic+formatting+syntax#Task+lists task list styling
diff --git a/quartz.config.ts b/quartz.config.ts
index 0bbc181..5378a8e 100644
--- a/quartz.config.ts
+++ b/quartz.config.ts
@@ -95,6 +95,7 @@
     filters: [Plugin.RemoveDrafts()],
     emitters: [
       Plugin.AliasRedirects(),
+      Plugin.ComponentResources(),
       Plugin.ContentPage({
         ...sharedPageComponents,
         ...contentPageLayout,
diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs
index 475e624..aa26ffc 100755
--- a/quartz/bootstrap-cli.mjs
+++ b/quartz/bootstrap-cli.mjs
@@ -82,8 +82,8 @@
   bundleInfo: {
     boolean: true,
     default: false,
-    describe: "show detailed bundle information"
-  }
+    describe: "show detailed bundle information",
+  },
 }
 
 function escapePath(fp) {
@@ -351,9 +351,9 @@
       console.log(
         `Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes(
           meta.bytes,
-        )})`)
-      console.log(await esbuild.analyzeMetafile(result.metafile, { color: true })
+        )})`,
       )
+      console.log(await esbuild.analyzeMetafile(result.metafile, { color: true }))
     }
 
     const { default: buildQuartz } = await import(cacheFile)
diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts
new file mode 100644
index 0000000..99f9657
--- /dev/null
+++ b/quartz/plugins/emitters/componentResources.ts
@@ -0,0 +1,154 @@
+import { FilePath, ServerSlug } from "../../path"
+import { PluginTypes, QuartzEmitterPlugin } from "../types"
+
+// @ts-ignore
+import spaRouterScript from "../../components/scripts/spa.inline"
+// @ts-ignore
+import plausibleScript from "../../components/scripts/plausible.inline"
+// @ts-ignore
+import popoverScript from "../../components/scripts/popover.inline"
+import styles from "../../styles/base.scss"
+import popoverStyle from "../../components/styles/popover.scss"
+import { BuildCtx } from "../../ctx"
+import { StaticResources } from "../../resources"
+import { QuartzComponent } from "../../components/types"
+import { googleFontHref, joinStyles } from "../../theme"
+
+type ComponentResources = {
+  css: string[]
+  beforeDOMLoaded: string[]
+  afterDOMLoaded: string[]
+}
+
+function getComponentResources(plugins: PluginTypes): ComponentResources {
+  const allComponents: Set<QuartzComponent> = new Set()
+  for (const emitter of plugins.emitters) {
+    const components = emitter.getQuartzComponents()
+    for (const component of components) {
+      allComponents.add(component)
+    }
+  }
+
+  const componentResources = {
+    css: new Set<string>(),
+    beforeDOMLoaded: new Set<string>(),
+    afterDOMLoaded: new Set<string>(),
+  }
+
+  for (const component of allComponents) {
+    const { css, beforeDOMLoaded, afterDOMLoaded } = component
+    if (css) {
+      componentResources.css.add(css)
+    }
+    if (beforeDOMLoaded) {
+      componentResources.beforeDOMLoaded.add(beforeDOMLoaded)
+    }
+    if (afterDOMLoaded) {
+      componentResources.afterDOMLoaded.add(afterDOMLoaded)
+    }
+  }
+
+  return {
+    css: [...componentResources.css],
+    beforeDOMLoaded: [...componentResources.beforeDOMLoaded],
+    afterDOMLoaded: [...componentResources.afterDOMLoaded],
+  }
+}
+
+function joinScripts(scripts: string[]): string {
+  // wrap with iife to prevent scope collision
+  return scripts.map((script) => `(function () {${script}})();`).join("\n")
+}
+
+function addGlobalPageResources(
+  ctx: BuildCtx,
+  staticResources: StaticResources,
+  componentResources: ComponentResources,
+) {
+  const cfg = ctx.cfg.configuration
+  const reloadScript = ctx.argv.serve
+  staticResources.css.push(googleFontHref(cfg.theme))
+
+  // popovers
+  if (cfg.enablePopovers) {
+    componentResources.afterDOMLoaded.push(popoverScript)
+    componentResources.css.push(popoverStyle)
+  }
+
+  if (cfg.analytics?.provider === "google") {
+    const tagId = cfg.analytics.tagId
+    staticResources.js.push({
+      src: `https://www.googletagmanager.com/gtag/js?id=${tagId}`,
+      contentType: "external",
+      loadTime: "afterDOMReady",
+    })
+    componentResources.afterDOMLoaded.push(`
+      window.dataLayer = window.dataLayer || [];
+      function gtag() { dataLayer.push(arguments); }
+      gtag(\`js\`, new Date());
+      gtag(\`config\`, \`${tagId}\`, { send_page_view: false });
+  
+      document.addEventListener(\`nav\`, () => {
+        gtag(\`event\`, \`page_view\`, {
+          page_title: document.title,
+          page_location: location.href,
+        });
+      });`)
+  } else if (cfg.analytics?.provider === "plausible") {
+    componentResources.afterDOMLoaded.push(plausibleScript)
+  }
+
+  // spa
+  if (cfg.enableSPA) {
+    componentResources.afterDOMLoaded.push(spaRouterScript)
+  } else {
+    componentResources.afterDOMLoaded.push(`
+        window.spaNavigate = (url, _) => window.location.assign(url)
+        const event = new CustomEvent("nav", { detail: { slug: document.body.dataset.slug } })
+        document.dispatchEvent(event)`)
+  }
+
+  if (reloadScript) {
+    staticResources.js.push({
+      loadTime: "afterDOMReady",
+      contentType: "inline",
+      script: `
+          const socket = new WebSocket('ws://localhost:3001')
+          socket.addEventListener('message', () => document.location.reload())
+        `,
+    })
+  }
+}
+
+export const ComponentResources: QuartzEmitterPlugin = () => ({
+  name: "ComponentResources",
+  getQuartzComponents() {
+    return []
+  },
+  async emit(ctx, _content, resources, emit): Promise<FilePath[]> {
+    // component specific scripts and styles
+    const componentResources = getComponentResources(ctx.cfg.plugins)
+    // 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
+    addGlobalPageResources(ctx, resources, componentResources)
+    const fps = await Promise.all([
+      emit({
+        slug: "index" as ServerSlug,
+        ext: ".css",
+        content: joinStyles(ctx.cfg.configuration.theme, styles, ...componentResources.css),
+      }),
+      emit({
+        slug: "prescript" as ServerSlug,
+        ext: ".js",
+        content: joinScripts(componentResources.beforeDOMLoaded),
+      }),
+      emit({
+        slug: "postscript" as ServerSlug,
+        ext: ".js",
+        content: joinScripts(componentResources.afterDOMLoaded),
+      }),
+    ])
+    return fps
+  },
+})
diff --git a/quartz/plugins/emitters/index.ts b/quartz/plugins/emitters/index.ts
index bb18361..d1149f7 100644
--- a/quartz/plugins/emitters/index.ts
+++ b/quartz/plugins/emitters/index.ts
@@ -5,3 +5,4 @@
 export { AliasRedirects } from "./aliases"
 export { Assets } from "./assets"
 export { Static } from "./static"
+export { ComponentResources } from "./componentResources"
\ No newline at end of file
diff --git a/quartz/plugins/index.ts b/quartz/plugins/index.ts
index a6231db..a8208e3 100644
--- a/quartz/plugins/index.ts
+++ b/quartz/plugins/index.ts
@@ -1,82 +1,7 @@
-import { GlobalConfiguration } from "../cfg"
-import { QuartzComponent } from "../components/types"
 import { StaticResources } from "../resources"
-import { joinStyles } from "../theme"
-import { EmitCallback, PluginTypes } from "./types"
-import styles from "../styles/base.scss"
+import { PluginTypes } from "./types"
 import { FilePath, ServerSlug } from "../path"
 
-export type ComponentResources = {
-  css: string[]
-  beforeDOMLoaded: string[]
-  afterDOMLoaded: string[]
-}
-
-export function getComponentResources(plugins: PluginTypes): ComponentResources {
-  const allComponents: Set<QuartzComponent> = new Set()
-  for (const emitter of plugins.emitters) {
-    const components = emitter.getQuartzComponents()
-    for (const component of components) {
-      allComponents.add(component)
-    }
-  }
-
-  const componentResources = {
-    css: new Set<string>(),
-    beforeDOMLoaded: new Set<string>(),
-    afterDOMLoaded: new Set<string>(),
-  }
-
-  for (const component of allComponents) {
-    const { css, beforeDOMLoaded, afterDOMLoaded } = component
-    if (css) {
-      componentResources.css.add(css)
-    }
-    if (beforeDOMLoaded) {
-      componentResources.beforeDOMLoaded.add(beforeDOMLoaded)
-    }
-    if (afterDOMLoaded) {
-      componentResources.afterDOMLoaded.add(afterDOMLoaded)
-    }
-  }
-
-  return {
-    css: [...componentResources.css],
-    beforeDOMLoaded: [...componentResources.beforeDOMLoaded],
-    afterDOMLoaded: [...componentResources.afterDOMLoaded],
-  }
-}
-
-function joinScripts(scripts: string[]): string {
-  // wrap with iife to prevent scope collision
-  return scripts.map((script) => `(function () {${script}})();`).join("\n")
-}
-
-export async function emitComponentResources(
-  cfg: GlobalConfiguration,
-  res: ComponentResources,
-  emit: EmitCallback,
-): Promise<FilePath[]> {
-  const fps = await Promise.all([
-    emit({
-      slug: "index" as ServerSlug,
-      ext: ".css",
-      content: joinStyles(cfg.theme, styles, ...res.css),
-    }),
-    emit({
-      slug: "prescript" as ServerSlug,
-      ext: ".js",
-      content: joinScripts(res.beforeDOMLoaded),
-    }),
-    emit({
-      slug: "postscript" as ServerSlug,
-      ext: ".js",
-      content: joinScripts(res.afterDOMLoaded),
-    }),
-  ])
-  return fps
-}
-
 export function getStaticResourcesFromPlugins(plugins: PluginTypes) {
   const staticResources: StaticResources = {
     css: [],
diff --git a/quartz/processors/emit.ts b/quartz/processors/emit.ts
index ea7fda9..570f505 100644
--- a/quartz/processors/emit.ts
+++ b/quartz/processors/emit.ts
@@ -1,89 +1,14 @@
 import path from "path"
 import fs from "fs"
 import { PerfTimer } from "../perf"
-import {
-  ComponentResources,
-  emitComponentResources,
-  getComponentResources,
-  getStaticResourcesFromPlugins,
-} from "../plugins"
+import { getStaticResourcesFromPlugins } from "../plugins"
 import { EmitCallback } from "../plugins/types"
 import { ProcessedContent } from "../plugins/vfile"
 import { FilePath } from "../path"
-
-// @ts-ignore
-import spaRouterScript from "../components/scripts/spa.inline"
-// @ts-ignore
-import plausibleScript from "../components/scripts/plausible.inline"
-// @ts-ignore
-import popoverScript from "../components/scripts/popover.inline"
-import popoverStyle from "../components/styles/popover.scss"
-import { StaticResources } from "../resources"
 import { QuartzLogger } from "../log"
-import { googleFontHref } from "../theme"
 import { trace } from "../trace"
 import { BuildCtx } from "../ctx"
 
-function addGlobalPageResources(
-  ctx: BuildCtx,
-  staticResources: StaticResources,
-  componentResources: ComponentResources,
-) {
-  const cfg = ctx.cfg.configuration
-  const reloadScript = ctx.argv.serve
-  staticResources.css.push(googleFontHref(cfg.theme))
-
-  // popovers
-  if (cfg.enablePopovers) {
-    componentResources.afterDOMLoaded.push(popoverScript)
-    componentResources.css.push(popoverStyle)
-  }
-
-  if (cfg.analytics?.provider === "google") {
-    const tagId = cfg.analytics.tagId
-    staticResources.js.push({
-      src: `https://www.googletagmanager.com/gtag/js?id=${tagId}`,
-      contentType: "external",
-      loadTime: "afterDOMReady",
-    })
-    componentResources.afterDOMLoaded.push(`
-    window.dataLayer = window.dataLayer || [];
-    function gtag() { dataLayer.push(arguments); }
-    gtag(\`js\`, new Date());
-    gtag(\`config\`, \`${tagId}\`, { send_page_view: false });
-
-    document.addEventListener(\`nav\`, () => {
-      gtag(\`event\`, \`page_view\`, {
-        page_title: document.title,
-        page_location: location.href,
-      });
-    });`)
-  } else if (cfg.analytics?.provider === "plausible") {
-    componentResources.afterDOMLoaded.push(plausibleScript)
-  }
-
-  // spa
-  if (cfg.enableSPA) {
-    componentResources.afterDOMLoaded.push(spaRouterScript)
-  } else {
-    componentResources.afterDOMLoaded.push(`
-      window.spaNavigate = (url, _) => window.location.assign(url)
-      const event = new CustomEvent("nav", { detail: { slug: document.body.dataset.slug } })
-      document.dispatchEvent(event)`)
-  }
-
-  if (reloadScript) {
-    staticResources.js.push({
-      loadTime: "afterDOMReady",
-      contentType: "inline",
-      script: `
-        const socket = new WebSocket('ws://localhost:3001')
-        socket.addEventListener('message', () => document.location.reload())
-      `,
-    })
-  }
-}
-
 export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
   const { argv, cfg } = ctx
   const perf = new PerfTimer()
@@ -98,27 +23,8 @@
     return pathToPage
   }
 
-  // initialize from plugins
-  const staticResources = getStaticResourcesFromPlugins(cfg.plugins)
-
-  // component specific scripts and styles
-  const componentResources = getComponentResources(cfg.plugins)
-
-  // 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
-  addGlobalPageResources(ctx, staticResources, componentResources)
-
   let emittedFiles = 0
-  const emittedResources = await emitComponentResources(cfg.configuration, componentResources, emit)
-  if (argv.verbose) {
-    for (const file of emittedResources) {
-      emittedFiles += 1
-      console.log(`[emit:Resources] ${file}`)
-    }
-  }
-
-  // emitter plugins
+  const staticResources = getStaticResourcesFromPlugins(cfg.plugins)
   for (const emitter of cfg.plugins.emitters) {
     try {
       const emitted = await emitter.emit(ctx, content, staticResources, emit)

--
Gitblit v1.10.0