Skip to content

Commit

Permalink
feat: hmr supports linked npm packages changes (#864)
Browse files Browse the repository at this point in the history
* feat: hmr supports linked npm packages changes

* feat: switch the way to get dependecies's paths to be watched

* feat: optimize the way to get modules

* feat: optimize the way to watch linked npm package

* feat: optimize watch performance and add logs

---------

Co-authored-by: zp365238 <[email protected]>
  • Loading branch information
zhangpanweb and zp365238 authored Jan 29, 2024
1 parent 0961f16 commit e161685
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 29 deletions.
9 changes: 5 additions & 4 deletions crates/mako/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use mako_core::{hyper, hyper_staticfile, hyper_tungstenite, tokio};

use crate::compiler::{Compiler, Context};
use crate::plugin::{PluginGenerateEndParams, PluginGenerateStats};
use crate::watch::Watch;
use crate::watch::Watcher;

pub struct DevServer {
root: PathBuf,
Expand Down Expand Up @@ -170,15 +170,15 @@ impl DevServer {
let (tx, rx) = mpsc::channel();
// let mut watcher = RecommendedWatcher::new(tx, notify::Config::default())?;
let mut debouncer = new_debouncer(Duration::from_millis(10), None, tx).unwrap();
let watcher = debouncer.watcher();
Watch::watch(&root, watcher)?;
let mut watcher = Watcher::new(&root, debouncer.watcher(), &compiler);
watcher.watch()?;

let initial_hash = compiler.full_hash();
let mut last_cache_hash = Box::new(initial_hash);
let mut hmr_hash = Box::new(initial_hash);

for result in rx {
let paths = Watch::normalize_events(result.unwrap());
let paths = Watcher::normalize_events(result.unwrap());
if !paths.is_empty() {
let compiler = compiler.clone();
let txws = txws.clone();
Expand All @@ -194,6 +194,7 @@ impl DevServer {
eprintln!("Error rebuilding: {:?}", e);
}
}
watcher.refresh_watch()?;
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions crates/mako/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![feature(box_patterns)]
#![feature(hasher_prefixfree_extras)]
#![feature(let_chains)]
#![feature(result_option_inspect)]

mod analyze_deps;
mod ast;
Expand Down
1 change: 1 addition & 0 deletions crates/mako/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(box_patterns)]
#![feature(let_chains)]
#![feature(result_option_inspect)]

use std::sync::Arc;

Expand Down
141 changes: 118 additions & 23 deletions crates/mako/src/watch.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,138 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::mpsc::Sender;
use std::sync::Arc;

use mako_core::anyhow;
use mako_core::notify::{self, EventKind, Watcher};
use mako_core::anyhow::{self, Ok};
use mako_core::colored::Colorize;
use mako_core::notify::{self, EventKind, Watcher as NotifyWatcher};
use mako_core::notify_debouncer_full::DebouncedEvent;
use mako_core::tokio::time::Instant;
use mako_core::tracing::debug;

pub struct Watch {
pub root: PathBuf,
pub delay: u64,
pub tx: Sender<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
use crate::compiler::Compiler;
use crate::resolve::ResolverResource;

pub struct Watcher<'a> {
pub watcher: &'a mut dyn NotifyWatcher,
pub root: &'a PathBuf,
pub compiler: &'a Compiler,
pub watched_files: HashSet<PathBuf>,
pub watched_dirs: HashSet<PathBuf>,
}

impl Watch {
impl<'a> Watcher<'a> {
pub fn new(
root: &'a PathBuf,
watcher: &'a mut notify::RecommendedWatcher,
compiler: &'a Arc<Compiler>,
) -> Self {
Self {
root,
watcher,
compiler,
watched_dirs: HashSet::new(),
watched_files: HashSet::new(),
}
}

// pub fn watch(root: &PathBuf, watcher: &mut notify::RecommendedWatcher) -> anyhow::Result<()> {
pub fn watch(root: &PathBuf, watcher: &mut notify::RecommendedWatcher) -> anyhow::Result<()> {
let items = std::fs::read_dir(root)?;
pub fn watch(&mut self) -> anyhow::Result<()> {
let t_watch = Instant::now();

let ignore_list = [".git", "node_modules", ".DS_Store", ".node"];

let mut root_ignore_list = ignore_list.to_vec();
root_ignore_list.push(self.compiler.context.config.output.path.to_str().unwrap());
self.watch_dir_recursive(self.root.into(), &root_ignore_list)?;

let module_graph = self.compiler.context.module_graph.read().unwrap();
let mut dirs = HashSet::new();
module_graph.modules().iter().for_each(|module| {
if let Some(ResolverResource::Resolved(resource)) = module
.info
.as_ref()
.and_then(|info| info.resolved_resource.as_ref())
{
if let Some(dir) = &resource.0.description {
let dir = dir.dir().as_ref();
// not in root dir or is root's parent dir
if dir.strip_prefix(self.root).is_err() && self.root.strip_prefix(dir).is_err()
{
dirs.insert(dir);
}
}
}
});
dirs.iter().try_for_each(|dir| {
self.watch_dir_recursive(dir.into(), ignore_list.as_slice())?;
Ok(())
})?;

let t_watch_duration = t_watch.elapsed();
debug!(
"{}",
format!(
"✓ watch in {}",
format!("{}ms", t_watch_duration.as_millis()).bold()
)
.green()
);

Ok(())
}

pub fn refresh_watch(&mut self) -> anyhow::Result<()> {
let t_refresh_watch = Instant::now();

self.watch()?;

let t_refresh_watch_duration = t_refresh_watch.elapsed();
debug!(
"{}",
format!(
"✓ refresh watch in {}",
format!("{}ms", t_refresh_watch_duration.as_millis()).bold()
)
.green()
);

Ok(())
}

fn watch_dir_recursive(&mut self, path: PathBuf, ignore_list: &[&str]) -> anyhow::Result<()> {
let items = std::fs::read_dir(path)?;
items
.into_iter()
.try_for_each(|item| -> anyhow::Result<()> {
let path = item.unwrap().path();
if Self::should_ignore_watch(&path) {
return Ok(());
}
if path.is_file() {
watcher.watch(path.as_path(), notify::RecursiveMode::NonRecursive)?;
} else if path.is_dir() {
watcher.watch(path.as_path(), notify::RecursiveMode::Recursive)?;
} else {
// others like symlink? should be ignore?
}
self.watch_file_or_dir(path, ignore_list)?;
Ok(())
})?;
Ok(())
}

fn should_ignore_watch(path: &Path) -> bool {
fn watch_file_or_dir(&mut self, path: PathBuf, ignore_list: &[&str]) -> anyhow::Result<()> {
if Self::should_ignore_watch(&path, ignore_list) {
return Ok(());
}

if path.is_file() && !self.watched_files.contains(&path) {
self.watcher
.watch(path.as_path(), notify::RecursiveMode::NonRecursive)?;
self.watched_files.insert(path);
} else if path.is_dir() && !self.watched_dirs.contains(&path) {
self.watcher
.watch(path.as_path(), notify::RecursiveMode::Recursive)?;
self.watched_dirs.insert(path);
} else {
// others like symlink? should be ignore?
}

Ok(())
}

fn should_ignore_watch(path: &Path, ignore_list: &[&str]) -> bool {
let path = path.to_string_lossy();
let ignore_list = [".git", "node_modules", ".DS_Store", "dist", ".node"];
ignore_list.iter().any(|ignored| path.ends_with(ignored))
}

Expand Down
Loading

0 comments on commit e161685

Please sign in to comment.