Skip to content

Commit

Permalink
fix: runtime condition of hot accept dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
LingyuCoder committed Feb 8, 2024
1 parent 9401101 commit 625e982
Show file tree
Hide file tree
Showing 18 changed files with 279 additions and 22 deletions.
12 changes: 4 additions & 8 deletions crates/rspack_core/src/dependency/runtime_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
compile_boolean_matcher_from_lists, get_import_var, property_access, to_comment,
to_normal_comment, AsyncDependenciesBlockId, ChunkGraph, Compilation, DependenciesBlock,
DependencyId, ExportsArgument, ExportsType, FakeNamespaceObjectMode, InitFragmentExt,
InitFragmentKey, InitFragmentStage, ModuleGraph, ModuleIdentifier, NormalInitFragment,
InitFragmentKey, InitFragmentStage, Module, ModuleGraph, ModuleIdentifier, NormalInitFragment,
RuntimeCondition, RuntimeGlobals, RuntimeSpec, TemplateContext,
};

Expand Down Expand Up @@ -277,17 +277,13 @@ pub fn module_id(
}

pub fn import_statement(
code_generatable_context: &mut TemplateContext,
module: &dyn Module,
compilation: &Compilation,
runtime_requirements: &mut RuntimeGlobals,
id: &DependencyId,
request: &str,
update: bool, // whether a new variable should be created or the existing one updated
) -> (String, String) {
let TemplateContext {
runtime_requirements,
compilation,
module,
..
} = code_generatable_context;
if compilation
.module_graph
.module_identifier_by_dependency_id(id)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
use std::sync::Arc;
use std::sync::{Arc, Mutex};

use once_cell::sync::Lazy;
use rspack_core::tree_shaking::symbol::{self, IndirectTopLevelSymbol};
use rspack_core::tree_shaking::visitor::SymbolRef;
use rspack_core::{
get_import_var, import_statement, AsContextDependency, AwaitDependenciesInitFragment,
ConnectionState, Dependency, DependencyCategory, DependencyCondition, DependencyId,
DependencyTemplate, DependencyType, ErrorSpan, ExtendedReferencedExport, InitFragmentExt,
InitFragmentKey, InitFragmentStage, ModuleDependency, ModuleIdentifier, NormalInitFragment,
RuntimeGlobals, TemplateContext, TemplateReplaceSource,
filter_runtime, get_import_var, import_statement, merge_runtime, AsContextDependency,
AwaitDependenciesInitFragment, ConnectionState, Dependency, DependencyCategory,
DependencyCondition, DependencyId, DependencyTemplate, DependencyType, ErrorSpan,
ExtendedReferencedExport, InitFragmentExt, InitFragmentKey, InitFragmentStage, ModuleDependency,
ModuleIdentifier, NormalInitFragment, RuntimeCondition, RuntimeGlobals, TemplateContext,
TemplateReplaceSource,
};
use rspack_core::{ModuleGraph, RuntimeSpec};
use rustc_hash::FxHashSet as HashSet;
use rustc_hash::{FxHashMap, FxHashSet as HashSet};
use swc_core::ecma::atoms::Atom;

pub type ImportEmittedMap =
FxHashMap<ModuleIdentifier, FxHashMap<ModuleIdentifier, RuntimeCondition>>;

use super::create_resource_identifier_for_esm_dependency;

// TODO: find a better way to implement this for performance
// Align with https://github.com/webpack/webpack/blob/51f0f0aeac072f989f8d40247f6c23a1995c5c37/lib/dependencies/HarmonyImportDependency.js#L361-L365
// This map is used to save the runtime conditions of modules and used by HarmonyAcceptDependency in hot module replacement.
// It can not be saved in TemplateContext because only dependencies of rebuild modules will be templated again.
static IMPORT_EMITTED_MAP: Mutex<Lazy<ImportEmittedMap>> = Mutex::new(Lazy::new(Default::default));

pub fn get_import_emitted_runtime(
module: &ModuleIdentifier,
referenced_module: &ModuleIdentifier,
) -> RuntimeCondition {
match IMPORT_EMITTED_MAP
.lock()
.expect("Can get import emitted map")
.get(module)
.and_then(|map| map.get(referenced_module))
{
Some(r) => r.to_owned(),
None => RuntimeCondition::Boolean(false),
}
}

#[derive(Debug, Clone)]
pub enum Specifier {
Namespace(Atom),
Expand Down Expand Up @@ -71,6 +97,7 @@ pub fn harmony_import_dependency_apply<T: ModuleDependency>(
compilation,
module,
runtime,
runtime_requirements,
..
} = code_generatable_context;
// Only available when module factorization is successful.
Expand Down Expand Up @@ -164,8 +191,22 @@ pub fn harmony_import_dependency_apply<T: ModuleDependency>(
return;
}
}

let runtime_condition = if let Some(connection) = compilation
.module_graph
.connection_by_dependency(module_dependency.id())
{
filter_runtime(*runtime, |r| {
connection.is_target_active(&compilation.module_graph, r)
})
} else {
RuntimeCondition::Boolean(true)
};

let content: (String, String) = import_statement(
code_generatable_context,
*module,
compilation,
runtime_requirements,
module_dependency.id(),
module_dependency.request(),
false,
Expand All @@ -187,6 +228,42 @@ pub fn harmony_import_dependency_apply<T: ModuleDependency>(
.map(|i| i.as_str())
.unwrap_or(module_dependency.request());
let key = format!("harmony import {}", module_key);

// NOTE: different with webpack
// The import emitted map is consumed by HarmonyAcceptDependency which enabled by `dev_server.hot`
if compilation.options.dev_server.hot {
if let Some(ref_module) = ref_module {
let mut imported_emitted_map = IMPORT_EMITTED_MAP
.lock()
.expect("Can not get import emitted map");

let emitted_modules = imported_emitted_map.entry(module.identifier()).or_default();

let old_runtime_condition = match emitted_modules.get(ref_module) {
Some(v) => v.to_owned(),
None => RuntimeCondition::Boolean(false),
};

let mut merged_runtime_condition = runtime_condition;
if !matches!(old_runtime_condition, RuntimeCondition::Boolean(false))
&& !matches!(merged_runtime_condition, RuntimeCondition::Boolean(true))
{
if matches!(merged_runtime_condition, RuntimeCondition::Boolean(false))
|| matches!(old_runtime_condition, RuntimeCondition::Boolean(true))
{
merged_runtime_condition = old_runtime_condition;
} else {
merged_runtime_condition = RuntimeCondition::Spec(merge_runtime(
old_runtime_condition.as_spec().expect("should be spec"),
merged_runtime_condition.as_spec().expect("should be spec"),
));
}
}
emitted_modules.insert(*ref_module, merged_runtime_condition);
drop(imported_emitted_map);
}
}

let is_async_module = matches!(ref_module, Some(ref_module) if compilation.module_graph.is_async(ref_module) == Some(true));
if is_async_module {
init_fragments.push(Box::new(NormalInitFragment::new(
Expand Down
1 change: 1 addition & 0 deletions crates/rspack_plugin_javascript/src/dependency/esm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub use self::harmony_export_expression_dependency::*;
pub use self::harmony_export_header_dependency::HarmonyExportHeaderDependency;
pub use self::harmony_export_imported_specifier_dependency::HarmonyExportImportedSpecifierDependency;
pub use self::harmony_export_specifier_dependency::HarmonyExportSpecifierDependency;
pub use self::harmony_import_dependency::get_import_emitted_runtime;
pub use self::harmony_import_dependency::harmony_import_dependency_apply;
pub use self::harmony_import_dependency::{HarmonyImportSideEffectDependency, Specifier};
pub use self::harmony_import_specifier_dependency::HarmonyImportSpecifierDependency;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use rspack_core::{
import_statement, AsDependency, DependencyId, DependencyTemplate, TemplateContext,
TemplateReplaceSource,
import_statement, runtime_condition_expression, AsDependency, DependencyId, DependencyTemplate,
RuntimeCondition, TemplateContext, TemplateReplaceSource,
};

use crate::dependency::get_import_emitted_runtime;

#[derive(Debug, Clone)]
pub struct HarmonyAcceptDependency {
start: u32,
Expand All @@ -28,12 +30,39 @@ impl DependencyTemplate for HarmonyAcceptDependency {
source: &mut TemplateReplaceSource,
code_generatable_context: &mut TemplateContext,
) {
let TemplateContext { compilation, .. } = code_generatable_context;
let TemplateContext {
compilation,
module,
runtime,
runtime_requirements,
..
} = code_generatable_context;

let mut content = String::default();

self.dependency_ids.iter().for_each(|id| {
let dependency = compilation.module_graph.dependency_by_id(id);
let runtime_condition =
match dependency.and_then(|dep| compilation.module_graph.get_module(dep.id())) {
Some(ref_module) => {
get_import_emitted_runtime(&module.identifier(), &ref_module.identifier())
}
None => RuntimeCondition::Boolean(false),
};

if matches!(runtime_condition, RuntimeCondition::Boolean(false)) {
return;
}

let condition = {
runtime_condition_expression(
&compilation.chunk_graph,
Some(&runtime_condition),
*runtime,
runtime_requirements,
)
};

let request = if let Some(dependency) = dependency.and_then(|d| d.as_module_dependency()) {
Some(dependency.request())
} else {
Expand All @@ -42,9 +71,23 @@ impl DependencyTemplate for HarmonyAcceptDependency {
.map(|d| d.request())
};
if let Some(request) = request {
let stmts = import_statement(code_generatable_context, id, request, true);
content.push_str(stmts.0.as_str());
content.push_str(stmts.1.as_str());
let stmts = import_statement(
*module,
compilation,
runtime_requirements,
id,
request,
true,
);
if condition == "true" {
content.push_str(stmts.0.as_str());
content.push_str(stmts.1.as_str());
} else {
content.push_str(format!("if ({}) {{\n", condition).as_str());
content.push_str(stmts.0.as_str());
content.push_str(stmts.1.as_str());
content.push_str("\n}\n");
}
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// TODO: remove this file after cache.
const path = require('path');

module.exports = [
path.resolve(__dirname, './moduleA.js'),
path.resolve(__dirname, './moduleAs.js'),
path.resolve(__dirname, './moduleB.js'),
path.resolve(__dirname, './moduleBs.js'),
]
1 change: 1 addition & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/chunk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "chunk";
1 change: 1 addition & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/chunkS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "chunkS";
33 changes: 33 additions & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const update = () =>
new Promise((resolve, reject) => {
NEXT(err => {
if (err) reject(err);
else resolve();
});
});

const expectMessage = (w, msg) =>
new Promise((resolve, reject) => {
w.onmessage = ({ data }) => {
if (data === msg) resolve();
else reject(new Error(data));
};
});

const next = w => {
const p = expectMessage(w, "next");
w.postMessage("next");
return p;
};

it("should support hot module replacement in WebWorkers", async () => {
const a = new Worker(new URL("workerA.js", import.meta.url));
const b = new Worker(new URL("workerB.js", import.meta.url));
for (let i = 0; i < 7; i++) {
await update();
await next(a);
await next(b);
}
await a.terminate();
await b.terminate();
});
1 change: 1 addition & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "module";
15 changes: 15 additions & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/moduleA.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default 0;
---
export default 1;
---
export default 2;
---
export default 3;
---
export default 4;
---
export default 5;
---
export default 6;
---
export default 7;
15 changes: 15 additions & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/moduleAs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default 0;
---
export default 1;
---
export default 2;
---
export default 3;
---
export default 4;
---
export default 5;
---
export default 6;
---
export default 7;
15 changes: 15 additions & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/moduleB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default 0;
---
export default 1;
---
export default 2;
---
export default 3;
---
export default 4;
---
export default 5;
---
export default 6;
---
export default 7;
15 changes: 15 additions & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/moduleBs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default 0;
---
export default 1;
---
export default 2;
---
export default 3;
---
export default 4;
---
export default 5;
---
export default 6;
---
export default 7;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "moduleS";
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
output: {
filename: '[name].[fullhash].js'
}
}
19 changes: 19 additions & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

export default fn => {
self.onmessage = async ({ data: msg }) => {
try {
switch (msg) {
case "next":
if (!(await import.meta.webpackHot.check(true)))
throw new Error("No update found");
await fn();
self.postMessage("next");
break;
default:
throw new Error("Unexpected message");
}
} catch (e) {
self.postMessage("error: " + e.stack);
}
};
};
5 changes: 5 additions & 0 deletions packages/rspack/tests/hotCases/worker/issue-5597/workerA.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import worker from "./worker";
import "./moduleA";
worker(() => import(/* webpackChunkName: "shared" */ "./moduleAs"));
import.meta.webpackHot.accept("./moduleA");
import.meta.webpackHot.accept("./moduleAs");
Loading

0 comments on commit 625e982

Please sign in to comment.