Skip to content

Commit c77b8f7

Browse files
authored
feat: diff context module sync mode (#6354)
1 parent bc61872 commit c77b8f7

File tree

35 files changed

+414
-262
lines changed

35 files changed

+414
-262
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/rspack_core/src/context_module.rs

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rspack_identifier::{Identifiable, Identifier};
1717
use rspack_macros::impl_source_map_config;
1818
use rspack_regex::RspackRegex;
1919
use rspack_sources::{BoxSource, ConcatSource, RawSource, SourceExt};
20-
use rspack_util::{json_stringify, source_map::SourceMapKind};
20+
use rspack_util::{fx_hash::FxIndexMap, json_stringify, source_map::SourceMapKind};
2121
use rustc_hash::FxHashMap as HashMap;
2222
use rustc_hash::FxHashSet as HashSet;
2323

@@ -146,6 +146,7 @@ pub struct ContextOptions {
146146
pub context: String,
147147
pub namespace_object: ContextNameSpaceObject,
148148
pub group_options: Option<GroupOptions>,
149+
pub replaces: Vec<(String, u32, u32)>,
149150
pub start: u32,
150151
pub end: u32,
151152
}
@@ -273,6 +274,13 @@ impl ContextModule {
273274
}
274275
}
275276

277+
fn get_fake_map_init_statement(&self, fake_map: &FakeMapValue) -> String {
278+
match fake_map {
279+
FakeMapValue::Bit(_) => "".to_string(),
280+
FakeMapValue::Map(map) => json_stringify(map),
281+
}
282+
}
283+
276284
fn get_return_module_object_source(
277285
&self,
278286
fake_map: &FakeMapValue,
@@ -306,36 +314,26 @@ impl ContextModule {
306314
&self,
307315
dependencies: impl IntoIterator<Item = &DependencyId>,
308316
compilation: &Compilation,
309-
) -> HashMap<String, String> {
317+
) -> FxIndexMap<String, Option<String>> {
318+
let module_graph = compilation.get_module_graph();
310319
let dependencies = dependencies.into_iter();
311-
let mut map = HashMap::default();
312-
for dependency in dependencies {
313-
if let Some(module_identifier) = compilation
314-
.get_module_graph()
315-
.module_identifier_by_dependency_id(dependency)
316-
{
317-
if let Some(dependency) = compilation.get_module_graph().dependency_by_id(dependency) {
318-
let request = if let Some(d) = dependency.as_module_dependency() {
320+
dependencies
321+
.filter_map(|dep_id| {
322+
let dep = module_graph.dependency_by_id(dep_id).and_then(|dep| {
323+
if let Some(d) = dep.as_module_dependency() {
319324
Some(d.user_request().to_string())
320325
} else {
321-
dependency
322-
.as_context_dependency()
323-
.map(|d| d.request().to_string())
324-
};
325-
if let Some(request) = request {
326-
map.insert(
327-
request,
328-
if let Some(module_id) = compilation.chunk_graph.get_module_id(*module_identifier) {
329-
format!("\"{module_id}\"")
330-
} else {
331-
"null".to_string()
332-
},
333-
);
326+
dep.as_context_dependency().map(|d| d.request().to_string())
334327
}
335-
}
336-
}
337-
}
338-
map
328+
});
329+
let module_id = module_graph
330+
.module_identifier_by_dependency_id(dep_id)
331+
.and_then(|module| compilation.chunk_graph.get_module_id(*module).clone());
332+
// module_id could be None in weak mode
333+
dep.map(|dep| (dep, module_id))
334+
})
335+
.sorted_by(|(a, _), (b, _)| a.cmp(b))
336+
.collect()
339337
}
340338

341339
fn get_source_for_empty_async_context(&self, compilation: &Compilation) -> BoxSource {
@@ -360,6 +358,24 @@ impl ContextModule {
360358
.boxed()
361359
}
362360

361+
fn get_source_for_empty_context(&self, compilation: &Compilation) -> BoxSource {
362+
RawSource::from(formatdoc! {r#"
363+
function webpackEmptyContext(req) {{
364+
var e = new Error("Cannot find module '" + req + "'");
365+
e.code = 'MODULE_NOT_FOUND';
366+
throw e;
367+
}}
368+
webpackEmptyContext.keys = {keys};
369+
webpackEmptyContext.resolve = webpackEmptyContext;
370+
webpackEmptyContext.id = {id};
371+
module.exports = webpackEmptyContext;
372+
"#,
373+
keys = returning_function("[]", ""),
374+
id = json_stringify(self.id(&compilation.chunk_graph))
375+
})
376+
.boxed()
377+
}
378+
363379
#[inline]
364380
fn get_source_string(&self, compilation: &Compilation) -> BoxSource {
365381
match self.options.context_options.mode {
@@ -379,6 +395,13 @@ impl ContextModule {
379395
let block = module_graph.block_by_id(block).expect("should have block");
380396
self.generate_source(block.get_dependencies(), compilation)
381397
}
398+
ContextMode::Sync => {
399+
if !self.get_dependencies().is_empty() {
400+
self.get_sync_source(compilation)
401+
} else {
402+
self.get_source_for_empty_context(compilation)
403+
}
404+
}
382405
_ => self.generate_source(self.get_dependencies(), compilation),
383406
}
384407
}
@@ -530,13 +553,50 @@ impl ContextModule {
530553
webpackAsyncContext.id = {id};
531554
module.exports = webpackAsyncContext;
532555
"#,
533-
map = stringify_map(&map),
556+
map = json_stringify(&map),
534557
keys = returning_function("Object.keys(map)", ""),
535558
id = json_stringify(self.id(&compilation.chunk_graph))
536559
}));
537560
source.boxed()
538561
}
539562

563+
fn get_sync_source(&self, compilation: &Compilation) -> BoxSource {
564+
let dependencies = self.get_dependencies();
565+
let map = self.get_user_request_map(dependencies, compilation);
566+
let fake_map = self.get_fake_map(dependencies, compilation);
567+
let return_module_object =
568+
self.get_return_module_object_source(&fake_map, false, "fakeMap[id]");
569+
let source = formatdoc! {r#"
570+
var map = {map};
571+
{fake_map_init_statement}
572+
573+
function webpackContext(req) {{
574+
var id = webpackContextResolve(req);
575+
{return_module_object}
576+
}}
577+
function webpackContextResolve(req) {{
578+
if(!{has_own_property}(map, req)) {{
579+
var e = new Error("Cannot find module '" + req + "'");
580+
e.code = 'MODULE_NOT_FOUND';
581+
throw e;
582+
}}
583+
return map[req];
584+
}}
585+
webpackContext.keys = function webpackContextKeys() {{
586+
return Object.keys(map);
587+
}};
588+
webpackContext.resolve = webpackContextResolve;
589+
module.exports = webpackContext;
590+
webpackContext.id = {id};
591+
"#,
592+
map = json_stringify(&map),
593+
fake_map_init_statement = self.get_fake_map_init_statement(&fake_map),
594+
has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY,
595+
id = json_stringify(self.id(&compilation.chunk_graph))
596+
};
597+
RawSource::from(source).boxed()
598+
}
599+
540600
fn generate_source(&self, dependencies: &[DependencyId], compilation: &Compilation) -> BoxSource {
541601
let map = self.get_user_request_map(dependencies, compilation);
542602
let fake_map = self.get_fake_map(dependencies, compilation);
@@ -561,7 +621,7 @@ impl ContextModule {
561621
let mut source = ConcatSource::default();
562622
source.add(RawSource::from(format!(
563623
"var map = {};\n",
564-
stringify_map(&map)
624+
json_stringify(&map)
565625
)));
566626
if let FakeMapValue::Map(map) = &fake_map {
567627
source.add(RawSource::from(format!(
@@ -623,7 +683,7 @@ impl ContextModule {
623683
source.add(RawSource::from("\n}\n"));
624684

625685
source.add(RawSource::from(format!(
626-
"webpackContext.id = '{}';\n",
686+
"webpackContext.id = {};\n",
627687
serde_json::to_string(self.id(&compilation.chunk_graph))
628688
.unwrap_or_else(|e| panic!("{}", e.to_string()))
629689
)));
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::{
2+
AsDependency, DependencyTemplate, InitFragmentExt, InitFragmentKey, InitFragmentStage,
3+
NormalInitFragment, TemplateContext, TemplateReplaceSource,
4+
};
5+
6+
#[derive(Debug, Clone)]
7+
pub struct CachedConstDependency {
8+
pub start: u32,
9+
pub end: u32,
10+
pub identifier: Box<str>,
11+
pub content: Box<str>,
12+
}
13+
14+
impl CachedConstDependency {
15+
pub fn new(start: u32, end: u32, identifier: Box<str>, content: Box<str>) -> Self {
16+
Self {
17+
start,
18+
end,
19+
identifier,
20+
content,
21+
}
22+
}
23+
}
24+
25+
impl DependencyTemplate for CachedConstDependency {
26+
fn apply(
27+
&self,
28+
source: &mut TemplateReplaceSource,
29+
code_generatable_context: &mut TemplateContext,
30+
) {
31+
code_generatable_context.init_fragments.push(
32+
NormalInitFragment::new(
33+
format!("var {} = {};\n", self.identifier, self.content),
34+
InitFragmentStage::StageConstants,
35+
0,
36+
InitFragmentKey::Const(self.identifier.to_string()),
37+
None,
38+
)
39+
.boxed(),
40+
);
41+
source.replace(self.start, self.end, &self.identifier, None);
42+
}
43+
44+
fn dependency_id(&self) -> Option<crate::DependencyId> {
45+
None
46+
}
47+
}
48+
49+
impl AsDependency for CachedConstDependency {}

crates/rspack_core/src/dependency/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod cached_const_dependency;
12
mod const_dependency;
23
mod context_dependency;
34
mod context_element_dependency;
@@ -17,6 +18,7 @@ mod static_exports_dependency;
1718

1819
use std::sync::Arc;
1920

21+
pub use cached_const_dependency::CachedConstDependency;
2022
pub use const_dependency::ConstDependency;
2123
pub use context_dependency::{AsContextDependency, ContextDependency};
2224
pub use context_element_dependency::ContextElementDependency;

crates/rspack_core/src/fake_namespace_object.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::fmt;
22

33
use bitflags::bitflags;
4+
use serde::Serialize;
45

56
bitflags! {
67
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
@@ -21,3 +22,12 @@ impl fmt::Display for FakeNamespaceObjectMode {
2122
f.write_fmt(format_args!("{}", self.bits()))
2223
}
2324
}
25+
26+
impl Serialize for FakeNamespaceObjectMode {
27+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
28+
where
29+
S: serde::Serializer,
30+
{
31+
serializer.serialize_u8(self.bits())
32+
}
33+
}

crates/rspack_core/src/init_fragment.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub type InitFragmentKeyUKey = rspack_database::Ukey<InitFragmentKeyUnique>;
2727

2828
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
2929
pub enum InitFragmentKey {
30+
Unique(InitFragmentKeyUKey),
3031
HarmonyImport(String),
3132
HarmonyExportStar(String), // TODO: align with webpack and remove this
3233
HarmonyExports,
@@ -35,8 +36,8 @@ pub enum InitFragmentKey {
3536
AwaitDependencies,
3637
HarmonyCompatibility,
3738
ModuleDecorator(String /* module_id */),
38-
Unique(InitFragmentKeyUKey),
3939
HarmonyFakeNamespaceObjectFragment(String),
40+
Const(String),
4041
}
4142

4243
impl InitFragmentKey {
@@ -113,7 +114,8 @@ impl InitFragmentKey {
113114
| InitFragmentKey::HarmonyExportStar(_)
114115
| InitFragmentKey::ExternalModule(_)
115116
| InitFragmentKey::ModuleDecorator(_)
116-
| InitFragmentKey::CommonJsExports(_) => first(fragments),
117+
| InitFragmentKey::CommonJsExports(_)
118+
| InitFragmentKey::Const(_) => first(fragments),
117119
InitFragmentKey::HarmonyCompatibility | InitFragmentKey::Unique(_) => {
118120
debug_assert!(fragments.len() == 1, "fragment = {:?}", self);
119121
first(fragments)

crates/rspack_plugin_javascript/src/dependency/context/common_js_require_context_dependency.rs

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use rspack_core::{module_raw, parse_resource, AsModuleDependency, ContextDependency};
2-
use rspack_core::{normalize_context, DependencyCategory, DependencyId, DependencyTemplate};
1+
use rspack_core::{AsModuleDependency, ContextDependency};
32
use rspack_core::{ContextOptions, Dependency, TemplateReplaceSource};
3+
use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate};
44
use rspack_core::{DependencyType, ErrorSpan, TemplateContext};
55

6-
use super::create_resource_identifier_for_context_dependency;
6+
use super::{
7+
context_dependency_template_as_require_call, create_resource_identifier_for_context_dependency,
8+
};
79

810
#[derive(Debug, Clone)]
911
pub struct CommonJsRequireContextDependency {
@@ -87,49 +89,14 @@ impl DependencyTemplate for CommonJsRequireContextDependency {
8789
source: &mut TemplateReplaceSource,
8890
code_generatable_context: &mut TemplateContext,
8991
) {
90-
let TemplateContext {
91-
compilation,
92-
runtime_requirements,
93-
..
94-
} = code_generatable_context;
95-
96-
let expr = module_raw(
97-
compilation,
98-
runtime_requirements,
99-
&self.id,
100-
self.request(),
101-
false,
92+
context_dependency_template_as_require_call(
93+
self,
94+
source,
95+
code_generatable_context,
96+
self.callee_start,
97+
self.callee_end,
98+
self.args_end,
10299
);
103-
104-
if compilation
105-
.get_module_graph()
106-
.module_graph_module_by_dependency_id(&self.id)
107-
.is_none()
108-
{
109-
source.replace(self.callee_start, self.args_end, &expr, None);
110-
return;
111-
}
112-
113-
source.replace(self.callee_start, self.callee_end, &expr, None);
114-
115-
let context = normalize_context(&self.options.request);
116-
let query = parse_resource(&self.options.request).and_then(|data| data.query);
117-
if !context.is_empty() {
118-
source.insert(self.callee_end, "(", None);
119-
source.insert(
120-
self.args_end,
121-
format!(".replace('{context}', './')").as_str(),
122-
None,
123-
);
124-
if let Some(query) = query {
125-
source.insert(
126-
self.args_end,
127-
format!(".replace('{query}', '')").as_str(),
128-
None,
129-
);
130-
}
131-
source.insert(self.args_end, ")", None);
132-
}
133100
}
134101

135102
fn dependency_id(&self) -> Option<DependencyId> {

0 commit comments

Comments
 (0)