make component resources a proper emitter
1 files added
6 files modified
| | |
| | | |
| | | ## 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 |
| | |
| | | filters: [Plugin.RemoveDrafts()], |
| | | emitters: [ |
| | | Plugin.AliasRedirects(), |
| | | Plugin.ComponentResources(), |
| | | Plugin.ContentPage({ |
| | | ...sharedPageComponents, |
| | | ...contentPageLayout, |
| | |
| | | bundleInfo: { |
| | | boolean: true, |
| | | default: false, |
| | | describe: "show detailed bundle information" |
| | | } |
| | | describe: "show detailed bundle information", |
| | | }, |
| | | } |
| | | |
| | | function escapePath(fp) { |
| | |
| | | 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) |
| New file |
| | |
| | | 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 |
| | | }, |
| | | }) |
| | |
| | | export { AliasRedirects } from "./aliases" |
| | | export { Assets } from "./assets" |
| | | export { Static } from "./static" |
| | | export { ComponentResources } from "./componentResources" |
| | |
| | | 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: [], |
| | |
| | | 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() |
| | |
| | | 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) |