hot content reload
This commit is contained in:
parent
2462347f0f
commit
548e73e46b
@ -30,4 +30,3 @@ For a comprehensive list of features, visit the [features page](/features). You
|
|||||||
|
|
||||||
### 🚧 Troubleshooting
|
### 🚧 Troubleshooting
|
||||||
Having trouble with Quartz? Try searching for your issue using the search feature. If you're still having trouble, feel free to [submit an issue](https://github.com/jackyzha0/quartz/issues) if you feel you found a bug or ask for help in our [Discord Community](https://discord.gg/cRFFHYye7t).
|
Having trouble with Quartz? Try searching for your issue using the search feature. If you're still having trouble, feel free to [submit an issue](https://github.com/jackyzha0/quartz/issues) if you feel you found a bug or ask for help in our [Discord Community](https://discord.gg/cRFFHYye7t).
|
||||||
|
|
||||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"version": "4.0.4",
|
"version": "4.0.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"version": "4.0.4",
|
"version": "4.0.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.6.3",
|
"@clack/prompts": "^0.6.3",
|
||||||
"@floating-ui/dom": "^1.4.0",
|
"@floating-ui/dom": "^1.4.0",
|
||||||
"@napi-rs/simple-git": "^0.1.8",
|
"@napi-rs/simple-git": "^0.1.8",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
"cli-spinner": "^0.2.10",
|
"cli-spinner": "^0.2.10",
|
||||||
"d3": "^7.8.5",
|
"d3": "^7.8.5",
|
||||||
"esbuild-sass-plugin": "^2.9.0",
|
"esbuild-sass-plugin": "^2.9.0",
|
||||||
@ -54,6 +55,7 @@
|
|||||||
"unist-util-visit": "^4.1.2",
|
"unist-util-visit": "^4.1.2",
|
||||||
"vfile": "^5.3.7",
|
"vfile": "^5.3.7",
|
||||||
"workerpool": "^6.4.0",
|
"workerpool": "^6.4.0",
|
||||||
|
"ws": "^8.13.0",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -69,6 +71,7 @@
|
|||||||
"@types/pretty-time": "^1.1.2",
|
"@types/pretty-time": "^1.1.2",
|
||||||
"@types/serve-handler": "^6.1.1",
|
"@types/serve-handler": "^6.1.1",
|
||||||
"@types/workerpool": "^6.4.0",
|
"@types/workerpool": "^6.4.0",
|
||||||
|
"@types/ws": "^8.5.5",
|
||||||
"@types/yargs": "^17.0.24",
|
"@types/yargs": "^17.0.24",
|
||||||
"esbuild": "^0.18.11",
|
"esbuild": "^0.18.11",
|
||||||
"tsx": "^3.12.7",
|
"tsx": "^3.12.7",
|
||||||
@ -1498,6 +1501,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
|
||||||
|
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.24",
|
"version": "17.0.24",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"@floating-ui/dom": "^1.4.0",
|
"@floating-ui/dom": "^1.4.0",
|
||||||
"@napi-rs/simple-git": "^0.1.8",
|
"@napi-rs/simple-git": "^0.1.8",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
"cli-spinner": "^0.2.10",
|
"cli-spinner": "^0.2.10",
|
||||||
"d3": "^7.8.5",
|
"d3": "^7.8.5",
|
||||||
"esbuild-sass-plugin": "^2.9.0",
|
"esbuild-sass-plugin": "^2.9.0",
|
||||||
@ -72,6 +73,7 @@
|
|||||||
"unist-util-visit": "^4.1.2",
|
"unist-util-visit": "^4.1.2",
|
||||||
"vfile": "^5.3.7",
|
"vfile": "^5.3.7",
|
||||||
"workerpool": "^6.4.0",
|
"workerpool": "^6.4.0",
|
||||||
|
"ws": "^8.13.0",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -84,6 +86,7 @@
|
|||||||
"@types/pretty-time": "^1.1.2",
|
"@types/pretty-time": "^1.1.2",
|
||||||
"@types/serve-handler": "^6.1.1",
|
"@types/serve-handler": "^6.1.1",
|
||||||
"@types/workerpool": "^6.4.0",
|
"@types/workerpool": "^6.4.0",
|
||||||
|
"@types/ws": "^8.5.5",
|
||||||
"@types/yargs": "^17.0.24",
|
"@types/yargs": "^17.0.24",
|
||||||
"esbuild": "^0.18.11",
|
"esbuild": "^0.18.11",
|
||||||
"tsx": "^3.12.7",
|
"tsx": "^3.12.7",
|
||||||
|
@ -72,7 +72,7 @@ const BuildArgv = {
|
|||||||
serve: {
|
serve: {
|
||||||
boolean: true,
|
boolean: true,
|
||||||
default: false,
|
default: false,
|
||||||
describe: 'run a local server to preview your Quartz'
|
describe: 'run a local server to live-preview your Quartz'
|
||||||
},
|
},
|
||||||
port: {
|
port: {
|
||||||
number: true,
|
number: true,
|
||||||
@ -255,6 +255,7 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
|
|||||||
setup(build) {
|
setup(build) {
|
||||||
build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
|
build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
|
||||||
let text = await promises.readFile(args.path, 'utf8')
|
let text = await promises.readFile(args.path, 'utf8')
|
||||||
|
|
||||||
// remove default exports that we manually inserted
|
// remove default exports that we manually inserted
|
||||||
text = text.replace('export default', '')
|
text = text.replace('export default', '')
|
||||||
text = text.replace('export', '')
|
text = text.replace('export', '')
|
||||||
|
@ -2,7 +2,7 @@ import 'source-map-support/register.js'
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { PerfTimer } from "./perf"
|
import { PerfTimer } from "./perf"
|
||||||
import { rimraf } from "rimraf"
|
import { rimraf } from "rimraf"
|
||||||
import { globby } from "globby"
|
import { globby, isGitIgnored } from "globby"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import http from "http"
|
import http from "http"
|
||||||
import serveHandler from "serve-handler"
|
import serveHandler from "serve-handler"
|
||||||
@ -11,6 +11,9 @@ import { filterContent } from "./processors/filter"
|
|||||||
import { emitContent } from "./processors/emit"
|
import { emitContent } from "./processors/emit"
|
||||||
import cfg from "../quartz.config"
|
import cfg from "../quartz.config"
|
||||||
import { FilePath } from "./path"
|
import { FilePath } from "./path"
|
||||||
|
import chokidar from "chokidar"
|
||||||
|
import { ProcessedContent } from './plugins/vfile'
|
||||||
|
import WebSocket, { WebSocketServer } from 'ws'
|
||||||
|
|
||||||
interface Argv {
|
interface Argv {
|
||||||
directory: string
|
directory: string
|
||||||
@ -51,10 +54,53 @@ export default async function buildQuartz(argv: Argv, version: string) {
|
|||||||
const filePaths = fps.map(fp => `${argv.directory}${path.sep}${fp}` as FilePath)
|
const filePaths = fps.map(fp => `${argv.directory}${path.sep}${fp}` as FilePath)
|
||||||
const parsedFiles = await parseMarkdown(cfg.plugins.transformers, argv.directory, filePaths, argv.verbose)
|
const parsedFiles = await parseMarkdown(cfg.plugins.transformers, argv.directory, filePaths, argv.verbose)
|
||||||
const filteredContent = filterContent(cfg.plugins.filters, parsedFiles, argv.verbose)
|
const filteredContent = filterContent(cfg.plugins.filters, parsedFiles, argv.verbose)
|
||||||
await emitContent(argv.directory, output, cfg, filteredContent, argv.verbose)
|
await emitContent(argv.directory, output, cfg, filteredContent, argv.serve, argv.verbose)
|
||||||
console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
|
console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
|
||||||
|
|
||||||
if (argv.serve) {
|
if (argv.serve) {
|
||||||
|
const wss = new WebSocketServer({ port: 3001 })
|
||||||
|
const connections: WebSocket[] = []
|
||||||
|
wss.on('connection', ws => connections.push(ws))
|
||||||
|
|
||||||
|
const ignored = await isGitIgnored()
|
||||||
|
const contentMap = new Map<FilePath, ProcessedContent>()
|
||||||
|
for (const content of parsedFiles) {
|
||||||
|
const [_tree, vfile] = content
|
||||||
|
contentMap.set(vfile.data.filePath!, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rebuild(fp: string, action: 'add' | 'change' | 'unlink') {
|
||||||
|
perf.addEvent('rebuild')
|
||||||
|
if (!ignored(fp)) {
|
||||||
|
console.log(chalk.yellow(`Detected change in ${fp}, rebuilding...`))
|
||||||
|
const fullPath = `${argv.directory}${path.sep}${fp}` as FilePath
|
||||||
|
if (action === 'add' || action === 'change') {
|
||||||
|
const [parsedContent] = await parseMarkdown(cfg.plugins.transformers, argv.directory, [fullPath], argv.verbose)
|
||||||
|
contentMap.set(fullPath, parsedContent)
|
||||||
|
} else if (action === 'unlink') {
|
||||||
|
contentMap.delete(fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
await rimraf(output)
|
||||||
|
const parsedFiles = [...contentMap.values()]
|
||||||
|
const filteredContent = filterContent(cfg.plugins.filters, parsedFiles, argv.verbose)
|
||||||
|
await emitContent(argv.directory, output, cfg, filteredContent, argv.serve, argv.verbose)
|
||||||
|
console.log(chalk.green(`Done rebuilding in ${perf.timeSince('rebuild')}`))
|
||||||
|
connections.forEach(conn => conn.send('rebuild'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const watcher = chokidar.watch('.', {
|
||||||
|
persistent: true,
|
||||||
|
cwd: argv.directory,
|
||||||
|
ignoreInitial: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher
|
||||||
|
.on('add', fp => rebuild(fp, 'add'))
|
||||||
|
.on('change', fp => rebuild(fp, 'change'))
|
||||||
|
.on('unlink', fp => rebuild(fp, 'unlink'))
|
||||||
|
|
||||||
const server = http.createServer(async (req, res) => {
|
const server = http.createServer(async (req, res) => {
|
||||||
await serveHandler(req, res, {
|
await serveHandler(req, res, {
|
||||||
public: output,
|
public: output,
|
||||||
@ -62,10 +108,10 @@ export default async function buildQuartz(argv: Argv, version: string) {
|
|||||||
})
|
})
|
||||||
const status = res.statusCode
|
const status = res.statusCode
|
||||||
const statusString = (status >= 200 && status < 300) ?
|
const statusString = (status >= 200 && status < 300) ?
|
||||||
chalk.green(`[${status}]`) :
|
chalk.green(`[${status}]`) :
|
||||||
(status >= 300 && status < 400) ?
|
(status >= 300 && status < 400) ?
|
||||||
chalk.yellow(`[${status}]`) :
|
chalk.yellow(`[${status}]`) :
|
||||||
chalk.red(`[${status}]`)
|
chalk.red(`[${status}]`)
|
||||||
console.log(statusString + chalk.grey(` ${req.url}`))
|
console.log(statusString + chalk.grey(` ${req.url}`))
|
||||||
})
|
})
|
||||||
server.listen(argv.port)
|
server.listen(argv.port)
|
||||||
|
@ -7,7 +7,6 @@ import { EmitCallback } from "../plugins/types"
|
|||||||
import { ProcessedContent } from "../plugins/vfile"
|
import { ProcessedContent } from "../plugins/vfile"
|
||||||
import { FilePath, QUARTZ, slugifyFilePath } from "../path"
|
import { FilePath, QUARTZ, slugifyFilePath } from "../path"
|
||||||
import { globbyStream } from "globby"
|
import { globbyStream } from "globby"
|
||||||
import chalk from "chalk"
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import spaRouterScript from '../components/scripts/spa.inline'
|
import spaRouterScript from '../components/scripts/spa.inline'
|
||||||
@ -21,7 +20,7 @@ import { QuartzLogger } from "../log"
|
|||||||
import { googleFontHref } from "../theme"
|
import { googleFontHref } from "../theme"
|
||||||
import { trace } from "../trace"
|
import { trace } from "../trace"
|
||||||
|
|
||||||
function addGlobalPageResources(cfg: GlobalConfiguration, staticResources: StaticResources, componentResources: ComponentResources) {
|
function addGlobalPageResources(cfg: GlobalConfiguration, reloadScript: boolean, staticResources: StaticResources, componentResources: ComponentResources) {
|
||||||
staticResources.css.push(googleFontHref(cfg.theme))
|
staticResources.css.push(googleFontHref(cfg.theme))
|
||||||
|
|
||||||
// popovers
|
// popovers
|
||||||
@ -64,9 +63,20 @@ function addGlobalPageResources(cfg: GlobalConfiguration, staticResources: Stati
|
|||||||
document.dispatchEvent(event)`
|
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(contentFolder: string, output: string, cfg: QuartzConfig, content: ProcessedContent[], verbose: boolean) {
|
export async function emitContent(contentFolder: string, output: string, cfg: QuartzConfig, content: ProcessedContent[], reloadScript: boolean, verbose: boolean) {
|
||||||
const perf = new PerfTimer()
|
const perf = new PerfTimer()
|
||||||
const log = new QuartzLogger(verbose)
|
const log = new QuartzLogger(verbose)
|
||||||
|
|
||||||
@ -88,18 +98,18 @@ export async function emitContent(contentFolder: string, output: string, cfg: Qu
|
|||||||
// important that this goes *after* component scripts
|
// important that this goes *after* component scripts
|
||||||
// as the "nav" event gets triggered here and we should make sure
|
// as the "nav" event gets triggered here and we should make sure
|
||||||
// that everyone else had the chance to register a listener for it
|
// that everyone else had the chance to register a listener for it
|
||||||
addGlobalPageResources(cfg.configuration, staticResources, componentResources)
|
addGlobalPageResources(cfg.configuration, reloadScript, staticResources, componentResources)
|
||||||
|
|
||||||
// emit in one go
|
let emittedFiles = 0
|
||||||
const emittedResources = await emitComponentResources(cfg.configuration, componentResources, emit)
|
const emittedResources = await emitComponentResources(cfg.configuration, componentResources, emit)
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
for (const file of emittedResources) {
|
for (const file of emittedResources) {
|
||||||
|
emittedFiles += 1
|
||||||
console.log(`[emit:Resources] ${file}`)
|
console.log(`[emit:Resources] ${file}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitter plugins
|
// emitter plugins
|
||||||
let emittedFiles = 0
|
|
||||||
for (const emitter of cfg.plugins.emitters) {
|
for (const emitter of cfg.plugins.emitters) {
|
||||||
try {
|
try {
|
||||||
const emitted = await emitter.emit(contentFolder, cfg.configuration, content, staticResources, emit)
|
const emitted = await emitter.emit(contentFolder, cfg.configuration, content, staticResources, emit)
|
||||||
|
Loading…
Reference in New Issue
Block a user