Compare commits

...

4 Commits

Author SHA1 Message Date
Jacky Zhao
1bb6f09db1 make fancy logger dumb in ci 2025-03-16 14:12:43 -07:00
Jacky Zhao
40a72eba44 update docs 2025-03-16 13:46:16 -07:00
Jacky Zhao
bdc15ecb05 smol doc update 2025-03-16 12:11:05 -07:00
Jacky Zhao
5ccb9ddc70 properly splice changes array 2025-03-16 12:07:48 -07:00
6 changed files with 52 additions and 16 deletions

View File

@ -221,12 +221,26 @@ export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
export type QuartzEmitterPluginInstance = { export type QuartzEmitterPluginInstance = {
name: string name: string
emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise<FilePath[]> emit(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
): Promise<FilePath[]> | AsyncGenerator<FilePath>
partialEmit?(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
changeEvents: ChangeEvent[],
): Promise<FilePath[]> | AsyncGenerator<FilePath> | null
getQuartzComponents(ctx: BuildCtx): QuartzComponent[] getQuartzComponents(ctx: BuildCtx): QuartzComponent[]
} }
``` ```
An emitter plugin must define a `name` field, an `emit` function, and a `getQuartzComponents` function. `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created. An emitter plugin must define a `name` field, an `emit` function, and a `getQuartzComponents` function. It can optionally implement a `partialEmit` function for incremental builds.
- `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created.
- `partialEmit` is an optional function that enables incremental builds. It receives information about which files have changed (`changeEvents`) and can selectively rebuild only the necessary files. This is useful for optimizing build times in development mode. If `partialEmit` is undefined, it will default to the `emit` function.
- `getQuartzComponents` declares which Quartz components the emitter uses to construct its pages.
Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `quartz/plugins/emitters/helpers.ts` if you are creating files that contain text. `write` has the following signature: Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `quartz/plugins/emitters/helpers.ts` if you are creating files that contain text. `write` has the following signature:

View File

@ -32,7 +32,7 @@ If you prefer instructions in a video format you can try following Nicole van de
## 🔧 Features ## 🔧 Features
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]], [[comments]] and [many more](./features/) right out of the box - [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]], [[comments]] and [many more](./features/) right out of the box
- Hot-reload for both configuration and content - Hot-reload on configuration edits and incremental rebuilds for content edits
- Simple JSX layouts and [[creating components|page components]] - Simple JSX layouts and [[creating components|page components]]
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes - [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
- Fully-customizable parsing, filtering, and page generation through [[making plugins|plugins]] - Fully-customizable parsing, filtering, and page generation through [[making plugins|plugins]]

2
package-lock.json generated
View File

@ -35,6 +35,7 @@
"mdast-util-to-hast": "^13.2.0", "mdast-util-to-hast": "^13.2.0",
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"micromorph": "^0.4.5", "micromorph": "^0.4.5",
"minimatch": "^10.0.1",
"pixi.js": "^8.8.1", "pixi.js": "^8.8.1",
"preact": "^10.26.4", "preact": "^10.26.4",
"preact-render-to-string": "^6.5.13", "preact-render-to-string": "^6.5.13",
@ -5254,6 +5255,7 @@
"version": "10.0.1", "version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },

View File

@ -61,6 +61,7 @@
"mdast-util-to-hast": "^13.2.0", "mdast-util-to-hast": "^13.2.0",
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"micromorph": "^0.4.5", "micromorph": "^0.4.5",
"minimatch": "^10.0.1",
"pixi.js": "^8.8.1", "pixi.js": "^8.8.1",
"preact": "^10.26.4", "preact": "^10.26.4",
"preact-render-to-string": "^6.5.13", "preact-render-to-string": "^6.5.13",

View File

@ -20,6 +20,7 @@ import { Mutex } from "async-mutex"
import { getStaticResourcesFromPlugins } from "./plugins" import { getStaticResourcesFromPlugins } from "./plugins"
import { randomIdNonSecure } from "./util/random" import { randomIdNonSecure } from "./util/random"
import { ChangeEvent } from "./plugins/types" import { ChangeEvent } from "./plugins/types"
import { minimatch } from "minimatch"
type ContentMap = Map< type ContentMap = Map<
FilePath, FilePath,
@ -117,11 +118,23 @@ async function startWatching(
}) })
} }
const gitIgnoredMatcher = await isGitIgnored()
const buildData: BuildData = { const buildData: BuildData = {
ctx, ctx,
mut, mut,
contentMap, contentMap,
ignored: await isGitIgnored(), ignored: (path) => {
if (gitIgnoredMatcher(path)) return true
const pathStr = path.toString()
for (const pattern of cfg.configuration.ignorePatterns) {
if (minimatch(pathStr, pattern)) {
return true
}
}
return false
},
changesSinceLastBuild: {}, changesSinceLastBuild: {},
lastBuildMs: 0, lastBuildMs: 0,
} }
@ -137,17 +150,17 @@ async function startWatching(
.on("add", (fp) => { .on("add", (fp) => {
if (buildData.ignored(fp)) return if (buildData.ignored(fp)) return
changes.push({ path: fp as FilePath, type: "add" }) changes.push({ path: fp as FilePath, type: "add" })
rebuild(changes, clientRefresh, buildData) void rebuild(changes, clientRefresh, buildData)
}) })
.on("change", (fp) => { .on("change", (fp) => {
if (buildData.ignored(fp)) return if (buildData.ignored(fp)) return
changes.push({ path: fp as FilePath, type: "change" }) changes.push({ path: fp as FilePath, type: "change" })
rebuild(changes, clientRefresh, buildData) void rebuild(changes, clientRefresh, buildData)
}) })
.on("unlink", (fp) => { .on("unlink", (fp) => {
if (buildData.ignored(fp)) return if (buildData.ignored(fp)) return
changes.push({ path: fp as FilePath, type: "delete" }) changes.push({ path: fp as FilePath, type: "delete" })
rebuild(changes, clientRefresh, buildData) void rebuild(changes, clientRefresh, buildData)
}) })
return async () => { return async () => {
@ -162,6 +175,7 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD
const buildId = randomIdNonSecure() const buildId = randomIdNonSecure()
ctx.buildId = buildId ctx.buildId = buildId
buildData.lastBuildMs = new Date().getTime() buildData.lastBuildMs = new Date().getTime()
const numChangesInBuild = changes.length
const release = await mut.acquire() const release = await mut.acquire()
// if there's another build after us, release and let them do it // if there's another build after us, release and let them do it
@ -180,16 +194,19 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD
} }
const staticResources = getStaticResourcesFromPlugins(ctx) const staticResources = getStaticResourcesFromPlugins(ctx)
const pathsToParse: FilePath[] = []
for (const [fp, type] of Object.entries(changesSinceLastBuild)) { for (const [fp, type] of Object.entries(changesSinceLastBuild)) {
if (type === "delete" || path.extname(fp) !== ".md") continue if (type === "delete" || path.extname(fp) !== ".md") continue
const fullPath = joinSegments(argv.directory, toPosixPath(fp)) as FilePath const fullPath = joinSegments(argv.directory, toPosixPath(fp)) as FilePath
const parsed = await parseMarkdown(ctx, [fullPath]) pathsToParse.push(fullPath)
for (const content of parsed) { }
contentMap.set(content[1].data.relativePath!, {
type: "markdown", const parsed = await parseMarkdown(ctx, pathsToParse)
content, for (const content of parsed) {
}) contentMap.set(content[1].data.relativePath!, {
} type: "markdown",
content,
})
} }
// update state using changesSinceLastBuild // update state using changesSinceLastBuild
@ -265,7 +282,7 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD
console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`) console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`)
console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
changes.length = 0 changes.splice(0, numChangesInBuild)
clientRefresh() clientRefresh()
release() release()
} }

View File

@ -10,7 +10,9 @@ export class QuartzLogger {
private readonly spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] private readonly spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
constructor(verbose: boolean) { constructor(verbose: boolean) {
this.verbose = verbose const isInteractiveTerminal =
process.stdout.isTTY && process.env.TERM !== "dumb" && !process.env.CI
this.verbose = verbose || !isInteractiveTerminal
} }
start(text: string) { start(text: string) {