hot content reload
This commit is contained in:
		
							parent
							
								
									b7966ff7fa
								
							
						
					
					
						commit
						8dd73704e6
					
				| @ -30,4 +30,3 @@ For a comprehensive list of features, visit the [features page](/features). You | ||||
| 
 | ||||
| ### 🚧 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). | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,18 +1,19 @@ | ||||
| { | ||||
|   "name": "@jackyzha0/quartz", | ||||
|   "version": "4.0.4", | ||||
|   "version": "4.0.5", | ||||
|   "lockfileVersion": 3, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "@jackyzha0/quartz", | ||||
|       "version": "4.0.4", | ||||
|       "version": "4.0.5", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@clack/prompts": "^0.6.3", | ||||
|         "@floating-ui/dom": "^1.4.0", | ||||
|         "@napi-rs/simple-git": "^0.1.8", | ||||
|         "chalk": "^4.1.2", | ||||
|         "chokidar": "^3.5.3", | ||||
|         "cli-spinner": "^0.2.10", | ||||
|         "d3": "^7.8.5", | ||||
|         "esbuild-sass-plugin": "^2.9.0", | ||||
| @ -54,6 +55,7 @@ | ||||
|         "unist-util-visit": "^4.1.2", | ||||
|         "vfile": "^5.3.7", | ||||
|         "workerpool": "^6.4.0", | ||||
|         "ws": "^8.13.0", | ||||
|         "yargs": "^17.7.2" | ||||
|       }, | ||||
|       "bin": { | ||||
| @ -69,6 +71,7 @@ | ||||
|         "@types/pretty-time": "^1.1.2", | ||||
|         "@types/serve-handler": "^6.1.1", | ||||
|         "@types/workerpool": "^6.4.0", | ||||
|         "@types/ws": "^8.5.5", | ||||
|         "@types/yargs": "^17.0.24", | ||||
|         "esbuild": "^0.18.11", | ||||
|         "tsx": "^3.12.7", | ||||
| @ -1498,6 +1501,15 @@ | ||||
|         "@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": { | ||||
|       "version": "17.0.24", | ||||
|       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", | ||||
|  | ||||
| @ -31,6 +31,7 @@ | ||||
|     "@floating-ui/dom": "^1.4.0", | ||||
|     "@napi-rs/simple-git": "^0.1.8", | ||||
|     "chalk": "^4.1.2", | ||||
|     "chokidar": "^3.5.3", | ||||
|     "cli-spinner": "^0.2.10", | ||||
|     "d3": "^7.8.5", | ||||
|     "esbuild-sass-plugin": "^2.9.0", | ||||
| @ -72,6 +73,7 @@ | ||||
|     "unist-util-visit": "^4.1.2", | ||||
|     "vfile": "^5.3.7", | ||||
|     "workerpool": "^6.4.0", | ||||
|     "ws": "^8.13.0", | ||||
|     "yargs": "^17.7.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
| @ -84,6 +86,7 @@ | ||||
|     "@types/pretty-time": "^1.1.2", | ||||
|     "@types/serve-handler": "^6.1.1", | ||||
|     "@types/workerpool": "^6.4.0", | ||||
|     "@types/ws": "^8.5.5", | ||||
|     "@types/yargs": "^17.0.24", | ||||
|     "esbuild": "^0.18.11", | ||||
|     "tsx": "^3.12.7", | ||||
|  | ||||
| @ -72,7 +72,7 @@ const BuildArgv = { | ||||
|   serve: { | ||||
|     boolean: true, | ||||
|     default: false, | ||||
|     describe: 'run a local server to preview your Quartz' | ||||
|     describe: 'run a local server to live-preview your Quartz' | ||||
|   }, | ||||
|   port: { | ||||
|     number: true, | ||||
| @ -255,6 +255,7 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. | ||||
|           setup(build) { | ||||
|             build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => { | ||||
|               let text = await promises.readFile(args.path, 'utf8') | ||||
| 
 | ||||
|               // remove default exports that we manually inserted
 | ||||
|               text = text.replace('export default', '') | ||||
|               text = text.replace('export', '') | ||||
|  | ||||
| @ -2,7 +2,7 @@ import 'source-map-support/register.js' | ||||
| import path from "path" | ||||
| import { PerfTimer } from "./perf" | ||||
| import { rimraf } from "rimraf" | ||||
| import { globby } from "globby" | ||||
| import { globby, isGitIgnored } from "globby" | ||||
| import chalk from "chalk" | ||||
| import http from "http" | ||||
| import serveHandler from "serve-handler" | ||||
| @ -11,6 +11,9 @@ import { filterContent } from "./processors/filter" | ||||
| import { emitContent } from "./processors/emit" | ||||
| import cfg from "../quartz.config" | ||||
| import { FilePath } from "./path" | ||||
| import chokidar from "chokidar" | ||||
| import { ProcessedContent } from './plugins/vfile' | ||||
| import WebSocket, { WebSocketServer } from 'ws' | ||||
| 
 | ||||
| interface Argv { | ||||
|   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 parsedFiles = await parseMarkdown(cfg.plugins.transformers, argv.directory, filePaths, 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()}`)) | ||||
| 
 | ||||
|   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) => { | ||||
|       await serveHandler(req, res, { | ||||
|         public: output, | ||||
| @ -62,10 +108,10 @@ export default async function buildQuartz(argv: Argv, version: string) { | ||||
|       }) | ||||
|       const status = res.statusCode | ||||
|       const statusString = (status >= 200 && status < 300) ? | ||||
|         chalk.green(`[${status}]`) :  | ||||
|         chalk.green(`[${status}]`) : | ||||
|         (status >= 300 && status < 400) ? | ||||
|         chalk.yellow(`[${status}]`) : | ||||
|         chalk.red(`[${status}]`)  | ||||
|           chalk.yellow(`[${status}]`) : | ||||
|           chalk.red(`[${status}]`) | ||||
|       console.log(statusString + chalk.grey(` ${req.url}`)) | ||||
|     }) | ||||
|     server.listen(argv.port) | ||||
|  | ||||
| @ -7,7 +7,6 @@ import { EmitCallback } from "../plugins/types" | ||||
| import { ProcessedContent } from "../plugins/vfile" | ||||
| import { FilePath, QUARTZ, slugifyFilePath } from "../path" | ||||
| import { globbyStream } from "globby" | ||||
| import chalk from "chalk" | ||||
| 
 | ||||
| // @ts-ignore
 | ||||
| import spaRouterScript from '../components/scripts/spa.inline' | ||||
| @ -21,7 +20,7 @@ import { QuartzLogger } from "../log" | ||||
| import { googleFontHref } from "../theme" | ||||
| 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)) | ||||
| 
 | ||||
|   // popovers
 | ||||
| @ -64,9 +63,20 @@ function addGlobalPageResources(cfg: GlobalConfiguration, staticResources: Stati | ||||
|       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 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 
 | ||||
|   // 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(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) | ||||
|   if (verbose) { | ||||
|     for (const file of emittedResources) { | ||||
|       emittedFiles += 1 | ||||
|       console.log(`[emit:Resources] ${file}`) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // emitter plugins
 | ||||
|   let emittedFiles = 0 | ||||
|   for (const emitter of cfg.plugins.emitters) { | ||||
|     try { | ||||
|       const emitted = await emitter.emit(contentFolder, cfg.configuration, content, staticResources, emit) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user