Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hmr): refactor hmr #835

Merged
merged 15 commits into from
Dec 24, 2023
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
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
Loading