Skip to content

Commit

Permalink
feat(hmr): refactor hmr (#835)
Browse files Browse the repository at this point in the history
* feat(hmr): refactor hmr

* chore: add dispose, decline

* chore: add svelet demo

* chore: update config

* chore: add more vite compabitabilities

* chore: suppor ws server of vite server adapter

* refactor: config normalize

* chore: hmr protocol alignment

* refactor: hmr detection

* chore: detect hmr boudaries

* chore: support hmr api

* chore: solve conflicts

* fix: test issues
  • Loading branch information
wre232114 authored Dec 24, 2023
1 parent 7e3f93e commit 8846d06
Show file tree
Hide file tree
Showing 126 changed files with 2,776 additions and 1,287 deletions.
7 changes: 7 additions & 0 deletions .changeset/witty-otters-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@farmfe/js-plugin-record-viewer': patch
'@farmfe/js-plugin-less': patch
'@farmfe/core': patch
---

Normalize js plugin hooks name
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions configs/farm-js-plugin.base.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { builtinModules } from 'module';

const format = process.env.FARM_FORMAT || 'cjs';
const ext = format === 'esm' ? 'mjs' : 'cjs';
console.log('format', format, ext);

export function createFarmJsPluginBuildConfig(plugins) {
return {
compilation: {
input: {
index: './src/index.ts'
},
output: {
path: `build/${format}`,
entryFilename: `[entryName].${ext}`,
targetEnv: 'node',
format
},
external: [
...builtinModules.map((m) => `^${m}$`),
...builtinModules.map((m) => `^node:${m}$`)
],
partialBundling: {
enforceResources: [
{
name: 'index.js',
test: ['.+']
}
]
},
minify: false,
sourcemap: false,
presetEnv: false,
persistentCache: {
envs: {
FARM_FORMAT: format
}
}
},
server: {
hmr: false
},
plugins,
};

}
2 changes: 1 addition & 1 deletion crates/compiler/src/generate/render_resource_pots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ pub fn render_resource_pots_and_generate_resources(
let cached_meta = cached_resource_pot.meta;

resource_pot.meta = cached_meta;

resource_pot.add_resource(cached_resource.resource.name.clone());

cached_resource.resource.info = Some(rendered_resource_pot_info);
Expand Down Expand Up @@ -188,6 +187,7 @@ pub fn render_resource_pot_generate_resources(

let mut param = PluginRenderResourcePotHookParam {
content: resource_pot.meta.rendered_content.clone(),
source_map_chain: resource_pot.meta.rendered_map_chain.clone(),
resource_pot_info: ChunkResourceInfo::new(resource_pot, context),
};

Expand Down
145 changes: 132 additions & 13 deletions crates/compiler/src/update/find_hmr_boundaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ pub fn find_hmr_boundaries(
let mut res = vec![];
let all_path_accepted =
find_hmr_accepted_recursively(id, &module_graph, &mut stack, &mut visited, &mut res);

// if any of the path is not accepted, reload the whole page
if !all_path_accepted {
continue;
return HashMap::new();
}

boundaries.insert(
Expand All @@ -50,25 +50,46 @@ fn find_hmr_accepted_recursively(
res: &mut Vec<Vec<ModuleId>>,
) -> bool {
let module = module_graph.module(id).unwrap();

// There is a path from the module to the root that does not have HMR accepted
if module_graph.entries.contains_key(id) {
return false;
} else if module.module_type.is_script() {
let hmr_accepted = module.meta.as_script().hmr_accepted;
}

if hmr_accepted {
res.push(stack.clone());
// self accepted, non script modules are not self-acceptable for now
if module.module_type.is_script() && module.meta.as_script().hmr_self_accepted {
res.push(stack.clone());

return true;
}
return true;
}

// check if any of the importers accepts the module
if !visited.contains(id) {
visited.insert(id.clone());

let parents = module_graph.dependents_ids(id);

for parent in parents {
// check if the parent accepts the module
let parent_module = module_graph.module(&parent).unwrap();

if !parent_module.module_type.is_script() {
return false;
}
// if the importer accepts the module, push
if parent_module
.meta
.as_script()
.hmr_accepted_deps
.contains(id)
{
let mut cloned_stack = stack.clone();
cloned_stack.push(parent.clone());
res.push(cloned_stack);
// skip self recursive check if accepts the module
continue;
}

stack.push(parent.clone());
let all_path_accepted =
find_hmr_accepted_recursively(&parent, module_graph, stack, visited, res);
Expand All @@ -85,7 +106,10 @@ fn find_hmr_accepted_recursively(

#[cfg(test)]
mod tests {
use std::{collections::HashMap, sync::Arc};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};

use farmfe_core::{
config::{Config, Mode},
Expand Down Expand Up @@ -117,7 +141,7 @@ mod tests {
let module_a = module_graph.module_mut(&"A".into()).unwrap();
module_a.module_type = ModuleType::Js;
module_a.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_accepted: true,
hmr_self_accepted: true,
..Default::default()
});

Expand All @@ -134,13 +158,13 @@ mod tests {
let module_d = module_graph.module_mut(&"D".into()).unwrap();
module_d.module_type = ModuleType::Js;
module_d.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_accepted: true,
hmr_self_accepted: true,
..Default::default()
});
let module_c = module_graph.module_mut(&"C".into()).unwrap();
module_c.module_type = ModuleType::Js;
module_c.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_accepted: true,
hmr_self_accepted: true,
..Default::default()
});

Expand All @@ -165,7 +189,65 @@ mod tests {
let module_d = module_graph.module_mut(&"D".into()).unwrap();
module_d.module_type = ModuleType::Js;
module_d.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_accepted: true,
hmr_self_accepted: true,
..Default::default()
});

let context = create_context(module_graph);
let boundaries = find_hmr_boundaries(&vec!["F".into()], &context);
// Be careful, the order of the paths may not be guaranteed. check the order if the test fails.
assert_eq!(boundaries, HashMap::new());
}

#[test]
fn find_hmr_boundaries_deps_1() {
let mut module_graph = construct_test_module_graph();

let module_d = module_graph.module_mut(&"D".into()).unwrap();
module_d.module_type = ModuleType::Js;
module_d.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_self_accepted: false,
hmr_accepted_deps: HashSet::from(["F".into()]),
..Default::default()
});
let module_c = module_graph.module_mut(&"C".into()).unwrap();
module_c.module_type = ModuleType::Js;
module_c.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_self_accepted: false,
hmr_accepted_deps: HashSet::from(["F".into()]),
..Default::default()
});

let context = create_context(module_graph);
let boundaries = find_hmr_boundaries(&vec!["F".into()], &context);
// Be careful, the order of the paths may not be guaranteed. check the order if the test fails.
assert_eq!(
boundaries,
vec![(
"F".into(),
vec![vec!["F".into(), "D".into()], vec!["F".into(), "C".into()]]
)]
.into_iter()
.collect::<HashMap<_, _>>()
);
}

#[test]
fn find_hmr_boundaries_deps_2() {
let mut module_graph = construct_test_module_graph();

let module_d = module_graph.module_mut(&"D".into()).unwrap();
module_d.module_type = ModuleType::Js;
module_d.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_self_accepted: false,
hmr_accepted_deps: HashSet::from(["F".into()]),
..Default::default()
});

let module_f = module_graph.module_mut(&"F".into()).unwrap();
module_f.module_type = ModuleType::Js;
module_f.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_self_accepted: false,
..Default::default()
});

Expand All @@ -174,4 +256,41 @@ mod tests {
// Be careful, the order of the paths may not be guaranteed. check the order if the test fails.
assert_eq!(boundaries, HashMap::new());
}

#[test]
fn find_hmr_boundaries_deps_3() {
let mut module_graph = construct_test_module_graph();

let module_b = module_graph.module_mut(&"B".into()).unwrap();
module_b.module_type = ModuleType::Js;
module_b.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_self_accepted: false,
hmr_accepted_deps: HashSet::from(["E".into()]),
..Default::default()
});

let module_e = module_graph.module_mut(&"E".into()).unwrap();
module_e.module_type = ModuleType::Js;
module_e.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_self_accepted: false,
..Default::default()
});

let module_g = module_graph.module_mut(&"G".into()).unwrap();
module_g.module_type = ModuleType::Js;
module_g.meta = ModuleMetaData::Script(ScriptModuleMetaData {
hmr_self_accepted: false,
..Default::default()
});

let context = create_context(module_graph);
let boundaries = find_hmr_boundaries(&vec!["G".into()], &context);
// Be careful, the order of the paths may not be guaranteed. check the order if the test fails.
assert_eq!(
boundaries,
vec![("G".into(), vec![vec!["G".into(), "E".into(), "B".into()]])]
.into_iter()
.collect::<HashMap<_, _>>()
);
}
}
2 changes: 1 addition & 1 deletion crates/compiler/src/update/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ impl Compiler {
render_and_generate_update_resource(&updated_module_ids, &diff_result, &self.context)?
};

// find the boundaries. TODO: detect the boundaries in the client side.
// find the boundaries.
let boundaries = find_hmr_boundaries::find_hmr_boundaries(&updated_module_ids, &self.context);

// TODO: support sourcemap for hmr. and should generate the hmr update response body in rust side.
Expand Down
3 changes: 3 additions & 0 deletions crates/compiler/tests/fixtures/script/accept_deps/bar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function bar() {
return 'bar';
}
15 changes: 15 additions & 0 deletions crates/compiler/tests/fixtures/script/accept_deps/dep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { foo } from './foo.js'
import { bar } from './bar.js'

foo()
bar()

// Can also accept an array of dep modules:
import.meta.hot.accept(
['./foo.js', './bar.js'],
([newFooModule, newBarModule]) => {
// The callback receives an array where only the updated module is
// non null. If the update was not successful (syntax error for ex.),
// the array is empty
},
)
3 changes: 3 additions & 0 deletions crates/compiler/tests/fixtures/script/accept_deps/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function foo() {
return 'foo';
}
11 changes: 11 additions & 0 deletions crates/compiler/tests/fixtures/script/accept_deps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { foo } from './foo.js'
import './dep';

foo()

if (import.meta.hot) {
import.meta.hot.accept('./foo.js', (newFoo) => {
// the callback receives the updated './foo.js' module
newFoo?.foo()
});
}
Loading

0 comments on commit 8846d06

Please sign in to comment.