diff --git a/crates/rspack_core/src/runtime_globals.rs b/crates/rspack_core/src/runtime_globals.rs index 9f49bd966ba1..ae79bc2a99a5 100644 --- a/crates/rspack_core/src/runtime_globals.rs +++ b/crates/rspack_core/src/runtime_globals.rs @@ -288,8 +288,9 @@ define_runtime_globals! { // defer import support const ASYNC_MODULE_EXPORT_SYMBOL; const MAKE_DEFERRED_NAMESPACE_OBJECT; - const MAKE_DEFERRED_NAMESPACE_OBJECT_SYMBOL; const MAKE_OPTIMIZED_DEFERRED_NAMESPACE_OBJECT; + const DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES; + const DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES_SYMBOL; // rspack only const ASYNC_STARTUP; @@ -358,8 +359,11 @@ pub fn runtime_globals_to_string( RuntimeGlobals::STARTUP => format!("{scope_name}.x"), RuntimeGlobals::MAKE_NAMESPACE_OBJECT => format!("{scope_name}.r"), RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT => format!("{scope_name}.z"), - RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT_SYMBOL => format!("{scope_name}.zS"), RuntimeGlobals::MAKE_OPTIMIZED_DEFERRED_NAMESPACE_OBJECT => format!("{scope_name}.zO"), + RuntimeGlobals::DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES => format!("{scope_name}.zT"), + RuntimeGlobals::DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES_SYMBOL => { + format!("{scope_name}.zS") + } RuntimeGlobals::EXPORTS => { runtime_variable_to_string(&RuntimeVariable::Exports, compiler_options) } diff --git a/crates/rspack_core/src/runtime_template.rs b/crates/rspack_core/src/runtime_template.rs index f899a8b50255..4a391448b088 100644 --- a/crates/rspack_core/src/runtime_template.rs +++ b/crates/rspack_core/src/runtime_template.rs @@ -1228,18 +1228,16 @@ impl ModuleCodegenRuntimeTemplate { request: &str, message: &str, weak: bool, + phase: ImportPhase, ) -> String { - if compilation - .get_module_graph() - .module_identifier_by_dependency_id(dep_id) - .is_none() - { + let mg = compilation.get_module_graph(); + let Some(target_module) = mg.get_module_by_dependency_id(dep_id) else { return self.missing_module_promise(request); }; let promise = self.block_promise(block, compilation, message); let exports_type = get_exports_type( - compilation.get_module_graph(), + mg, &compilation.module_graph_cache_artifact, dep_id, &module_id, @@ -1257,8 +1255,73 @@ impl ModuleCodegenRuntimeTemplate { } else { None }; - let mut fake_type = FakeNamespaceObjectMode::PROMISE_LIKE; + let mut appending; + + if phase.is_defer() && !target_module.build_meta().has_top_level_await { + let mode = render_make_deferred_namespace_mode_from_exports_type(exports_type); + let async_deps = get_outgoing_async_modules(compilation, target_module.as_ref()); + if !async_deps.is_empty() { + if let Some(header) = header { + let rendered_async_deps_fn = self.render_runtime_globals( + &RuntimeGlobals::DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES, + ); + appending = format!( + ".then({})", + self.basic_function( + "", + &format!( + "{header}\nreturn {}({})", + &rendered_async_deps_fn, + json_stringify(&async_deps) + ) + ) + ); + } else { + let rendered_async_deps_fn = self.render_runtime_globals( + &RuntimeGlobals::DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES, + ); + appending = format!( + ".then({})", + self.returning_function( + &format!( + "{}({})", + &rendered_async_deps_fn, + json_stringify(&async_deps) + ), + "" + ) + ); + } + appending.push_str(&format!( + ".then({}.bind({}, {module_id_expr}, {mode}))", + self.render_runtime_globals(&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT), + self.render_runtime_globals(&RuntimeGlobals::REQUIRE) + )); + } else if let Some(header) = header { + let rendered_async_deps_fn = + self.render_runtime_globals(&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT); + appending = format!( + ".then({})", + self.basic_function( + "", + &format!( + "{header}\nreturn {}({module_id_expr}, {mode});", + &rendered_async_deps_fn + ) + ) + ); + } else { + appending = format!( + ".then({}.bind({}, {module_id_expr}, {mode}))", + self.render_runtime_globals(&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT), + self.render_runtime_globals(&RuntimeGlobals::REQUIRE) + ); + } + return format!("{promise}{appending}"); + } + + let mut fake_type = FakeNamespaceObjectMode::PROMISE_LIKE; match exports_type { ExportsType::Namespace => { if let Some(header) = header { diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs index 36938e8fb249..9133f53e63fd 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs @@ -5,8 +5,8 @@ use rspack_cacheable::{ use rspack_core::{ AsContextDependency, Dependency, DependencyCategory, DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, ExportsType, - ExtendedReferencedExport, FactorizeInfo, ImportAttributes, ModuleDependency, ModuleGraph, - ModuleGraphCacheArtifact, ReferencedExport, ResourceIdentifier, TemplateContext, + ExtendedReferencedExport, FactorizeInfo, ImportAttributes, ImportPhase, ModuleDependency, + ModuleGraph, ModuleGraphCacheArtifact, ReferencedExport, ResourceIdentifier, TemplateContext, TemplateReplaceSource, create_exports_object_referenced, }; use swc_core::ecma::atoms::Atom; @@ -68,6 +68,7 @@ pub struct ImportDependency { #[cacheable(with=AsOption>>)] referenced_exports: Option>>, attributes: Option, + phase: ImportPhase, pub comments: Vec<(bool, String)>, resource_identifier: ResourceIdentifier, factorize_info: FactorizeInfo, @@ -80,6 +81,7 @@ impl ImportDependency { range: DependencyRange, referenced_exports: Option>>, attributes: Option, + phase: ImportPhase, optional: bool, comments: Vec<(bool, String)>, ) -> Self { @@ -91,6 +93,7 @@ impl ImportDependency { id: DependencyId::new(), referenced_exports, attributes, + phase, resource_identifier, factorize_info: Default::default(), optional, @@ -125,6 +128,10 @@ impl Dependency for ImportDependency { self.attributes.as_ref() } + fn get_phase(&self) -> ImportPhase { + self.phase + } + fn range(&self) -> Option { Some(self.range) } @@ -217,6 +224,7 @@ impl DependencyTemplate for ImportDependencyTemplate { dep.request(), dep.dependency_type().as_str(), false, + dep.get_phase(), ) .as_str(), None, diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs index e7804dbbdc33..550425e4e236 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs @@ -5,7 +5,7 @@ use rspack_cacheable::{ use rspack_core::{ AsContextDependency, Dependency, DependencyCategory, DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate, DependencyTemplateType, DependencyType, FactorizeInfo, - ImportAttributes, ModuleDependency, ModuleGraphCacheArtifact, ResourceIdentifier, + ImportAttributes, ImportPhase, ModuleDependency, ModuleGraphCacheArtifact, ResourceIdentifier, TemplateContext, TemplateReplaceSource, }; use swc_core::ecma::atoms::Atom; @@ -25,6 +25,7 @@ pub struct ImportEagerDependency { #[cacheable(with=AsOption>>)] referenced_exports: Option>>, attributes: Option, + phase: ImportPhase, resource_identifier: ResourceIdentifier, factorize_info: FactorizeInfo, } @@ -35,6 +36,7 @@ impl ImportEagerDependency { range: DependencyRange, referenced_exports: Option>>, attributes: Option, + phase: ImportPhase, ) -> Self { let resource_identifier = create_resource_identifier_for_esm_dependency(request.as_str(), attributes.as_ref()); @@ -44,6 +46,7 @@ impl ImportEagerDependency { id: DependencyId::new(), referenced_exports, attributes, + phase, resource_identifier, factorize_info: Default::default(), } @@ -76,6 +79,10 @@ impl Dependency for ImportEagerDependency { self.attributes.as_ref() } + fn get_phase(&self) -> ImportPhase { + self.phase + } + fn range(&self) -> Option { Some(self.range) } @@ -164,6 +171,7 @@ impl DependencyTemplate for ImportEagerDependencyTemplate { &dep.request, dep.dependency_type().as_str(), false, + dep.get_phase(), ) .as_str(), None, diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs index c19babe3efbc..fd111a4ead80 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use rspack_core::{ AsyncDependenciesBlock, ChunkGroupOptions, ContextDependency, ContextNameSpaceObject, ContextOptions, DependencyCategory, DependencyRange, DependencyType, DynamicImportFetchPriority, - DynamicImportMode, GroupOptions, ImportAttributes, + DynamicImportMode, GroupOptions, ImportAttributes, ImportPhase, }; use rspack_error::{Error, Severity}; use rspack_util::{SpanExt, swc::get_swc_comments}; @@ -321,6 +321,16 @@ impl JavascriptParserPlugin for ImportParserPlugin { parser.add_warning(error.into()); } + let phase: ImportPhase = node + .callee + .as_import() + .expect("should be import") + .phase + .into(); + if phase.is_defer() && !parser.compiler_options.experiments.defer_import { + parser.add_error(rspack_error::error!("deferImport is still an experimental feature. To continue using it, please enable 'experiments.deferImport'.").into()); + } + let attributes = get_attributes_from_call_expr(node); let param = parser.evaluate_expression(dyn_imported.expr.as_ref()); @@ -331,6 +341,7 @@ impl JavascriptParserPlugin for ImportParserPlugin { import_call_span.into(), exports, attributes, + phase, ); let dep_idx = parser.next_dependency_idx(); parser.add_dependency(Box::new(dep)); @@ -345,6 +356,7 @@ impl JavascriptParserPlugin for ImportParserPlugin { import_call_span.into(), exports, attributes, + phase, parser.in_try, get_swc_comments( parser.comments, @@ -378,6 +390,10 @@ impl JavascriptParserPlugin for ImportParserPlugin { return None; } + if phase.is_defer() { + parser.add_error(rspack_error::error!("import.defer() is not yet supported for ContextModule (the import path is a dynamic expression).").into()); + } + let ContextModuleScanResult { context, reg, diff --git a/crates/rspack_plugin_lazy_compilation/src/module.rs b/crates/rspack_plugin_lazy_compilation/src/module.rs index e2cb6714d7c0..249a4c79bb7e 100644 --- a/crates/rspack_plugin_lazy_compilation/src/module.rs +++ b/crates/rspack_plugin_lazy_compilation/src/module.rs @@ -5,8 +5,8 @@ use rspack_collections::Identifiable; use rspack_core::{ AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildMeta, BuildResult, ChunkGraph, CodeGenerationResult, Compilation, Context, - DependenciesBlock, DependencyId, DependencyRange, FactoryMeta, LibIdentOptions, Module, - ModuleArgument, ModuleCodeGenerationContext, ModuleFactoryCreateData, ModuleGraph, + DependenciesBlock, DependencyId, DependencyRange, FactoryMeta, ImportPhase, LibIdentOptions, + Module, ModuleArgument, ModuleCodeGenerationContext, ModuleFactoryCreateData, ModuleGraph, ModuleIdentifier, ModuleLayer, ModuleType, RuntimeGlobals, RuntimeSpec, SourceType, ValueCacheVersions, impl_module_meta_info, module_update_hash, rspack_sources::{BoxSource, RawStringSource}, @@ -262,7 +262,8 @@ impl Module for LazyCompilationProxyModule { Some(block_id), &self.resource, "import()", - false + false, + ImportPhase::Evaluation, ), json_stringify( ChunkGraph::get_module_id(&compilation.module_ids_artifact, *module) diff --git a/crates/rspack_plugin_rstest/src/parser_plugin.rs b/crates/rspack_plugin_rstest/src/parser_plugin.rs index 5c27bdd50a3e..e6aea4f86268 100644 --- a/crates/rspack_plugin_rstest/src/parser_plugin.rs +++ b/crates/rspack_plugin_rstest/src/parser_plugin.rs @@ -1,6 +1,7 @@ use camino::Utf8PathBuf; use rspack_core::{ - AsyncDependenciesBlock, ConstDependency, DependencyRange, ImportAttributes, RuntimeGlobals, + AsyncDependenciesBlock, ConstDependency, DependencyRange, ImportAttributes, ImportPhase, + RuntimeGlobals, }; use rspack_plugin_javascript::{ JavascriptParserPlugin, @@ -143,6 +144,7 @@ impl RstestParserPlugin { call_expr.span.into(), None, Some(attrs), + ImportPhase::Evaluation, parser.in_try, get_swc_comments( parser.comments, @@ -467,6 +469,7 @@ impl RstestParserPlugin { call_expr.span.into(), None, Some(attrs), + ImportPhase::Evaluation, parser.in_try, get_swc_comments( parser.comments, diff --git a/crates/rspack_plugin_runtime/src/runtime_module/runtime/async_module.ejs b/crates/rspack_plugin_runtime/src/runtime_module/runtime/async_module.ejs index 106c7d24d045..3719ba7b2401 100644 --- a/crates/rspack_plugin_runtime/src/runtime_module/runtime/async_module.ejs +++ b/crates/rspack_plugin_runtime/src/runtime_module/runtime/async_module.ejs @@ -3,11 +3,20 @@ var rspackQueues = hasSymbol ? Symbol("rspack queues") : "__rspack_queues"; var rspackExports = <%- ASYNC_MODULE_EXPORT_SYMBOL %> = hasSymbol ? Symbol("rspack exports") : "<%- EXPORTS %>"; var rspackError = hasSymbol ? Symbol("rspack error") : "__rspack_error"; var rspackDone = hasSymbol ? Symbol("rspack done") : "__rspack_done"; -var rspackDefer = <%- MAKE_DEFERRED_NAMESPACE_OBJECT_SYMBOL %> = hasSymbol ? Symbol("rspack defer") : "__rspack_defer"; +var rspackDefer = <%- DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES_SYMBOL %> = hasSymbol ? Symbol("rspack defer") : "__rspack_defer"; +<%- DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES %> = <%- basicFunction("asyncDeps") %> { + var hasUnresolvedAsyncSubgraph = asyncDeps.some((id) => { + var cache = <%- _module_cache %>[id]; + return !cache || cache[rspackDone] === false; + }); + if (hasUnresolvedAsyncSubgraph) { + return ({ then(onFulfilled, onRejected) { return Promise.all(asyncDeps.map(<%- REQUIRE %>)).then(onFulfilled, onRejected) } }); + } +} var resolveQueue = <%- basicFunction("queue") %> { - if (queue && queue.d < 1) { - queue.d = 1; - queue.forEach(<%- expressionFunction("fn.r--", "fn") %>); + if (queue && queue.d < 1) { + queue.d = 1; + queue.forEach(<%- expressionFunction("fn.r--", "fn") %>); queue.forEach(<%- expressionFunction("fn.r-- ? fn.r++ : fn()", "fn") %>); } } @@ -15,16 +24,12 @@ var wrapDeps = <%- basicFunction("deps") %> { return deps.map(<%- basicFunction("dep") %> { if (dep !== null && typeof dep === "object") { if(!dep[rspackQueues] && dep[rspackDefer]) { - var asyncDeps = dep[rspackDefer]; - var hasUnresolvedAsyncSubgraph = asyncDeps.some(<%- basicFunction("id") %> { - var cache = <%- _module_cache %>[id]; - return !cache || cache[rspackDone] === false; - }); - if (hasUnresolvedAsyncSubgraph) { + var asyncDeps = <%- DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES %>(dep[rspackDefer]); + if (asyncDeps) { var d = dep; dep = { - then(callback) { - Promise.all(asyncDeps.map(<%- REQUIRE %>)).then(<%- returningFunction("callback(d)", "") %>) + then(onFulfilled, onRejected) { + asyncDeps.then(<%- returningFunction("onFulfilled(d)", "") %>, onRejected); } }; } else return dep; diff --git a/crates/rspack_plugin_runtime/src/runtime_module/runtime/make_optimized_deferred_namespace_object.ejs b/crates/rspack_plugin_runtime/src/runtime_module/runtime/make_optimized_deferred_namespace_object.ejs index 230f606578ae..663f0715de14 100644 --- a/crates/rspack_plugin_runtime/src/runtime_module/runtime/make_optimized_deferred_namespace_object.ejs +++ b/crates/rspack_plugin_runtime/src/runtime_module/runtime/make_optimized_deferred_namespace_object.ejs @@ -21,7 +21,7 @@ } }; <% if (_has_async) { %> - if(isAsync) obj[<%- MAKE_DEFERRED_NAMESPACE_OBJECT_SYMBOL %>] = asyncDeps; + if(isAsync) obj[<%- DEFERRED_MODULES_ASYNC_TRANSITIVE_DEPENDENCIES_SYMBOL %>] = asyncDeps; <% } %> return obj; }; diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/async-mod-dep.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/async-mod-dep.js new file mode 100644 index 000000000000..cf4cadf88cb8 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/async-mod-dep.js @@ -0,0 +1,4 @@ +export {}; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START async-mod-dep.js"); +__configCases__defer_import_async_in_graph_dynamic_import.push("END async-mod-dep.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/async-mod.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/async-mod.js new file mode 100644 index 000000000000..c007336b98a8 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/async-mod.js @@ -0,0 +1,8 @@ +import "./async-mod-dep.js"; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START async-mod.js"); + +await 0; +export let x = 2; + +__configCases__defer_import_async_in_graph_dynamic_import.push("END async-mod.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/deep-async-dep.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/deep-async-dep.js new file mode 100644 index 000000000000..c8197f5fc0f8 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/deep-async-dep.js @@ -0,0 +1,5 @@ +export {}; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START deep-async-dep.js"); +await 0; +__configCases__defer_import_async_in_graph_dynamic_import.push("END deep-async-dep.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/deep-async.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/deep-async.js new file mode 100644 index 000000000000..eddf396f8895 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/deep-async.js @@ -0,0 +1,7 @@ +import "./deep-async-dep.js"; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START deep-async.js"); + +export let x = 3; + +__configCases__defer_import_async_in_graph_dynamic_import.push("END deep-async.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/entry.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/entry.js new file mode 100644 index 000000000000..7f813ae3514a --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/entry.js @@ -0,0 +1,10 @@ +const fullSync = await import.defer("./full-sync.js"); +const asyncMod = await import.defer("./async-mod.js"); +const deepAsync = await import.defer("./deep-async.js"); +const reexportAsync = await import.defer("./reexport-async.js"); + +__configCases__defer_import_async_in_graph_dynamic_import.push("START entry.js"); + +export default { fullSync, asyncMod, deepAsync, reexportAsync }; + +__configCases__defer_import_async_in_graph_dynamic_import.push("END entry.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/full-sync-dep.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/full-sync-dep.js new file mode 100644 index 000000000000..c0e23f5b838e --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/full-sync-dep.js @@ -0,0 +1,4 @@ +export {}; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START full-sync-dep.js"); +__configCases__defer_import_async_in_graph_dynamic_import.push("END full-sync-dep.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/full-sync.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/full-sync.js new file mode 100644 index 000000000000..c44e9f6b4e0b --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/full-sync.js @@ -0,0 +1,7 @@ +import "./full-sync-dep.js"; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START full-sync.js"); + +export let x = 1; + +__configCases__defer_import_async_in_graph_dynamic_import.push("END full-sync.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/index.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/index.js new file mode 100644 index 000000000000..82060a84a077 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/index.js @@ -0,0 +1,59 @@ +it("should compile", async () => { + const logs = global.__configCases__defer_import_async_in_graph_dynamic_import = []; + + let mod = import("./entry.js"); + expect(mod).toBeInstanceOf(Promise); + + let { default: namespaces } = await mod; + + expect(logs).toEqual([ + "START async-mod-dep.js", + "END async-mod-dep.js", + "START async-mod.js", + "END async-mod.js", + "START deep-async-dep.js", + "END deep-async-dep.js", + "START reexport-async-dep-inner.js", + "END reexport-async-dep-inner.js", + "START entry.js", + "END entry.js" + ]); + logs.length = 0; + + let fullSyncX = namespaces.fullSync.x; + expect(fullSyncX).toBe(1); + expect(logs).toEqual([ + "START full-sync-dep.js", + "END full-sync-dep.js", + "START full-sync.js", + "END full-sync.js" + ]); + logs.length = 0; + + let asyncModX = namespaces.asyncMod.x; + expect(asyncModX).toBe(2); + expect(logs).toEqual([]); + + let deepAsyncX = namespaces.deepAsync.x; + expect(deepAsyncX).toBe(3); + expect(logs).toEqual([ + "START deep-async.js", + "END deep-async.js" + ]); + logs.length = 0; + + let reexportAsync = namespaces.reexportAsync.dep; + expect(reexportAsync).not.toBeInstanceOf(Promise); + expect(logs).toEqual([ + "START reexport-async.js", + "END reexport-async.js", + ]); + + logs.length = 0; + let reexportAsyncX = reexportAsync.x; + expect(reexportAsyncX).toBe(4); + expect(logs).toEqual([ + "START reexport-async-dep.js", + "END reexport-async-dep.js", + ]); +}); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async-dep-inner.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async-dep-inner.js new file mode 100644 index 000000000000..723763a7a88e --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async-dep-inner.js @@ -0,0 +1,5 @@ +__configCases__defer_import_async_in_graph_dynamic_import.push("START reexport-async-dep-inner.js"); + +export const x = await 4; + +__configCases__defer_import_async_in_graph_dynamic_import.push("END reexport-async-dep-inner.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async-dep.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async-dep.js new file mode 100644 index 000000000000..cab98391c0f6 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async-dep.js @@ -0,0 +1,4 @@ +export * from "./reexport-async-dep-inner.js"; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START reexport-async-dep.js"); +__configCases__defer_import_async_in_graph_dynamic_import.push("END reexport-async-dep.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async.js new file mode 100644 index 000000000000..de21f2269483 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/reexport-async.js @@ -0,0 +1,7 @@ +import defer * as dep from "./reexport-async-dep.js"; + +__configCases__defer_import_async_in_graph_dynamic_import.push("START reexport-async.js"); + +export { dep }; + +__configCases__defer_import_async_in_graph_dynamic_import.push("END reexport-async.js"); diff --git a/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/rspack.config.js b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/rspack.config.js new file mode 100644 index 000000000000..604b8b0e6a77 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/async-in-graph-dynamic-import/rspack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: [`async-node${process.versions.node.split(".").map(Number)[0]}`], + mode: "none", + experiments: { + deferImport: true + } +}; diff --git a/tests/rspack-test/configCases/defer-import/defer-runtime-dynamic-import-native-syntax/rspack.config.js b/tests/rspack-test/configCases/defer-import/defer-runtime-dynamic-import-native-syntax/rspack.config.js new file mode 100644 index 000000000000..838f41a33260 --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/defer-runtime-dynamic-import-native-syntax/rspack.config.js @@ -0,0 +1,13 @@ +"use strict"; + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + target: [`async-node${process.versions.node.split(".").map(Number)[0]}`], + entry: ["../defer-runtime/all-dynamic-import-native-syntax.js"], + optimization: { + concatenateModules: false + }, + experiments: { + deferImport: true + } +}; diff --git a/tests/rspack-test/configCases/defer-import/defer-runtime/all-dynamic-import-native-syntax.js b/tests/rspack-test/configCases/defer-import/defer-runtime/all-dynamic-import-native-syntax.js new file mode 100644 index 000000000000..273a762b828c --- /dev/null +++ b/tests/rspack-test/configCases/defer-import/defer-runtime/all-dynamic-import-native-syntax.js @@ -0,0 +1,208 @@ +import { + assertTouched as a, + assertUntouched as b, + reset as c +} from "./side-effect-counter.js"; +const [assertTouched, assertUntouched, reset] = [a, b, c]; + +it("should defer the module until first use", async () => { + const dynamic_default = await import.defer("./commonjs/dynamic_default.cjs"); + const dynamic_default_ns = await import.defer("./commonjs/dynamic_default_ns.cjs"); + const dynamic_named = await import.defer("./commonjs/dynamic_named.cjs"); + const dynamic_named_ns = await import.defer("./commonjs/dynamic_named_ns.cjs"); + const dynamic_both = await import.defer("./commonjs/dynamic-both.cjs"); + const dynamic_both_ns = await import.defer("./commonjs/dynamic_both_ns.cjs"); + + const flagged_default = await import.defer("./commonjs/flagged_default.js"); + const flagged_default_ns = await import.defer("./commonjs/flagged_default_ns.js"); + const flagged_named = await import.defer("./commonjs/flagged_named.js"); + const flagged_named_ns = await import.defer("./commonjs/flagged_named_ns.js"); + const flagged_both = await import.defer("./commonjs/flagged_both.js"); + const flagged_both_ns = await import.defer("./commonjs/flagged_both_ns.js"); + + const esm_default = await import.defer("./esm/esm_default.mjs"); + const esm_default_ns = await import.defer("./esm/esm_default_ns.mjs"); + const esm_named = await import.defer("./esm/esm_named.mjs"); + const esm_named_ns = await import.defer("./esm/esm_named_ns.mjs"); + const esm_both = await import.defer("./esm/esm_both.mjs"); + const esm_both_ns = await import.defer("./esm/esm_both_ns.mjs"); + + const { reexport_ns, reexport_cjs_ns } = await import.defer("./esm/reexport.mjs"); + + reset(); + dynamic_default.default; + assertTouched(); + expect(dynamic_default.default()).toBe("func"); + assertTouched(); + + reset(); + dynamic_named.f; + assertTouched(); + expect(dynamic_named.f()).toBe("func"); + expect(new dynamic_named.T().x).toBe(1); + assertTouched(); + + reset(); + dynamic_both.default; + assertTouched(); + expect(dynamic_both.default()).toBe("func"); + expect(dynamic_both.default.x).toBe(1); + expect(new dynamic_both.default.T().x).toBe(1); + assertTouched(); + + // then flagged, without namespace + reset(); + flagged_default.default; + assertTouched(); + expect(flagged_default.default()).toBe("func"); + assertTouched(); + + reset(); + flagged_named.f; + assertTouched(); + expect(flagged_named.f()).toBe("func"); + expect(new flagged_named.T().x).toBe(1); + assertTouched(); + + reset(); + flagged_both.default; + assertTouched(); + expect(flagged_both.default()).toBe("func"); + expect(flagged_both.x).toBe(1); + expect(new flagged_both.T().x).toBe(1); + assertTouched(); + + // then esm, without namespace + reset(); + esm_default.default; + assertTouched(); + expect(esm_default.default()).toBe("func"); + assertTouched(); + + reset(); + esm_named.f; + assertTouched(); + expect(esm_named.f()).toBe("func"); + expect(new esm_named.T().x).toBe(1); + assertTouched(); + + reset(); + esm_both.default; + assertTouched(); + expect(esm_both.default()).toBe("func"); + expect(esm_both.x).toBe(1); + expect(new esm_both.T().x).toBe(1); + assertTouched(); + + // then dynamic with namespace + reset(); + assertIsNamespaceObject(dynamic_default_ns); + assertUntouched(); + Reflect.get(dynamic_default_ns, "default"); + assertTouched(); + expect(Reflect.get(dynamic_default_ns, "default")()).toBe("func"); + assertTouched(); + + reset(); + assertIsNamespaceObject(dynamic_named_ns); + assertUntouched(); + Reflect.get(dynamic_named_ns, "f"); + assertTouched(); + expect(Reflect.get(dynamic_named_ns, "f")()).toBe("func"); + expect(new dynamic_named_ns.T().x).toBe(1); + assertTouched(); + + reset(); + assertIsNamespaceObject(dynamic_both_ns); + assertUntouched(); + Reflect.get(dynamic_both_ns, "default"); + assertTouched(); + expect(Reflect.get(dynamic_both_ns, "default")()).toBe("func"); + expect(Reflect.get(dynamic_both_ns, "x")).toBe(1); + expect(new dynamic_both_ns.T().x).toBe(1); + assertTouched(); + + // then flagged with namespace + reset(); + assertIsNamespaceObject(flagged_default_ns); + assertUntouched(); + Reflect.get(flagged_default_ns, "default"); + assertTouched(); + expect(Reflect.get(flagged_default_ns, "default")()).toBe("func"); + assertTouched(); + + reset(); + assertIsNamespaceObject(flagged_named_ns); + assertUntouched(); + Reflect.get(flagged_named_ns, "f"); + assertTouched(); + expect(Reflect.get(flagged_named_ns, "f")()).toBe("func"); + expect(new flagged_named_ns.T().x).toBe(1); + assertTouched(); + + reset(); + assertIsNamespaceObject(flagged_both_ns); + assertUntouched(); + Reflect.get(flagged_both_ns, "default"); + assertTouched(); + expect(Reflect.get(flagged_both_ns, "default")()).toBe("func"); + expect(Reflect.get(flagged_both_ns, "x")).toBe(1); + expect(new flagged_both_ns.T().x).toBe(1); + assertTouched(); + + // then esm with namespace + reset(); + assertIsNamespaceObject(esm_default_ns); + assertUntouched(); + Reflect.get(esm_default_ns, "default"); + assertTouched(); + expect(Reflect.get(esm_default_ns, "default")()).toBe("func"); + assertTouched(); + + reset(); + assertIsNamespaceObject(esm_named_ns); + assertUntouched(); + Reflect.get(esm_named_ns, "f"); + assertTouched(); + expect(Reflect.get(esm_named_ns, "f")()).toBe("func"); + expect(new esm_named_ns.T().x).toBe(1); + assertTouched(); + + reset(); + assertIsNamespaceObject(esm_both_ns); + assertUntouched(); + Reflect.get(esm_both_ns, "default"); + assertTouched(); + expect(Reflect.get(esm_both_ns, "default")()).toBe("func"); + expect(Reflect.get(esm_both_ns, "x")).toBe(1); + expect(new esm_both_ns.T().x).toBe(1); + assertTouched(); + + // then reexported with namespace + reset(); + assertIsNamespaceObject(reexport_ns); + assertUntouched(); + Reflect.get(reexport_ns, "f"); + assertTouched(); + expect(Reflect.get(reexport_ns, "f")()).toBe("func"); + expect(new reexport_ns.T().x).toBe(1); + assertTouched(); + + reset(); + assertIsNamespaceObject(reexport_cjs_ns); + assertUntouched(); + Reflect.get(reexport_cjs_ns, "default"); + Reflect.get(reexport_cjs_ns, "default").f; + assertTouched(); + expect(Reflect.get(reexport_cjs_ns, "default").f()).toBe("func"); + expect(new reexport_cjs_ns.T().x).toBe(1); + assertTouched(); +}); +function assertIsNamespaceObject(ns) { + if (typeof ns !== "object" || !ns) + throw new TypeError("namespace is not an object."); + if (!ns[Symbol.toStringTag]) + throw new Error( + "namespace object does not have a Symbol.toStringTag property." + ); +} diff --git a/tests/rspack-test/esmOutputCases/basic/async-modules/__snapshots__/esm.snap.txt b/tests/rspack-test/esmOutputCases/basic/async-modules/__snapshots__/esm.snap.txt index f09241bd1a83..6448ffbca64c 100644 --- a/tests/rspack-test/esmOutputCases/basic/async-modules/__snapshots__/esm.snap.txt +++ b/tests/rspack-test/esmOutputCases/basic/async-modules/__snapshots__/esm.snap.txt @@ -106,10 +106,19 @@ var rspackExports = __webpack_require__.aE = hasSymbol ? Symbol("rspack exports" var rspackError = hasSymbol ? Symbol("rspack error") : "__rspack_error"; var rspackDone = hasSymbol ? Symbol("rspack done") : "__rspack_done"; var rspackDefer = __webpack_require__.zS = hasSymbol ? Symbol("rspack defer") : "__rspack_defer"; +__webpack_require__.zT = (asyncDeps) => { + var hasUnresolvedAsyncSubgraph = asyncDeps.some((id) => { + var cache = __webpack_module_cache__[id]; + return !cache || cache[rspackDone] === false; + }); + if (hasUnresolvedAsyncSubgraph) { + return ({ then(onFulfilled, onRejected) { return Promise.all(asyncDeps.map(__webpack_require__)).then(onFulfilled, onRejected) } }); + } +} var resolveQueue = (queue) => { - if (queue && queue.d < 1) { - queue.d = 1; - queue.forEach((fn) => (fn.r--)); + if (queue && queue.d < 1) { + queue.d = 1; + queue.forEach((fn) => (fn.r--)); queue.forEach((fn) => (fn.r-- ? fn.r++ : fn())); } } @@ -117,16 +126,12 @@ var wrapDeps = (deps) => { return deps.map((dep) => { if (dep !== null && typeof dep === "object") { if(!dep[rspackQueues] && dep[rspackDefer]) { - var asyncDeps = dep[rspackDefer]; - var hasUnresolvedAsyncSubgraph = asyncDeps.some((id) => { - var cache = __webpack_module_cache__[id]; - return !cache || cache[rspackDone] === false; - }); - if (hasUnresolvedAsyncSubgraph) { + var asyncDeps = __webpack_require__.zT(dep[rspackDefer]); + if (asyncDeps) { var d = dep; dep = { - then(callback) { - Promise.all(asyncDeps.map(__webpack_require__)).then(() => (callback(d))) + then(onFulfilled, onRejected) { + asyncDeps.then(() => (onFulfilled(d)), onRejected); } }; } else return dep;