Skip to content

Commit

Permalink
refactor: diff context module lazy once mode (#6398)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahabhgk authored Apr 29, 2024
1 parent a1ec4c1 commit 5cf18b3
Show file tree
Hide file tree
Showing 20 changed files with 216 additions and 23 deletions.
100 changes: 82 additions & 18 deletions crates/rspack_core/src/context_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ use rustc_hash::FxHashMap as HashMap;
use rustc_hash::FxHashSet as HashSet;

use crate::{
contextify, get_exports_type_with_strict, impl_module_meta_info, returning_function,
stringify_map, to_path, AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency,
BuildContext, BuildInfo, BuildMeta, BuildMetaDefaultObject, BuildMetaExportsType, BuildResult,
ChunkGraph, ChunkGroupOptions, CodeGenerationResult, Compilation, ConcatenationScope,
ContextElementDependency, DependenciesBlock, Dependency, DependencyCategory, DependencyId,
DynamicImportMode, ExportsType, FactoryMeta, FakeNamespaceObjectMode, GroupOptions,
LibIdentOptions, Module, ModuleType, Resolve, ResolveInnerOptions,
ResolveOptionsWithDependencyType, ResolverFactory, RuntimeGlobals, RuntimeSpec, SourceType,
block_promise, contextify, get_exports_type_with_strict, impl_module_meta_info,
returning_function, stringify_map, to_path, AsyncDependenciesBlock,
AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildMeta,
BuildMetaDefaultObject, BuildMetaExportsType, BuildResult, ChunkGraph, ChunkGroupOptions,
CodeGenerationResult, Compilation, ConcatenationScope, ContextElementDependency,
DependenciesBlock, Dependency, DependencyCategory, DependencyId, DynamicImportMode, ExportsType,
FactoryMeta, FakeNamespaceObjectMode, GroupOptions, LibIdentOptions, Module, ModuleType, Resolve,
ResolveInnerOptions, ResolveOptionsWithDependencyType, ResolverFactory, RuntimeGlobals,
RuntimeSpec, SourceType,
};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -277,7 +278,7 @@ impl ContextModule {
fn get_fake_map_init_statement(&self, fake_map: &FakeMapValue) -> String {
match fake_map {
FakeMapValue::Bit(_) => "".to_string(),
FakeMapValue::Map(map) => json_stringify(map),
FakeMapValue::Map(map) => format!("var fakeMap = {}", json_stringify(map)),
}
}

Expand Down Expand Up @@ -377,7 +378,11 @@ impl ContextModule {
}

#[inline]
fn get_source_string(&self, compilation: &Compilation) -> BoxSource {
fn get_source_string(
&self,
compilation: &Compilation,
code_gen_result: &mut CodeGenerationResult,
) -> BoxSource {
match self.options.context_options.mode {
ContextMode::Lazy => {
if !self.get_blocks().is_empty() {
Expand All @@ -387,13 +392,11 @@ impl ContextModule {
}
}
ContextMode::LazyOnce => {
let module_graph = compilation.get_module_graph();
let block = self
.get_blocks()
.first()
.expect("LazyOnce ContextModule should have first block");
let block = module_graph.block_by_id(block).expect("should have block");
self.generate_source(block.get_dependencies(), compilation)
if let Some(block) = self.get_blocks().first() {
self.get_lazy_once_source(compilation, block, code_gen_result)
} else {
self.get_source_for_empty_async_context(compilation)
}
}
ContextMode::Sync => {
if !self.get_dependencies().is_empty() {
Expand Down Expand Up @@ -560,6 +563,67 @@ impl ContextModule {
source.boxed()
}

fn get_lazy_once_source(
&self,
compilation: &Compilation,
block_id: &AsyncDependenciesBlockIdentifier,
code_gen_result: &mut CodeGenerationResult,
) -> BoxSource {
let mg = compilation.get_module_graph();
let block = mg.block_by_id_expect(block_id);
let dependencies = block.get_dependencies();
let promise = block_promise(
Some(block_id),
&mut code_gen_result.runtime_requirements,
compilation,
);
let map = self.get_user_request_map(dependencies, compilation);
let fake_map = self.get_fake_map(dependencies, compilation);
let then_function = if !matches!(
fake_map,
FakeMapValue::Bit(FakeNamespaceObjectMode::NAMESPACE)
) {
formatdoc! {r#"
function(id) {{
{}
}}
"#,
self.get_return_module_object_source(&fake_map, true, "fakeMap[id]"),
}
} else {
RuntimeGlobals::REQUIRE.name().to_string()
};
let source = formatdoc! {r#"
var map = {map};
{fake_map_init_statement}
function webpackAsyncContext(req) {{
return webpackAsyncContextResolve(req).then({then_function});
}}
function webpackAsyncContextResolve(req) {{
return {promise}.then(function() {{
if(!{has_own_property}(map, req)) {{
var e = new Error("Cannot find module '" + req + "'");
e.code = 'MODULE_NOT_FOUND';
throw e;
}}
return map[req];
}})
}}
webpackAsyncContext.keys = {keys};
webpackAsyncContext.resolve = webpackAsyncContextResolve;
webpackAsyncContext.id = {id};
module.exports = webpackAsyncContext;
"#,
map = json_stringify(&map),
fake_map_init_statement = self.get_fake_map_init_statement(&fake_map),
has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY,
keys = returning_function("Object.keys(map)", ""),
id = json_stringify(self.id(&compilation.chunk_graph))
};
RawSource::from(source).boxed()
}

fn get_sync_source(&self, compilation: &Compilation) -> BoxSource {
let dependencies = self.get_dependencies();
let map = self.get_user_request_map(dependencies, compilation);
Expand Down Expand Up @@ -799,7 +863,7 @@ impl Module for ContextModule {
_: Option<ConcatenationScope>,
) -> Result<CodeGenerationResult> {
let mut code_generation_result = CodeGenerationResult::default();
let source = self.get_source_string(compilation);
let source = self.get_source_string(compilation, &mut code_generation_result);
code_generation_result.add(SourceType::JavaScript, source);
let mut all_deps = self.get_dependencies().to_vec();
let module_graph = compilation.get_module_graph();
Expand Down
11 changes: 11 additions & 0 deletions crates/rspack_core/src/module_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,17 @@ impl<'a> ModuleGraph<'a> {
self.loop_partials(|p| p.blocks.get(block_id))?.as_ref()
}

pub fn block_by_id_expect(
&self,
block_id: &AsyncDependenciesBlockIdentifier,
) -> &AsyncDependenciesBlock {
self
.loop_partials(|p| p.blocks.get(block_id))
.expect("should insert block before get it")
.as_ref()
.expect("block has been removed to None")
}

pub fn dependencies(&self) -> HashMap<DependencyId, &BoxDependency> {
let mut res = HashMap::default();
for item in self.partials.iter() {
Expand Down
2 changes: 1 addition & 1 deletion crates/rspack_core/src/options/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl From<&str> for DynamicImportMode {
"weak" => DynamicImportMode::Weak,
"eager" => DynamicImportMode::Eager,
"lazy" => DynamicImportMode::Lazy,
"lazyOnce" => DynamicImportMode::LazyOnce,
"lazy-once" => DynamicImportMode::LazyOnce,
_ => {
// TODO: warning
DynamicImportMode::default()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import "./dynamic-import";
import "./cjs-require";
import "./namespace-object-lazy"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
exports.__esModule = true;
exports.default = "default";
exports.named = "named";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exports.default = "default";
exports.named = "named";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exports.named = "named";
exports.default = "default";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exports.named = "named";
exports.default = "default";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
exports.__esModule = true;
exports.named = "named";
exports.default = "default";

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default "default";
export var named = "named";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default "default";
export var named = "named";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default "default";
export var named = "named";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"default": "default",
"named": "named"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exports.named = "named";
exports.default = "default";
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default "default";
export var named = "named";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
exports.__esModule = true;
exports.named = "named";
exports.default = "default";

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// non-mjs-namespace-object-lazy

it("should receive a namespace object when importing commonjs", function(done) {
import("./cjs").then(function(result) {
expect(result).toEqual(nsObj({ named: "named", default: { named: "named", default: "default" } }));
done();
}).catch(done);
});

it("should receive a namespace object when importing commonjs with __esModule", function(done) {
import("./cjs-esmodule").then(function(result) {
expect(result).toEqual({ __esModule: true, named: "named", default: "default" });
done();
}).catch(done);
});

function contextCJS(name) {
return Promise.all([
import(`./dir-cjs/${name}`),
import(/* webpackMode: "lazy-once", webpackChunkName: "dir-cjs-1" */`./dir-cjs?1/${name}`),
// import(/* webpackMode: "eager" */`./dir-cjs?2/${name}`)
]).then(function(results) {
// return import(/* webpackMode: "weak" */`./dir-cjs/${name}`).then(function(r) {
// results.push(r);
// return results;
// });
});
}

function contextHarmony(name) {
return Promise.all([
import(`./dir-harmony/${name}`),
import(/* webpackMode: "lazy-once", webpackChunkName: "dir-harmony-1" */`./dir-harmony?1/${name}`),
// import(/* webpackMode: "eager" */`./dir-harmony?2/${name}`)
]).then(function(results) {
// return import(/* webpackMode: "weak" */`./dir-harmony/${name}`).then(function(r) {
// results.push(r);
// return results;
// });
});
}

function contextMixed(name) {
return Promise.all([
import(`./dir-mixed/${name}`),
import(/* webpackMode: "lazy-once", webpackChunkName: "dir-mixed-1" */`./dir-mixed?1/${name}`),
// import(/* webpackMode: "eager" */`./dir-mixed?2/${name}`)
]).then(function(results) {
// return import(/* webpackMode: "weak" */`./dir-mixed/${name}`).then(function(r) {
// results.push(r);
// return results;
// });
});
}

function promiseTest(promise, equalsTo) {
return promise.then(function(results) {
for(const result of results)
expect(result).toEqual(equalsTo);
});
}

it("should receive a namespace object when importing commonjs via context", function() {
return Promise.all([
promiseTest(contextCJS("one"), nsObj({ named: "named", default: { named: "named", default: "default" } })),
promiseTest(contextCJS("two"), { __esModule: true, named: "named", default: "default" }),
promiseTest(contextCJS("three"), nsObj({ named: "named", default: { named: "named", default: "default" } })),
promiseTest(contextCJS("null"), nsObj({ default: null }))
]);
});

it("should receive a namespace object when importing harmony via context", function() {
return Promise.all([
promiseTest(contextHarmony("one"), nsObj({ named: "named", default: "default" })),
promiseTest(contextHarmony("two"), nsObj({ named: "named", default: "default" })),
promiseTest(contextHarmony("three"), nsObj({ named: "named", default: "default" }))
]);
});

it("should receive a namespace object when importing mixed content via context", function() {
return Promise.all([
promiseTest(contextMixed("one"), nsObj({ named: "named", default: { named: "named", default: "default" } })),
promiseTest(contextMixed("two"), { __esModule: true, named: "named", default: "default" }),
promiseTest(contextMixed("three"), nsObj({ named: "named", default: "default" })),
promiseTest(contextMixed("null"), nsObj({ default: null })),
promiseTest(contextMixed("json.json"), nsObj({ named: "named", default: { named: "named", default: "default" } }))
]);
});
4 changes: 0 additions & 4 deletions webpack-test/cases/runtime/issue-15518/test.filter.js

This file was deleted.

2 comments on commit 5cf18b3

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs, self-hosted, Linux, ci ❌ failure
_selftest, ubuntu-latest ✅ success
nx, ubuntu-latest ✅ success
rspress, ubuntu-latest ❌ failure
rsbuild, ubuntu-latest ✅ success
compat, ubuntu-latest ✅ success
examples, ubuntu-latest ✅ success

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2024-04-29 ec3c0df) Current Change
10000_development-mode + exec 2.8 s ± 43 ms 2.83 s ± 36 ms +1.22 %
10000_development-mode_hmr + exec 690 ms ± 16 ms 703 ms ± 7.5 ms +1.81 %
10000_production-mode + exec 2.49 s ± 18 ms 2.64 s ± 35 ms +6.12 %
arco-pro_development-mode + exec 2.54 s ± 39 ms 2.6 s ± 85 ms +2.12 %
arco-pro_development-mode_hmr + exec 431 ms ± 2.6 ms 431 ms ± 2 ms +0.17 %
arco-pro_development-mode_hmr_intercept-plugin + exec 443 ms ± 2.5 ms 443 ms ± 1.7 ms +0.07 %
arco-pro_development-mode_intercept-plugin + exec 3.25 s ± 72 ms 3.37 s ± 69 ms +3.74 %
arco-pro_production-mode + exec 3.99 s ± 103 ms 4.14 s ± 93 ms +3.72 %
arco-pro_production-mode_intercept-plugin + exec 4.75 s ± 102 ms 4.99 s ± 98 ms +4.95 %
threejs_development-mode_10x + exec 2.08 s ± 23 ms 2.12 s ± 32 ms +2.18 %
threejs_development-mode_10x_hmr + exec 771 ms ± 32 ms 773 ms ± 11 ms +0.25 %
threejs_production-mode_10x + exec 5.26 s ± 54 ms 5.3 s ± 83 ms +0.79 %

Threshold exceeded: ["10000_production-mode + exec"]

Please sign in to comment.