feat: watch basic diffing

This commit is contained in:
Maciej Jur 2024-07-06 21:59:36 +02:00
parent 518f9a56ed
commit ad9c0b1505
Signed by: kamov
GPG key ID: 191CBFF5F72ECAFD
4 changed files with 62 additions and 25 deletions

View file

@ -23,9 +23,9 @@ pub(crate) fn build_styles() {
fs::write("dist/styles.css", css).unwrap();
}
pub(crate) fn build_content(ctx: &BuildContext, content: &[&Output]) {
pub(crate) fn build_content(ctx: &BuildContext, pending: &[&Output], hole: &[&Output]) {
let now = std::time::Instant::now();
render_all(ctx, content);
render_all(ctx, pending, hole);
println!("Elapsed: {:.2?}", now.elapsed());
}
@ -72,8 +72,8 @@ fn copy_recursively(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<
Ok(())
}
fn render_all(ctx: &BuildContext, items: &[&Output]) {
for item in items {
fn render_all(ctx: &BuildContext, pending: &[&Output], hole: &[&Output]) {
for item in pending {
let file = match &item.kind {
OutputKind::Asset(a) => Some(&a.meta.path),
OutputKind::Virtual(_) => None,
@ -82,7 +82,7 @@ fn render_all(ctx: &BuildContext, items: &[&Output]) {
item,
Sack {
ctx,
hole: items,
hole,
path: &item.path,
file,
},

View file

@ -105,7 +105,7 @@ fn main() {
},
];
let special = &[
let special = vec![
Output {
kind: Virtual::new(|sack| crate::html::map(sack).render().to_owned().into()).into(),
path: "map/index.html".into(),
@ -159,11 +159,11 @@ fn main() {
match args.mode {
Mode::Build => {
build(&ctx, sources, special);
let _ = build(&ctx, sources, special);
}
Mode::Watch => {
build(&ctx, sources, special);
watch::watch(&ctx, sources).unwrap()
let state = build(&ctx, sources, special);
watch::watch(&ctx, sources, state).unwrap()
}
}
}
@ -198,7 +198,7 @@ impl Source {
}
}
fn build(ctx: &BuildContext, sources: &[Source], special: &[Output]) {
fn build(ctx: &BuildContext, sources: &[Source], special: Vec<Output>) -> Vec<Output> {
crate::build::clean_dist();
let sources: Vec<_> = sources
@ -208,13 +208,15 @@ fn build(ctx: &BuildContext, sources: &[Source], special: &[Output]) {
.filter_map(Option::from)
.collect();
let assets: Vec<_> = sources.iter().chain(special).collect();
let assets: Vec<_> = sources.iter().chain(special.iter()).collect();
crate::build::build_content(ctx, &assets);
crate::build::build_content(ctx, &assets, &assets);
crate::build::build_static();
crate::build::build_styles();
crate::build::build_pagefind();
crate::build::build_js();
sources.into_iter().chain(special).collect()
}
pub fn parse_frontmatter<D>(raw: &str) -> (D, String)

View file

@ -41,7 +41,7 @@ pub(crate) trait Content {
/// Marks whether the item should be treated as a content page, converted into a standalone HTML
/// page, or as a bundled asset.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub(crate) enum FileItemKind {
/// Marks items converted to `index.html`.
Index,
@ -50,7 +50,7 @@ pub(crate) enum FileItemKind {
}
/// Metadata for a single item consumed by SSG.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub(crate) struct FileItem {
/// The kind of an item from disk.
pub kind: FileItemKind,

View file

@ -1,13 +1,15 @@
use std::collections::HashMap;
use std::env;
use std::io::Result;
use std::net::{TcpListener, TcpStream};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use std::time::Duration;
use camino::Utf8PathBuf;
use camino::{Utf8Path, Utf8PathBuf};
use notify::RecursiveMode;
use notify_debouncer_mini::new_debouncer;
use tungstenite::WebSocket;
@ -69,7 +71,7 @@ fn new_thread_ws_reload(
(tx, thread)
}
pub fn watch(ctx: &BuildContext, sources: &[Source]) -> Result<()> {
pub fn watch(ctx: &BuildContext, sources: &[Source], state: Vec<Output>) -> Result<()> {
let root = env::current_dir().unwrap();
let server = TcpListener::bind("127.0.0.1:1337")?;
let client = Arc::new(Mutex::new(vec![]));
@ -90,23 +92,46 @@ pub fn watch(ctx: &BuildContext, sources: &[Source]) -> Result<()> {
let thread_i = new_thread_ws_incoming(server, client.clone());
let (tx_reload, thread_o) = new_thread_ws_reload(client.clone());
let mut state: Vec<Rc<Output>> = state.into_iter().map(Rc::new).collect();
while let Ok(events) = rx.recv().unwrap() {
let items = events
let paths: Vec<Utf8PathBuf> = events
.into_iter()
.filter_map(|event| {
Utf8PathBuf::from_path_buf(event.path)
.ok()
.and_then(|path| path.strip_prefix(&root).ok().map(ToOwned::to_owned))
.and_then(|path| sources.iter().find_map(|s| s.get_maybe(&path)))
})
.filter_map(Option::from)
.collect::<Vec<Output>>();
.collect();
let items = items.iter().collect::<Vec<_>>();
let mut dirty = false;
build_content(ctx, &items);
build_styles();
tx_reload.send(()).unwrap();
{
let items: Vec<Rc<Output>> = paths
.iter()
.filter_map(|path| sources.iter().find_map(|s| s.get_maybe(path)))
.filter_map(Option::from)
.map(Rc::new)
.collect();
if !items.is_empty() {
let state_next = update_stream(&state, &items);
let abc: Vec<&Output> = items.iter().map(AsRef::as_ref).collect();
let xyz: Vec<&Output> = state_next.iter().map(AsRef::as_ref).collect();
build_content(ctx, &abc, &xyz);
state = state_next;
dirty = true;
}
}
if paths.iter().any(|path| path.starts_with("styles")) {
build_styles();
dirty = true;
}
if dirty {
tx_reload.send(()).unwrap();
}
}
thread_i.join().unwrap();
@ -114,3 +139,13 @@ pub fn watch(ctx: &BuildContext, sources: &[Source]) -> Result<()> {
Ok(())
}
fn update_stream(old: &[Rc<Output>], new: &[Rc<Output>]) -> Vec<Rc<Output>> {
let mut map: HashMap<&Utf8Path, Rc<Output>> = HashMap::new();
for output in old.iter().chain(new) {
map.insert(&*output.path, output.clone());
}
map.into_values().collect()
}