Compare commits

..

No commits in common. "1bb6f09db1769bdb4cc64cc6afa3c272402765d3" and "7681a8681594ce2e4777bbb401bd7409cbed5915" have entirely different histories.

6 changed files with 16 additions and 52 deletions

View File

@ -221,26 +221,12 @@ export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
export type QuartzEmitterPluginInstance = { export type QuartzEmitterPluginInstance = {
name: string name: string
emit( emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise<FilePath[]>
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. It can optionally implement a `partialEmit` function for incremental builds. 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.
- `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 on configuration edits and incremental rebuilds for content edits - Hot-reload for both configuration and content
- 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,7 +35,6 @@
"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",
@ -5255,7 +5254,6 @@
"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,7 +61,6 @@
"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,7 +20,6 @@ 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,
@ -118,23 +117,11 @@ async function startWatching(
}) })
} }
const gitIgnoredMatcher = await isGitIgnored()
const buildData: BuildData = { const buildData: BuildData = {
ctx, ctx,
mut, mut,
contentMap, contentMap,
ignored: (path) => { ignored: await isGitIgnored(),
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,
} }
@ -150,17 +137,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" })
void rebuild(changes, clientRefresh, buildData) 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" })
void rebuild(changes, clientRefresh, buildData) 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" })
void rebuild(changes, clientRefresh, buildData) rebuild(changes, clientRefresh, buildData)
}) })
return async () => { return async () => {
@ -175,7 +162,6 @@ 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
@ -194,19 +180,16 @@ 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
pathsToParse.push(fullPath) const parsed = await parseMarkdown(ctx, [fullPath])
} for (const content of parsed) {
contentMap.set(content[1].data.relativePath!, {
const parsed = await parseMarkdown(ctx, pathsToParse) type: "markdown",
for (const content of parsed) { content,
contentMap.set(content[1].data.relativePath!, { })
type: "markdown", }
content,
})
} }
// update state using changesSinceLastBuild // update state using changesSinceLastBuild
@ -282,7 +265,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.splice(0, numChangesInBuild) changes.length = 0
clientRefresh() clientRefresh()
release() release()
} }

View File

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