Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 104 additions & 55 deletions crates/mako/src/dev/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use tracing::debug;
use crate::build::BuildError;
use crate::compiler::Compiler;
use crate::generate::transform::transform_modules;
use crate::module::{Dependency, Module, ModuleId, ResolveType};
use crate::module::{Dependency, Module, ModuleId, ResolveTypeFlags};
use crate::module_graph::ModuleGraph;
use crate::resolve::{self, clear_resolver_cache};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -320,33 +321,38 @@ impl Compiler {
add_modules.insert(module_id, module);
});

let mut d = diff(current_dependencies, target_dependencies);
// if added dep is a dynamic dependency, need to full re-group
d.added.iter().for_each(|added| {
if added.1.resolve_type == ResolveType::DynamicImport {
d.dep_changed.insert((module.id.clone(), added.1.clone()));
}
});
let modules_diff = diff(&current_dependencies, &target_dependencies);

debug!("build by modify: {:?} end", entry);
Result::Ok((module, d.added, d.removed, d.dep_changed, add_modules))
Result::Ok((module, modules_diff, add_modules, target_dependencies))
})
.collect::<Result<Vec<_>>>();
let result = result?;
let modified_results = result?;

let mut added = vec![];
let mut modified_module_ids = HashSet::new();
let mut dep_changed_module_ids = HashSet::new();

let mut module_graph = self.context.module_graph.write().unwrap();
for (module, add, remove, dep_change, mut add_modules) in result {
for (modified_module, diff, mut add_modules, dependencies) in modified_results {
if diff.dependence_changed(&modified_module.id, &module_graph, &dependencies) {
dep_changed_module_ids.insert(modified_module.id.clone());
}

// remove bind dependency
for (remove_module_id, dep) in remove {
module_graph.remove_dependency(&module.id, &remove_module_id, &dep);
for remove_module_id in &diff.removed {
module_graph.clear_dependency(&modified_module.id, remove_module_id);
}

// add bind dependency
for (add_module_id, dep) in &add {
for add_module_id in &diff.added {
let mut deps = dependencies
.iter()
.filter(|(module_id, _dep)| module_id == add_module_id)
.map(|(_, dep)| dep)
.collect::<Vec<_>>();
deps.sort_by_key(|d| d.order);

// english: In theory, the add_module_id that add_modules should exist in must exist, but in actual scenarios, an unwrap() error still occurs, so add a guard check here
// TODO: Need to find the root cause
let add_module = add_modules.remove(add_module_id);
Expand All @@ -361,21 +367,30 @@ impl Compiler {
}

module_graph.add_module(add_module);
module_graph.add_dependency(&module.id, add_module_id, dep.clone());

deps.iter().for_each(|&dep| {
module_graph.add_dependency(&modified_module.id, add_module_id, dep.clone());
});
}

if !&dep_change.is_empty() {
dep_changed_module_ids.insert(module.id.clone());
diff.modified.iter().for_each(|to_module_id| {
let deps = dependencies
.iter()
.filter(|(module_id, _dep)| module_id == to_module_id)
.map(|(_, dep)| dep)
.collect::<Vec<_>>();

for (dep_change_module_id, dep) in &dep_change {
module_graph.add_dependency(&module.id, dep_change_module_id, dep.clone());
}
}
module_graph.clear_dependency(&modified_module.id, to_module_id);

deps.iter().for_each(|&dep| {
module_graph.add_dependency(&modified_module.id, to_module_id, dep.clone());
});
});

modified_module_ids.insert(module.id.clone());
modified_module_ids.insert(modified_module.id.clone());

// replace module
module_graph.replace_module(module);
module_graph.replace_module(modified_module);
}

Result::Ok((modified_module_ids, dep_changed_module_ids, added))
Expand Down Expand Up @@ -410,50 +425,84 @@ impl Compiler {
}

pub struct Diff {
added: HashSet<(ModuleId, Dependency)>,
removed: HashSet<(ModuleId, Dependency)>,
dep_changed: HashSet<(ModuleId, Dependency)>,
added: HashSet<ModuleId>,
removed: HashSet<ModuleId>,
modified: HashSet<ModuleId>,
}

impl Diff {
fn dependence_changed(
&self,
module_id: &ModuleId,
module_graph: &ModuleGraph,
new_dependencies: &[(ModuleId, Dependency)],
) -> bool {
if !self.added.is_empty() {
return true;
}

if !self.removed.is_empty() {
return true;
}

let new_deps = new_dependencies
.iter()
.fold(HashMap::new(), |mut map, (module_id, dep)| {
let flag: ResolveTypeFlags = (&dep.resolve_type).into();

map.entry(module_id.clone())
.and_modify(|e: &mut ResolveTypeFlags| {
e.insert(flag);
})
.or_insert(flag);
map
});

let original = module_graph.get_dependencies(module_id).into_iter().fold(
HashMap::new(),
|mut map, (module_id, dep)| {
let flag: ResolveTypeFlags = (&dep.resolve_type).into();
map.entry(module_id.clone())
.and_modify(|e: &mut ResolveTypeFlags| e.insert(flag))
.or_insert(flag);
map
},
);

new_deps.eq(&original)
}
}

// 对比两颗 Dependency 的差异
fn diff(origin: Vec<(ModuleId, Dependency)>, target: Vec<(ModuleId, Dependency)>) -> Diff {
// 比较两个依赖列表的差异
// 未变化的模块算作 modified,因为依赖数据必然发生了变化;eg: order,span
fn diff(origin: &[(ModuleId, Dependency)], new_deps: &[(ModuleId, Dependency)]) -> Diff {
let origin_module_ids = origin
.iter()
.map(|(module_id, _dep)| module_id)
.map(|(module_id, _dep)| module_id.clone())
.collect::<HashSet<_>>();
let target_module_ids = target
let target_module_ids = new_deps
.iter()
.map(|(module_id, _dep)| module_id)
.map(|(module_id, _dep)| module_id.clone())
.collect::<HashSet<_>>();

let mut added: HashSet<(ModuleId, Dependency)> = HashSet::new();
let mut removed: HashSet<(ModuleId, Dependency)> = HashSet::new();
let mut dep_changed: HashSet<(ModuleId, Dependency)> = HashSet::new();
let removed = origin_module_ids
.difference(&target_module_ids)
.cloned()
.collect::<HashSet<_>>();

target
.iter()
.filter(|(module_id, _dep)| !origin_module_ids.contains(module_id))
.for_each(|(module_id, dep)| {
added.insert((module_id.clone(), dep.clone()));
});

origin.iter().for_each(|(module_id, dep)| {
if target_module_ids.contains(module_id) {
let (_, t_dep) = target
.iter()
.find(|(t_module_id, _)| t_module_id == module_id)
.unwrap();
if dep.resolve_type != t_dep.resolve_type {
dep_changed.insert((module_id.clone(), t_dep.clone()));
}
} else {
removed.insert((module_id.clone(), dep.clone()));
}
});
let added = target_module_ids
.difference(&origin_module_ids)
.cloned()
.collect::<HashSet<_>>();

let modified = origin_module_ids
.intersection(&target_module_ids)
.cloned()
.collect::<HashSet<_>>();

Diff {
added,
removed,
dep_changed,
modified,
}
}
15 changes: 15 additions & 0 deletions crates/mako/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub struct Dependency {
}

bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Default)]
pub struct ResolveTypeFlags: u16 {
const Sync = 1;
const Async = 1<<2;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Default)]
pub struct ImportType: u16 {
const Default = 1;
Expand All @@ -51,6 +57,15 @@ bitflags! {
}
}

impl From<&ResolveType> for ResolveTypeFlags {
fn from(value: &ResolveType) -> Self {
match value {
ResolveType::DynamicImport | ResolveType::Worker => Self::Async,
_ => Self::Sync,
}
}
}

impl From<&ImportDecl> for ImportType {
fn from(decl: &ImportDecl) -> Self {
if decl.specifiers.is_empty() {
Expand Down
23 changes: 23 additions & 0 deletions crates/mako/src/module_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,29 @@ impl ModuleGraph {
self.graph.node_weights_mut().collect()
}

pub fn clear_dependency(&mut self, from: &ModuleId, to: &ModuleId) {
let from_index = self.id_index_map.get(from).unwrap_or_else(|| {
panic!(
r#"from node "{}" does not exist in the module graph when remove edge"#,
from.id
)
});

let to_index = self.id_index_map.get(to).unwrap_or_else(|| {
panic!(
r#"to node "{}" does not exist in the module graph when remove edge"#,
to.id
)
});

self.graph
.find_edge(*from_index, *to_index)
.and_then(|edge| {
self.graph.remove_edge(edge);
None::<()>
});
}

pub fn remove_dependency(&mut self, from: &ModuleId, to: &ModuleId, dep: &Dependency) {
let from_index = self.id_index_map.get(from).unwrap_or_else(|| {
panic!(
Expand Down
46 changes: 46 additions & 0 deletions scripts/test-hmr.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,52 @@ runTest('change async import to import', async () => {
await cleanup({ process, browser });
});

runTest('async module update', async () => {
write(
normalizeFiles({
'/src/index.tsx': `
import g2 from "@antv/g2";
console.log(g2);
document.getElementById("root").innerHTML = "g2 loaded";
`,
'mako.config.json': `
{
"externals": {
"@antv/g2": {
"root": "G2",
"script": "https://gw.alipayobjects.com/os/lib/antv/g2/3.5.19/dist/g2.min.js"
}
}
}
`,
}),
);
const { process } = await startMakoDevServer();
await delay(DELAY_TIME);
const { browser, page } = await startBrowser();
let lastResult;
let thisResult;
lastResult = normalizeHtml(await getRootHtml(page));
console.log('last html', lastResult.html);
assert.match(lastResult.html, /g2 loaded/);
write({
'/src/index.tsx': `
import g2 from "@antv/g2";
console.log(g2);
if (g2.version === "3.5.19"){
document.getElementById("root").innerHTML = "Update Success";
}else{
document.getElementById("root").innerHTML = "Failed";
}
`,
});
await delay(DELAY_TIME);
thisResult = normalizeHtml(await getRootHtml(page));
console.log(`new html`, thisResult.html);
assert.match(thisResult.html, /Update Success/, 'after reload');
await cleanup({ process, browser });
});

runTest('add async import', async () => {
write(
normalizeFiles({
Expand Down