make component resources a proper emitter
This commit is contained in:
		
							
								
								
									
										154
									
								
								quartz/plugins/emitters/componentResources.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								quartz/plugins/emitters/componentResources.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
|   }, | ||||
| }) | ||||
| @@ -5,3 +5,4 @@ export { ContentIndex } from "./contentIndex" | ||||
| export { AliasRedirects } from "./aliases" | ||||
| export { Assets } from "./assets" | ||||
| export { Static } from "./static" | ||||
| export { ComponentResources } from "./componentResources" | ||||
		Reference in New Issue
	
	Block a user