perf: incremental rebuild (--fastRebuild v2 but default) (#1841)
* checkpoint * incremental all the things * properly splice changes array * smol doc update * update docs * make fancy logger dumb in ci
This commit is contained in:
		@@ -7,7 +7,6 @@ import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../
 | 
			
		||||
import { FullPageLayout } from "../../cfg"
 | 
			
		||||
import path from "path"
 | 
			
		||||
import {
 | 
			
		||||
  FilePath,
 | 
			
		||||
  FullSlug,
 | 
			
		||||
  SimpleSlug,
 | 
			
		||||
  stripSlashes,
 | 
			
		||||
@@ -18,13 +17,89 @@ import {
 | 
			
		||||
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
 | 
			
		||||
import { FolderContent } from "../../components"
 | 
			
		||||
import { write } from "./helpers"
 | 
			
		||||
import { i18n } from "../../i18n"
 | 
			
		||||
import DepGraph from "../../depgraph"
 | 
			
		||||
 | 
			
		||||
import { i18n, TRANSLATIONS } from "../../i18n"
 | 
			
		||||
import { BuildCtx } from "../../util/ctx"
 | 
			
		||||
import { StaticResources } from "../../util/resources"
 | 
			
		||||
interface FolderPageOptions extends FullPageLayout {
 | 
			
		||||
  sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function* processFolderInfo(
 | 
			
		||||
  ctx: BuildCtx,
 | 
			
		||||
  folderInfo: Record<SimpleSlug, ProcessedContent>,
 | 
			
		||||
  allFiles: QuartzPluginData[],
 | 
			
		||||
  opts: FullPageLayout,
 | 
			
		||||
  resources: StaticResources,
 | 
			
		||||
) {
 | 
			
		||||
  for (const [folder, folderContent] of Object.entries(folderInfo) as [
 | 
			
		||||
    SimpleSlug,
 | 
			
		||||
    ProcessedContent,
 | 
			
		||||
  ][]) {
 | 
			
		||||
    const slug = joinSegments(folder, "index") as FullSlug
 | 
			
		||||
    const [tree, file] = folderContent
 | 
			
		||||
    const cfg = ctx.cfg.configuration
 | 
			
		||||
    const externalResources = pageResources(pathToRoot(slug), resources)
 | 
			
		||||
    const componentData: QuartzComponentProps = {
 | 
			
		||||
      ctx,
 | 
			
		||||
      fileData: file.data,
 | 
			
		||||
      externalResources,
 | 
			
		||||
      cfg,
 | 
			
		||||
      children: [],
 | 
			
		||||
      tree,
 | 
			
		||||
      allFiles,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const content = renderPage(cfg, slug, componentData, opts, externalResources)
 | 
			
		||||
    yield write({
 | 
			
		||||
      ctx,
 | 
			
		||||
      content,
 | 
			
		||||
      slug,
 | 
			
		||||
      ext: ".html",
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function computeFolderInfo(
 | 
			
		||||
  folders: Set<SimpleSlug>,
 | 
			
		||||
  content: ProcessedContent[],
 | 
			
		||||
  locale: keyof typeof TRANSLATIONS,
 | 
			
		||||
): Record<SimpleSlug, ProcessedContent> {
 | 
			
		||||
  // Create default folder descriptions
 | 
			
		||||
  const folderInfo: Record<SimpleSlug, ProcessedContent> = Object.fromEntries(
 | 
			
		||||
    [...folders].map((folder) => [
 | 
			
		||||
      folder,
 | 
			
		||||
      defaultProcessedContent({
 | 
			
		||||
        slug: joinSegments(folder, "index") as FullSlug,
 | 
			
		||||
        frontmatter: {
 | 
			
		||||
          title: `${i18n(locale).pages.folderContent.folder}: ${folder}`,
 | 
			
		||||
          tags: [],
 | 
			
		||||
        },
 | 
			
		||||
      }),
 | 
			
		||||
    ]),
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // Update with actual content if available
 | 
			
		||||
  for (const [tree, file] of content) {
 | 
			
		||||
    const slug = stripSlashes(simplifySlug(file.data.slug!)) as SimpleSlug
 | 
			
		||||
    if (folders.has(slug)) {
 | 
			
		||||
      folderInfo[slug] = [tree, file]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return folderInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _getFolders(slug: FullSlug): SimpleSlug[] {
 | 
			
		||||
  var folderName = path.dirname(slug ?? "") as SimpleSlug
 | 
			
		||||
  const parentFolderNames = [folderName]
 | 
			
		||||
 | 
			
		||||
  while (folderName !== ".") {
 | 
			
		||||
    folderName = path.dirname(folderName ?? "") as SimpleSlug
 | 
			
		||||
    parentFolderNames.push(folderName)
 | 
			
		||||
  }
 | 
			
		||||
  return parentFolderNames
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (userOpts) => {
 | 
			
		||||
  const opts: FullPageLayout = {
 | 
			
		||||
    ...sharedPageComponents,
 | 
			
		||||
@@ -53,22 +128,6 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (user
 | 
			
		||||
        Footer,
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    async getDependencyGraph(_ctx, content, _resources) {
 | 
			
		||||
      // Example graph:
 | 
			
		||||
      // nested/file.md --> nested/index.html
 | 
			
		||||
      // nested/file2.md ------^
 | 
			
		||||
      const graph = new DepGraph<FilePath>()
 | 
			
		||||
 | 
			
		||||
      content.map(([_tree, vfile]) => {
 | 
			
		||||
        const slug = vfile.data.slug
 | 
			
		||||
        const folderName = path.dirname(slug ?? "") as SimpleSlug
 | 
			
		||||
        if (slug && folderName !== "." && folderName !== "tags") {
 | 
			
		||||
          graph.addEdge(vfile.data.filePath!, joinSegments(folderName, "index.html") as FilePath)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      return graph
 | 
			
		||||
    },
 | 
			
		||||
    async *emit(ctx, content, resources) {
 | 
			
		||||
      const allFiles = content.map((c) => c[1].data)
 | 
			
		||||
      const cfg = ctx.cfg.configuration
 | 
			
		||||
@@ -83,59 +142,29 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (user
 | 
			
		||||
        }),
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      const folderDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
 | 
			
		||||
        [...folders].map((folder) => [
 | 
			
		||||
          folder,
 | 
			
		||||
          defaultProcessedContent({
 | 
			
		||||
            slug: joinSegments(folder, "index") as FullSlug,
 | 
			
		||||
            frontmatter: {
 | 
			
		||||
              title: `${i18n(cfg.locale).pages.folderContent.folder}: ${folder}`,
 | 
			
		||||
              tags: [],
 | 
			
		||||
            },
 | 
			
		||||
          }),
 | 
			
		||||
        ]),
 | 
			
		||||
      )
 | 
			
		||||
      const folderInfo = computeFolderInfo(folders, content, cfg.locale)
 | 
			
		||||
      yield* processFolderInfo(ctx, folderInfo, allFiles, opts, resources)
 | 
			
		||||
    },
 | 
			
		||||
    async *partialEmit(ctx, content, resources, changeEvents) {
 | 
			
		||||
      const allFiles = content.map((c) => c[1].data)
 | 
			
		||||
      const cfg = ctx.cfg.configuration
 | 
			
		||||
 | 
			
		||||
      for (const [tree, file] of content) {
 | 
			
		||||
        const slug = stripSlashes(simplifySlug(file.data.slug!)) as SimpleSlug
 | 
			
		||||
        if (folders.has(slug)) {
 | 
			
		||||
          folderDescriptions[slug] = [tree, file]
 | 
			
		||||
        }
 | 
			
		||||
      // Find all folders that need to be updated based on changed files
 | 
			
		||||
      const affectedFolders: Set<SimpleSlug> = new Set()
 | 
			
		||||
      for (const changeEvent of changeEvents) {
 | 
			
		||||
        if (!changeEvent.file) continue
 | 
			
		||||
        const slug = changeEvent.file.data.slug!
 | 
			
		||||
        const folders = _getFolders(slug).filter(
 | 
			
		||||
          (folderName) => folderName !== "." && folderName !== "tags",
 | 
			
		||||
        )
 | 
			
		||||
        folders.forEach((folder) => affectedFolders.add(folder))
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (const folder of folders) {
 | 
			
		||||
        const slug = joinSegments(folder, "index") as FullSlug
 | 
			
		||||
        const [tree, file] = folderDescriptions[folder]
 | 
			
		||||
        const externalResources = pageResources(pathToRoot(slug), file.data, resources)
 | 
			
		||||
        const componentData: QuartzComponentProps = {
 | 
			
		||||
          ctx,
 | 
			
		||||
          fileData: file.data,
 | 
			
		||||
          externalResources,
 | 
			
		||||
          cfg,
 | 
			
		||||
          children: [],
 | 
			
		||||
          tree,
 | 
			
		||||
          allFiles,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const content = renderPage(cfg, slug, componentData, opts, externalResources)
 | 
			
		||||
        yield write({
 | 
			
		||||
          ctx,
 | 
			
		||||
          content,
 | 
			
		||||
          slug,
 | 
			
		||||
          ext: ".html",
 | 
			
		||||
        })
 | 
			
		||||
      // If there are affected folders, rebuild their pages
 | 
			
		||||
      if (affectedFolders.size > 0) {
 | 
			
		||||
        const folderInfo = computeFolderInfo(affectedFolders, content, cfg.locale)
 | 
			
		||||
        yield* processFolderInfo(ctx, folderInfo, allFiles, opts, resources)
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _getFolders(slug: FullSlug): SimpleSlug[] {
 | 
			
		||||
  var folderName = path.dirname(slug ?? "") as SimpleSlug
 | 
			
		||||
  const parentFolderNames = [folderName]
 | 
			
		||||
 | 
			
		||||
  while (folderName !== ".") {
 | 
			
		||||
    folderName = path.dirname(folderName ?? "") as SimpleSlug
 | 
			
		||||
    parentFolderNames.push(folderName)
 | 
			
		||||
  }
 | 
			
		||||
  return parentFolderNames
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user